手写Spring-MVC之前后置处理器与异常处理、数据库框架
Day48
手写Spring-MVC之前后置处理器与异常处理
前后置处理器
概念:从服务器获取的JSON数据可能是加密后的,因此服务端获取的时候需要进行解密(前置处理器)。
而从服务器传出的JSON数据可能需要加密,因此需要在处理返回值的时候进行加密(后置处理器)。
思路:
首先搭建前后置处理器的框架:
和是否处理JSON格式的数据类似,需要根据注解判断controller层中的方法是否需要对JSON格式的数据进行解密或者返回数据进行加密,因此要添加两个注解@BeforeAdviser和@AfterAdviser
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface BeforeAdviser { }@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AfterAdviser { }
controller层的方法需要添加相应的注解标识
/*** 使用postman发送请求* url:http://localhost:8080/user/test12.action* json:{"username":"zs","password":"123123"}* 传递JSON参数和返回JSON*/@RequestMapping("/test12.action")@ResponseBody@AfterAdviserpublic User test12(@RequestBody @BeforeAdviser User user){System.out.println("user对象:"+ user);return user;
同时需要在参数描述类中和方法描述类中添加是否有相应注解的属性:
/*** 参数描述类 */ @NoArgsConstructor @AllArgsConstructor @Data public class ParameterDefinition {private String name;//参数名private Class<?> clazz;//参数类型private int index;//参数下标private Type[] actualTypeArguments;//参数泛型的数组private boolean requestBodyHasOrNot;//参数上是否有@RequestBody注解private boolean beforeAdviserHasOrNot;//参数上是否有@BeforeAdviser注解}/*** 方法描述类 */ @NoArgsConstructor @AllArgsConstructor @Data public class MethodDefinition {private String requestMappingPath;//子级URiprivate String name;//方法名private Method method;//方法对象private Class<?> returnClazz;//返回值类型private List<ParameterDefinition> parameterDefinitions;//参数描述类对象的集合private boolean responseBodyHasOrNot;//方法上是否有@ResponseBody注解private boolean afterAdviserHasOrNot;//方法上是否有@AfterAdviser注解 }
添加后监听器中封装部分也要进行相应修改:
//获取参数上是否有@BeforeAdviser注解 boolean beforeAdviserHasOrNot = false; BeforeAdviser beforeAdviser = parameters[i].getAnnotation(BeforeAdviser.class); if(beforeAdviser!=null){beforeAdviserHasOrNot = true; }ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments,requestBodyHasOrNot,beforeAdviserHasOrNot);//封装参数描述类对象 parameterList.add(parameterDefinition);
//获取方法上是否有@AfterAdviser注解 boolean afterAdviserHasOrNot = false; AfterAdviser afterAdviser = method.getAnnotation(AfterAdviser.class); if(afterAdviser!=null){afterAdviserHasOrNot = true; } MethodDefinition methodDefinition = new MethodDefinition(sonUri, methodName, method, returnType, parameterList,responseBodyHasOrNot,afterAdviserHasOrNot);//封装方法描述类对象
至此,监听器就能把信息记录下来,调度的DispatcherServlet进行工作的时候就可以获取到相应的注解信息。
前置处理器的使用是在获取参数类型的时候会判断是否有@BeforeAdviser注解,如果有则代表需要进行解密操作:
//解密 if(parameterDefinition.isBeforeAdviserHasOrNot()){//在这里进行具体的解密操作吗? }
在处理返回值的时候会判断方法是否有@AfterAdviser注解,如果有则代表需要进行加密操作:
//加密 if(methodDefinition.isAfterAdviserHasOrNot()){//在这里进行加密操作吗? }
进行具体的解密和加密操作:
前后置处理器的框架搭建好之后,会发现一个问题,如果在DispatcherServlet中进行具体的解密加密的话,那么在用户使用该框架的时候,就只能使用框架所规定的解密加密,这显然不具有灵活性。不同的用户解密、加密逻辑不同,所以这里的思路是在框架中只写抽象类,在DispatcherServlet中利用多态创建抽象类的继承类对象,调用继承类对象中的解密加密方法。而在web模块中用户可以自己重写一个解密加密抽象方法,这样调用的就是用户自定义的逻辑方法了。
抽象方法:
public abstract class HanderAdviserResolver {public abstract String beforeRequestBody(String reqData);public abstract String afterResponseBody(String respData);public String before(String reqData){return beforeRequestBody(reqData);}public String after(String respData){return afterResponseBody(respData);} }
注意:这里的抽象方法是交给用户重写的,而自己的成员方法则直接调用抽象方法,通过这种方式实现在DispatcherServlet中调用用户重写方法的逻辑。
用户自定义前后置处理器:
public class BeforeAndAfterAdviser extends HanderAdviserResolver {@Overridepublic String beforeRequestBody(String reqData) {System.out.println("解密:"+reqData);return reqData;}@Overridepublic String afterResponseBody(String respData) {System.out.println("加密:"+respData);return respData;} }
又一个问题来了,框架中怎样拿到用户自定义的类对象呢?思路和监听器拿到controller层类对象相似,通过配置文件拿到注解类,注解类通过一个注解注明用户自定义的前后置处理器路径。这样的注解叫做使能注解,它的功能就是使得DispatcherServlet能够拿到用户自定义类。然后在DispatcherServlet中重写init()方法,在方法中获取配置文件信息,进而拿到注解类,通过注解类的注解信息拿到自定义前后置处理器类对象,调用其重写的解密加密处理方法。
使能注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EnableAdviser {String adviserPackage(); }
注解类:
@EnableAdviser(adviserPackage= "com.qf.shop.web.adviser.BeforeAndAfterAdviser")
DispatcherServlet中:
private HanderAdviserResolver adviserResolver;public HanderAdviserResolver getAdviserResolver(String config){try {Class<?> clazz = Class.forName(config);EnableAdviser enableAdviserAnnotation = clazz.getAnnotation(EnableAdviser.class);String adviserPackage = enableAdviserAnnotation.adviserPackage();if(adviserPackage!=null){Class<?> adviserClass = Class.forName(adviserPackage);adviserResolver = (HanderAdviserResolver) adviserClass.newInstance();}return adviserResolver;} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}}@Overridepublic void init() throws ServletException {ServletContext servletContext = this.getServletContext();String config = servletContext.getInitParameter("config");adviserResolver = getAdviserResolver(config);}
//加密 if(methodDefinition.isAfterAdviserHasOrNot()){jsonString = adviserResolver.after(jsonString); }
//解密 if(parameterDefinition.isBeforeAdviserHasOrNot()){jsonStr = adviserResolver.before(jsonStr); }
异常
概念:DispatcherServlet中对于全局的异常需要进行处理,而具体如何处理也是由业务决定的,换言之是用户进行定义而非框架中写死。但是框架中又需要调用处理异常的方法,如何处理?-和前后置处理器一样,通过多态,servlet调用的是用户继承框架中抽象类的类重写的方法。
抽象类:
public abstract class HanderGlobalException {public abstract void handlerException(Exception err, HttpServletRequest request, HttpServletResponse response);public void hander(Exception err, HttpServletRequest request, HttpServletResponse response){handlerException(err,request,response);} }
用户继承类:
public class GlobalException extends HanderGlobalException {@Overridepublic void handlerException(Exception err, HttpServletRequest request, HttpServletResponse response) {System.out.println("处理全局异常......");try {request.getRequestDispatcher("/err.jsp").forward(request,response);} catch (ServletException | IOException e) {throw new RuntimeException(e);}} }
那么框架如何拿到用户自己写的处理异常类和方法呢?思路和前后置处理器一样,DispactherServlet在重写的init()方法中通过配置文件拿到配置类,配置类通过框架中写的使能注解将用户自定义的异常处理类路径告诉servlet。
使能注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EnableException {String exceptionPackage(); }
配置类:
/*** 当前项目的配置类 */ @Configuration("com.qf.shop.web.controller") @EnableAdviser(adviserPackage= "com.qf.shop.web.adviser.BeforeAndAfterAdviser") @EnableException(exceptionPackage = "com.qf.shop.web.globalException.GlobalException") public class AppConfig { }
DispatcherServlet:
private HanderGlobalException globalException;public HanderGlobalException getGlobalException(String config){try {Class<?> clazz = Class.forName(config);EnableException enableExceptionAnnotation = clazz.getAnnotation(EnableException.class);String exceptionPackage = enableExceptionAnnotation.exceptionPackage();if(exceptionPackage!=null){Class<?> exceptionClass = Class.forName(exceptionPackage);globalException = (HanderGlobalException) exceptionClass.newInstance();}return globalException;} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);} }@Overridepublic void init() throws ServletException {ServletContext servletContext = this.getServletContext();String config = servletContext.getInitParameter("config");adviserResolver = getAdviserResolver(config);globalException = getGlobalException(config);}
try{//调用Controller层里的某个方法Object returnVal = method.invoke(t, args);if(returnVal!=null){//处理返回值handlerReturnVal(methodDefinition,returnVal,request,response,model);} }catch (Exception e){globalException.hander(e,request,response);//处理全局异常 }
数据库模块
功能:该模块不是Spring-MVC中的一部分,是用来和数据库交互的框架。
思路:分包逐步实现JDBC。
base包:根据获得的结果集实现封装功能的接口,包含结果集处理器接口和行处理器接口
/** * 结果集处理器的接口 *实现类:BeanHandler(获取单个对象)、BeanListHandler(获取对象集合) */ public interface ResultSetHandler<T> {public T handler(ResultSet resultSet)throws SQLException,IllegalAccessException,InstantiationException, InvocationTargetException; }
/** * 行处理器的接口 * @param <T> */ public interface RowProcessor<T> {public T toArray(ResultSet resultSet) throws SQLException; }
handler包:实现接口:
/** * 结果集处理接口的实现类 * 操作结果集并封装实体类 * @param <T> */ public class BeanHandler<T> implements ResultSetHandler<T> {private Class beanClass;public BeanHandler(Class beanClass) {this.beanClass = beanClass;}@Overridepublic T handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();if(resultSet.next()){T t = (T) beanClass.newInstance();for (int i = 0; i < columnCount; i++) {String columnName = metaData.getColumnName(i + 1);Object columnValue = resultSet.getObject(columnName);BeanUtils.copyProperty(t,columnName,columnValue);}return t;}return null;} }
/** * 结果集处理接口的实现类 * 处理结果集并封装为集合 * @param <T> */ public class BeanListHandler<T> implements ResultSetHandler<List<T>> {private Class beanListClass;public BeanListHandler(Class beanListClass) {this.beanListClass = beanListClass;}@Overridepublic List<T> handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {List<T> list = new ArrayList<>();ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();while(resultSet.next()){T t = (T) beanListClass.newInstance();for (int i = 0; i < columnCount; i++) {String columnName = metaData.getColumnName(i + 1);T columnValue = (T) resultSet.getObject(columnName);BeanUtils.copyProperty(t,columnName,columnValue);list.add(t);}}return list;} }
/** * 结果集处理接口的实现类 * 操作结果集并封装数组对象 * * @param <T> */ public class ArrayHandler<T> implements ResultSetHandler<T> {//行处理器private RowProcessor<T> rowProcessor;public ArrayHandler(RowProcessor<T> rowProcessor) {this.rowProcessor = rowProcessor;}@Overridepublic T handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {return rowProcessor.toArray(resultSet);} }
其中,行处理的逻辑是拿到结果集后交给自定义的行处理接口实现类方法处理,这样做的目的是统一代码的格式(见后续web项目中的使用)。
processor包(处理行):
/** * 行处理器的实现类,将结果集中的数据获取并封装成数组 * @param <T> */ public class BaskRowProcessor<T> implements RowProcessor<T[]> {private Class arrayTClass;public BaskRowProcessor(Class arrayTClass) {this.arrayTClass = arrayTClass;}@Overridepublic T[] toArray(ResultSet resultSet) throws SQLException {//创建数据容器List<T> list = new ArrayList<>();//遍历结果集while(resultSet.next()){T t = (T) resultSet.getObject(1);list.add(t);}if(list.size()==0||resultSet==null){throw new RuntimeException("参数异常无法获取泛型数组");}else {//创建数组T[] ts = (T[]) Array.newInstance(arrayTClass, list.size());//添加数据for (int i = 0; i < list.size(); i++) {ts[i] = list.get(i);}return ts;}} }
通过结果集返回封装好的对象、列表、数组的功能已实现,接下类实现操作数据库返回结果集的功能:
core包:
public class QueryRunner {private DruidDataSource dataSource;private ThreadLocal<Connection> local = new ThreadLocal<>();public QueryRunner(DruidDataSource dataSource) {this.dataSource = dataSource;}//获取连接private Connection getConnection() throws SQLException {Connection connection = local.get();if(connection==null){connection = dataSource.getConnection();local.set(connection);}return connection;}//配置参数private PreparedStatement getPreparedStatement(Connection connection,String sql,Object... args) throws SQLException {PreparedStatement statement = connection.prepareStatement(sql);for (int i = 0; i < args.length; i++) {statement.setObject(i+1,args[i]);}return statement;}//更新操作public int update(String sql,Object... args) throws SQLException {Connection connection = getConnection();PreparedStatement statement = getPreparedStatement(connection, sql, args);int i = statement.executeUpdate();return i;}//查询操作public <T> T query(ResultSetHandler<T> handler,String sql,Object... args) throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {Connection connection = getConnection();PreparedStatement statement = getPreparedStatement(connection, sql, args);ResultSet resultSet = statement.executeQuery();T t = handler.handler(resultSet);return t;}}
至此,框架搭建完毕,接下来以用户创建的web项目举例:
数据库工具类:
public class JDBCUtils {//德鲁伊连接池引用private static DruidDataSource dataSource;static{//创建德鲁伊连接池dataSource = new DruidDataSource();//获取配置信息Properties properties = new Properties();try {properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("DBConfig.properties"));String username = properties.getProperty("username");String password = properties.getProperty("password");String url = properties.getProperty("url");String driverName = properties.getProperty("driverName");dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setDriverClassName(driverName);} catch (IOException e) {throw new RuntimeException(e);}}public static QueryRunner getQueryRunner(){return new QueryRunner(dataSource);} }
controller层中对数据库进行操作:
//---操作数据库--------------------------------------------------------------------------------------- @RequestMapping("/test13.action") public void test13() throws SQLException {//操作更新语句JDBCUtils.getQueryRunner().update("update user set password = ? where username = ?","123456","zs"); } @RequestMapping("/test14.action") public void test14() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询对象User zs = JDBCUtils.getQueryRunner().query(new BeanHandler<>(User.class), "select * from user where username = ?", "zs");System.out.println(zs); } @RequestMapping("/test15.action") public void test15() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询对象列表List<User> users = JDBCUtils.getQueryRunner().query(new BeanListHandler<>(User.class), "select * from user");for (User user : users) {System.out.println("列表中的对象:"+user);} } @RequestMapping("/test16.action") public void test16() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询所有的usernameString[] usernames = JDBCUtils.getQueryRunner().query(new ArrayHandler<>(new BaskRowProcessor<>(String.class)),"select username from user");System.out.println(usernames); }
遇到的问题:
1.无法找到数据库连接的配置文件DBCfig.properties
解决方案:将配置文件放到src/main/resources文件夹中去,这样 资源文件才会被复制到target/classes 目录下。
相关文章:
手写Spring-MVC之前后置处理器与异常处理、数据库框架
Day48 手写Spring-MVC之前后置处理器与异常处理 前后置处理器 概念:从服务器获取的JSON数据可能是加密后的,因此服务端获取的时候需要进行解密(前置处理器)。 而从服务器传出的JSON数据可能需要加密,因此需要在处理返…...

学习笔记(linux高级编程)11
进程间通信 》信号通信 应用:异步通信。 中断,, 1~64;32应用编程。 如何响应: Term Default action is to terminate the process. Ign Default action is to ignore the signal. wait Core Default action is …...
vite+vue3+nginx配置统一公共前缀
方案1:重定向 server {listen 80;server_name localhost;location / {root /usr/share/nginx/html;index index.html;}location /music/ {proxy_pass http://127.0.0.1:80/;} }方案2:vitenginx双重配置 在方案1中,我们虽然能够实现 通过 …...
android 国内下载Gradle源
在中国使用 Gradle 时,可以配置使用一些国内的镜像源,以提高下载速度和稳定性。以下是几个常用的 Gradle 镜像源地址: 配置 gradle-wrapper.properties 文件: 阿里云: distributionUrlhttps\://services.gradle.org/distributions/gradle-7.…...
mysql8一键安装脚本(linux) 拿走即用
创建一个shell文件,将下面的代码放里面去,然后放到linux服务器上运行就可以了 #!/bin/bash#---------------------* # * # 2021-10-08 * # install mysql-8 * # * #---------------------*route=/usr #包存放路径 mys…...
C# 开发Winform DataGridView的增删改查实战
在C# WinForms应用程序中,DataGridView控件是一个非常强大的工具,用于显示和编辑表格数据。下面我将详细介绍如何在WinForm应用程序中使用DataGridView实现基本的数据库操作:增加、删除、修改和查询(CRUD)。 第一步&a…...
CentOS 7镜像列表服务下线,还想继续使用该怎么办?
目录 问题和解决方法 mirrorlist.centos.org 作用 vault.centos.org 作用 CentOS 7的生命周期已经在2024年6月30日终止(End of Life,EOL),官方将不再对该版本进行问题修复、功能更新以及其他形式的维护支持。这意味着使用 Cent…...
代码随想录训练营第二十八天 122买卖股票的最佳时间II 55跳跃游戏 45跳跃游戏II 1005K次取反后最大化的数组和
第一题: 原题链接:122. 买卖股票的最佳时机 II - 力扣(LeetCode) 思路: 这题十分简单,就是把相邻天数的金额相减,如果发现大于0就加到res中,返回res即可 代码如下: …...

在node环境使用MySQL
什么是Sequelize? Sequelize是一个基于Promise的NodeJS ORM模块 什么是ORM? ORM(Object-Relational-Mapping)是对象关系映射 对象关系映射可以把JS中的类和对象,和数据库中的表和数据进行关系映射。映射之后我们就可以直接通过类和对象来操作数据表和数据了, 就…...

spdlog一个非常好用的C++日志库(四): 源码分析之logger类
目录 1.简介 2.类图关系 3.logger数据成员 4.logger函数成员 4.1.构造与析构 4.1.1.构造函数 4.1.2.拷贝构造、移动构造 4.2.交换操作 4.3.log()记录日志消息 4.3.1.格式串 4.3.2.普通字符串 4.3.3.日志级别 4.3.4.宽字符支持 4.4.sink_it_:将log消息…...

逻辑这回事(七)---- 器件基础
Xilinx FPGA创建了先进的硅模块(ASMBL)架构,以实现FPGA具有针对不同应用程序领域优化的各种功能组合的平台。通过这一创新,Xilinx提供了更多的设备选择,使客户能够为其特定设计选择具有正确的功能和功能组合的FPGA。ASMBL体系结构通过以下方式突破了传统的设计障碍:消除几…...

中俄汽车产业链合作前景广阔,东方经济论坛助力双边合作与创新
随着中国汽车零部件企业的竞争力和创新能力不断增强,中国汽车及零部件行业在俄罗斯的市场份额和品牌影响力显著提升,中俄两国在汽车产业链上的合作展现出巨大的潜力和广阔的前景。2024年5月,俄罗斯乘用车新车销量达到12.8万辆,同比…...

第六篇:精通Docker Compose:打造高效的多容器应用环境
精通Docker Compose:打造高效的多容器应用环境 1. 引言 1.1 目的与重要性 在现代软件开发中,随着应用程序的复杂性不断增加,传统的单一容器部署方式已无法满足需求。Docker Compose作为一种强大的工具,专门用于定义和运行多容器…...

C++视觉开发 一.OpenCV环境配置
一.OpenCV安装环境配置 1.OpenCV安装 (1)下载 官方下载链接:http://opencv.org/releases 这边选择需要的版本,我是在windows下的4.9.0。(科学上网下载很快,否则可能会有点慢) (2)安装 双击下…...
大数据面试题之Kafka(3)
目录 Kafka支持什么语义,怎么实现ExactlyOnce? Kafka的消费者和消费者组有什么区别?为什么需要消费者组? Kafka producer的写入数据过程? Kafka producer的ack设署 Kafka的ack机制,解决了什么问题? Kafka读取消息是推还是拉的模式?有什…...

视频监控平台web客户端的免密查看视频页:在PC浏览器上如何调试手机上的前端网页(PC上的手机浏览器的开发者工具)
目录 一、手机上做前端页面开发调试 1、背景 2、视频监控平台AS-V1000的视频分享页 3、调试手机前端页面代码的条件 二、手机端的准备工作 1、手机准备 2、手机的开发者模式 3、PC和手机的连接 (1)进入调试模式 (2)选择…...
力扣2488.统计中位数为 K 的子数组
力扣2488.统计中位数为 K 的子数组 等价转换 子数组为奇数 : 左小 右小 左大 右大 左小 – 左大 右大 – 右小 子数组为偶数 : 左小 右小 左大 右大 – 1 左小 – 左大 右大 – 右小 - 1提示中说明k为两数中左边那个 先从k的下标pos开始往左逆序…...

Zabbix对接Elasticsearch(ES)数据库(未成功)
0.需求分析 不管zabbix的后端数据库是oracle还是mysql,当zabbix监控的量级达到了一定程度后,那么对数据库的性能是一个非常严峻的挑战。特别是对历史数据的查询,将会变得非常非常的慢,别告诉我可以建索引优化,当量级达…...

【unity实战】使用Unity实现动作游戏的攻击 连击 轻重攻击和打击感
最终效果 文章目录 最终效果前言素材下载:玩家移动跳跃控制攻击动画配置轻攻击重攻击 攻击时禁止移动和攻击移动补偿敌人击退和播放受击动画受击特效攻击停顿和屏幕震动局部顿帧(补充)参考源码完结 前言 注意本文为自己的学习记录笔记&#…...

ELK 企业实战7
ELKkafkafilebeat企业内部日志分析系统 1、组件介绍 1、Elasticsearch: 是一个基于Lucene的搜索服务器。提供搜集、分析、存储数据三大功能。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的ÿ…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...