秒级启动的集成测试框架

本文介绍了一种秒级启动的集成测试框架,使用该框架可以方便的修改和完善测试用例,使得测试用例成为测试过程的产物。

背景
传统的单元测试,测试的范围往往非常有限,常常覆盖的是一些工具类、静态方法或者较为底层纯粹的类实现,但是一般整个应用代码是比较复杂的,存在不同分层。在DDD中,一般包括:防腐层、领域层、服务层、应用层。越到上层的类其依赖关系越复杂,这些上层的类对象往往不太适合用单测来覆盖。
但是集成测试的启动速度较慢,随着工程的增大,启动速度会越来越慢。这就导致修改和 Debug 测试用例变得非常耗时,一部分人甚至会放弃写测试,而通过系统界面或手动接口测试(Postman等)方式来保证功能正确性。但这样做之后,在未来重构或者开发新需求时很难完整回归已有功能。完整回归已有功能将是每次发布的负担,回归遗漏可能引发线上故障。
测试的执行速度至关重要,这往往会影响人们是否自觉地完成测试覆盖。在实际开发过程中,我们可能需要本地反复执行某些测试用例,并不断修改,如果应用能在10秒内启动完成,那么开发是高效的,否则可能会让人试图通过其他方式来测试功能的正确性。

解决方案
针对这一问题,一个比较直观的想法是让集成测试执行速度和单元测试一个数量级。
一般的Java工程都使用了Spring框架,其应用启动慢,往往是一些涉及网络通信的Bean的初始化过程比较耗时,比如RPC框架、缓存、数据库等,这些中间件的Bean对象初始化都需要和外部建立网络连接,等待数据推送等,有的涉及多次网络通信,将这些Bean完全Mock掉,可大大加快应用启动速度。
很直观的解法是将这些耗时的Bean替换为MockBean,有两种方式:
使用Spring的@Primary注解,并禁止耗时Bean的初始化
Mock Spring容器
第一种方式的困难在于Bean初始化的方式多种多样,有的在init方法中,有的通过BeanPostProcessor动态创建,要精准的禁止这类逻辑的执行是比较困难的。
第二种方式则是自己实现一个Mock的Spring框架,基于约定的方式实现Mock对象的自动加载,以及普通Bean对象和Spring一样的方式初始化,从而实现应用的快速启动。

Mock Spring容器
我们基于第二种思路实现了Mock的Spring容器,但仅仅实现其了基础功能,因为通常我们的工程没有用到Spring比较复杂的能力(大多数工程都是如此)。工程中采用约定大于配置的方式,可以减少Mock的工作量。在Mock Spring框架时其实最需要的是自动构建依赖树的能力,即根据当前Bean对象的依赖关系,按需动态创建一系列其关联的Bean。而且对于外部依赖,可以基于某种约定来优先加载Mock对象,保证所有对象Bean创建是按需的,且不需要网络等待,这样可以实现对象依赖树秒级创建,集测秒级启动。其他特殊的功能可以通过其他方式来绕过,本方案也在不断完善中。从实践来看,启动大约需要1-10秒。
本方案的基本思路如下:

记录接口与实现类的关系,是为了根据接口查找实现类,实现按需加载
Mock对象相当于加了@Primary注解,在同类型中会优先被注入,保证覆盖中间件等外部依赖Bean对象
初始化基础Bean对象,是优先加载@Configuration修饰的类中定义的Bean对象
以下是工程中定义的扫包代码片段,每个测试执行Bean都是按需加载,不会将所有Bean全部创建。
// 确定扫包路径,扫包规则,只有@Component等注解修饰的类才会被注册为Bean
Predicate<Class<?>> classFilter = clazz -> !clazz.getSimpleName().endsWith("Test");
Set<Class<?>> beanClasses = ClassScanUtil.scanPackages(classFilter, // 应用包路径"com.nbf.gateway",
); 以上的应用包路径和Spring Boot应用的扫包路径一致。
以下是Bean初始化简化后的逻辑:
protected <T> T getBeanObject(Class<T> requiredType) {// 首先查找Bean的真实类型Set<Class<?>> beanClassList = implClassMap.get(requiredType);int size = CollectionUtils.size(beanClassList);Class<?> beanClass;if (size > 1) {throw new BusinessException(CommonErrorCode.UNKNOWN_EXCEPTION, requiredType.getName() + "包含多个实现类");} else if (size == 1){beanClass = beanClassList.iterator().next();} else {beanClass = requiredType;}T bean;Constructor<?> constructor = ListUtils.firstElementOf(beanClass.getConstructors());if (null == constructor) {throw new BusinessException(new Exception("class: " + beanClass.getName() + " 构造器为null."));}Class<?>[] classes = constructor.getParameterTypes();Object[] params = new Object[classes.length];for (int i = 0; i < classes.length; ++i) {params[i] = this.getBean(classes[i]);}try {//noinspection uncheckedbean = (T)constructor.newInstance(params);} catch (Exception e) {throw new BusinessException(e);}// 处理@Autowired@和Resourcethis.processMemberBean(bean);// 执行初始化逻辑Method[] methods = beanClass.getDeclaredMethods();for (Method method : methods) {if (method.getAnnotation(PostConstruct.class) != null) {try {method.invoke(bean);} catch (Exception e) {throw new BusinessException(e);}}}return bean;
} 以下是在测试类中获取Bean对象的方法,类似@Autowired。MockApplicationContext即是我们Mock Spring容器类的名字。
public class GroupVersionRepositoryUnitTest {private final static GroupTunnel groupTunnel = MockApplicationContext.getBeanOfType(GroupTunnel.class);
} 
Mock数据库
基于以上的思路,我们还需要Mock数据库、外部依赖、中间件。下面小节将重点介绍Mock数据库的一种实现。
▐ 第一层Mock:Example
Mock数据库,最直观的想法就是使用HashMap,也在很多的工程中有用到。看到很多的实现是,在测试中,我们调用DAO层相关代码替换为在HashMap中操作对应数据。这样的实现有两个比较明显的缺点:
每个数据操作都需要手动翻译为对Map的数据操作,费时费力,容易存在翻译偏差
每次翻译过程,需要case by case处理
当然能做到这种替换,还有个前提是,我们将DAO层的操作都统一封装到了一层,这样才能实现使用Mock对象替换的方式实现整体替换。
解决方案
目前大多数的Java工程都使用Mybatis,解决思路是实现一套类似Mybatis的查询工具类,让写Mock实现和真实的DAO层方法调用类似,让翻译过程尽量简单直观。
为此,我们定义了MockExample对应Mybatis的查询参数Param,MockCriteria对应Criteria(用户暂时不感知),MockTunnelUtil对应DAO,Mock对象和Mybatis真实对象映射关系如下图所示:

MockExample、MockCiteria都以DO(Data Object)作为泛型参数,用于指定操作哪张表
原始某段真实Mybatis查询代码如下:
@Override
public ApiInfoDO get(String apiInfoId) { ApiInfoQuery query = new ApiInfoQuery();query.createCriteria().andApiInfoIdEqualTo(apiInfoId);List<ApiInfoDO> apiInfoDOList = apiInfoDao.selectByQueryWithBLOBs(query);return firstElementOf(apiInfoDOList);
} 翻译后的Mock实现如下: @Override
public ApiInfoDO get(String apiInfoId) { MockExample<SlsJobDO> example = new MockExample<>();example.createCriteria() .andEqualTo(apiInfoId, ApiInfoDO::getApiInfoId);List<ApiInfoDO> apiInfoDOList = MockTunnelUtil.selectByExample(this, example);return firstElementOf(apiInfoDOList);
} 这里有三点需要对照修改:
创建查询参数,比如:ApiInfoQuery,需要替换为创建MockExample
查询条件增加属性的方法引用,比如:ApiInfoDO::getApiInfoId
使用MockTunnelUtil代替DAO进行查询
MockExample实现主要使用了断言Predicate,以下是In条件的实现:
public <F> MockCriteria<DO> andIn(List<F> field, Function<DO, F> getter) {Predicate<DO> predicate = obj -> field.contains(getter.apply(obj));return this.addCondition(predicate);
} ▐ 第二层Mock:DAO
上述实现大大简化了Mock 数据库的难度,但仍然存在如下缺点:
查询 & 修改逻辑变更,Mock逻辑需要跟着变更,存在比较严重的一致性问题
-
很多时候会忘记修改,导致Mock结果和实际运行不一致
如果Mybatis调用逻辑散落各处,没有统一收敛到一层,则Mock比较困难
为此我们需要将Mock的层再向下降一层,直接Mock DAO,在测试中调用DAO,则会调用到我们的Mock实现,做到Mock实现不依赖业务代码变化。
思路
一个比较直观的解决方案是实现一套通用逻辑,将Mybatis的Param直接转换为MockExample,则不需要再手动去写那段翻译逻辑,即可自动将业务实现转换为Mock实现。
难点
这里的一个难点是Mybatis生成的查询Criteria缺乏公共的父类,每个方法的名称都是和用户参数名相关的,比如andApiInfoIdEqualTo。
解决方案
通过分析,我们可以发现,其实问题的根源在于Mybatis的Example、Criteria、Criterion缺乏公共的接口或基类。为了解决这个问题,我们定义了SqlParam、SqlCriterion、SqlCriteria,用来抽象这三个层次的对象。以下是这三个类的定义:
public interface SqlCriteria<Criterion> {List<Criterion> getCriteria();
} public interface SqlCriterion {String getCondition();Object getValue();Object getSecondValue();
} public interface SqlParam<Criteria> {/*** 是否分页*/boolean isPage();/*** 获取页码(1开始)*/Integer getPageIndex();/*** 获取页大小*/Integer getPageSize();/*** 获取排序语句*/String getOrderByClause();/*** 获取查询条件*/List<Criteria> getOredCriteria();
} 我们在Mock DAO层的实现中,定义不同DO的这三个接口实现即可,这样我们就可以基于这些信息将Mybatis Param转换为MockExample了。以下是Mock DAO实现的样例:
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MockApiInfoDaoImpl
extends AbstractMockDaoImpl<ApiInfoDO, ApiInfoQuery, Criteria, Criterion>
implements ApiInfoDao {private static final MockApiInfoDaoImpl INSTANCE = new MockApiInfoDaoImpl();public static MockApiInfoDaoImpl getInstance() {return INSTANCE;}@Overridepublic Function<ApiInfoDO, Object> getIdGetter() {return ApiInfoDO::getId;}@Overrideprotected SqlParam<Criteria> getSqlParam(ApiInfoQuery query) {return new SqlParam<Criteria>() {@Overridepublic boolean isPage() {return query.getRows() != null;}@Overridepublic Integer getPageIndex() {return query.getOffset() / query.getRows() + 1;}@Overridepublic Integer getPageSize() {return query.getRows();}@Overridepublic String getOrderByClause() {return query.getOrderByClause();}@Overridepublic List<Criteria> getOredCriteria() {return query.getOredCriteria();}};}@Overrideprotected SqlCriteria<Criterion> getSqlCriteria(Criteria criteria) {return criteria::getCriteria;}@Overrideprotected SqlCriterion getSqlCriterion(Criterion criterion) {return new SqlCriterion() {@Overridepublic String getCondition() {return criterion.getCondition();}@Overridepublic Object getValue() {return criterion.getValue();}@Overridepublic Object getSecondValue() {return criterion.getSecondValue();}};}
} 以下是Mybatis查询条件Param转换为MockExample的转换逻辑:
protected MockExample<DO> convert(Param param) {MockExample<DO> example = new MockExample<>();// 设置条件boolean first = true;SqlParam<Criteria> sqlParam = getSqlParam(param);for (Criteria criteria : sqlParam.getOredCriteria()) {MockCriteria<DO> mockCriteria;if (first) {mockCriteria = example.createCriteria();first = false;} else {mockCriteria = example.or();}SqlCriteria<Criterion> sqlCriteria = getSqlCriteria(criteria);for (Criterion criterion : sqlCriteria.getCriteria()) {SqlCriterion sqlCriterion = getSqlCriterion(criterion);String condition = sqlCriterion.getCondition();int index = condition.indexOf(NbfSymbolConstants.SPACE);String property = NbfStringUtils.underLineToCamel(condition.substring(0, index).trim());String getterMethod = "get" + StringUtils.capitalize(property);String operator = condition.substring(index + 1).trim();// 添加属性List<Object> valueList = new ArrayList<>();Object value = sqlCriterion.getValue();if (value != null) {valueList.add(value);}Object secondValue = sqlCriterion.getSecondValue();if (secondValue != null) {valueList.add(secondValue);}Function<DO, Object> getter = obj -> {try {Method method = getDoClass().getDeclaredMethod(getterMethod);return method.invoke(obj);} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {throw new BusinessException(e);}};// 操作符OperatorEnum operatorEnum = OperatorEnum.of(operator);mockCriteria.and(operatorEnum, getter, valueList);}}// 设置分页if (sqlParam.isPage()) {example.setPagination(sqlParam.getPageIndex(), sqlParam.getPageSize());}// 设置排序String orderByClause = sqlParam.getOrderByClause();if (StringUtils.isNotBlank(orderByClause)) {example.setOrderByClause(orderByClause);}return example;
} 不同DAO的Mock实现基本类似,只要拷贝并修改泛型参数即可。
在上述的DAO层Mock实现MockApiInfoDaoImpl中,继承了基类AbstractMockDaoImpl,这是由于同一套Mybatis插件生成的DAO接口方法类似,我们可以定义一个抽象类实现这些接口,DAO层Mock实现继承该抽象类,则不需要再去实现DAO层的接口了。其部分实现如下:
public abstract class AbstractMockDaoImpl<DO, Param, Criteria, Criterion>extends AbstractMockTunnelImpl<DO, Param, Criteria, Criterion> {public long countByQuery(Param param) {MockExample<DO> example = this.convert(param);return MockTunnelUtil.countByExample(this, example);}public int deleteByQuery(Param param) {MockExample<DO> example = this.convert(param);return MockTunnelUtil.deleteByExample(this, example);}
} ▐ 小结
这里我们介绍了完整Mock数据库的一种思路,这种Mock实现仍然存在一些缺陷:
暂时无法支持事务
无法实现数据库的特性,比如必填校验等
以上两点都可以在未来支持。它的优点也是比较明显的:
执行速度快
不依赖数据库已有数据,不会受数据库已有数据的影响,不会造成脏数据

造数据
基于以上的两个基础设施:Mock Spring容器、Mock数据库,可以使得写测试变得更加容易,对于测试中比较费时费力的造数据,也可以更加快速的实现。
在日常的集成测试中,造数据是一个比较麻烦的事情,虽然我们使用测试的RollBack机制,可以保证对现有数据无污染。但是在某些依赖已有数据的情况,则比较麻烦。如果预先造了这样的数据,可能被其他人无意修改。而且在一些查询场景,已有数据可能对测试执行结果造成干扰。
有了这套完整的Mock工具,我们可以使用线上数据进行测试,更加快捷的回归 & 发现问题。
▐ 造数据的几种方式
常见造数据的两种方式:
通过属性设值。即各种New对象,Set属性
通过JSON解析文件
第一种方式的开发维护成本较高,尤其是构建大对象时。
造数据的来源,也有两种方式:
通过DO(Data Object)去造数据,即把数据直接插入数据库
通过领域对象造数据,调用Repository去创建数据
根据数据来源也分为日常、线上。显然线上数据质量远高于日常,更容易发现问题。
▐ 方案
将数据库查询到的线上(日常)库数据,转换为领域对象,有比较大的转换成本;如果转换为DO对象,也有一定成本,但成本较低。所以本方案采用了后一种方案。
但是把数据库查询出来的数据拷贝出来,直接转换为DO所需要的JSON格式文件,也有较高的成本,所以这里直接使用字段拆分解析的方式读取其内容,再反射设值到DO对象中。这里有个问题,数据库查询出来的字段顺序可能和DO中字段定义顺序不一致,所以需要有个元信息文件,用于指定数据库查询出来数据的字段顺序。
以下是本测试框架的 TableLoadUtil#Load 方法,用于将数据库查询出来的数据转换为DO数据。第一个参数对应的文件内容是数据库查询出来的各行数据,第二个参数对应的文件内容是DO的字段顺序。
public class TableLoadUtil {/*** 根据元信息定义加载数据* @param fileName 表数据文件路径* @param metadata 表字段顺序元信息定义文件路径* @param clazz DO类*/public static <T> List<T> load(String fileName, String metadata, Class<T> clazz);
} 这样就实现了通过数据库数据直接快速造数据的目的,推荐使用线上数据(但对敏感数据需要脱敏),保证测试质量。
我们需要将测试涉及到的表的少量行数据(不需要全量)查询出来,并添加到对应文件中。对于复杂场景,这种造数据的方式显然更加高效。而且可以做到每个测试的数据都是重新初始化的,互相隔离不影响。这些数据还可以在不同测试间共享。不需要启动完整的Spring容器,只需要启动Mock的Spring容器,保证测试启动(无论工程多么庞大)在10秒以内,大部分测试启动在3秒以内。
比较通用的做法是在测试基类里做数据的初始化和清理,具体的测试类继承该类,以下是一个线上应用的测试基类:
public class DataPrepareBaseOnTable {/*** 准备数据*/@BeforeClasspublic static void prepare() {cleanUp();initData(BackendServiceConfigDO.class, MockBackendServiceConfigMapperImpl.getInstance()::insert);}/*** 清理数据*/@AfterClasspublic static void cleanUp() {MockBackendServiceConfigMapperImpl.getInstance().getCache().clear();}/*** 初始化数据*/public static <DO> void initData(Class<DO> clazz, Consumer<DO> insertMethod) {String objName = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() -2);List<DO> doList = TableLoadUtil.load("table/" + objName + "/" + objName + ".txt","table/Metadata/" + objName + ".txt",clazz);NbfListUtils.forEach(doList, insertMethod);}
} 这里可以设置为整个测试类初始化 & 清理一次数据,也可以设置为单个测试初始化一次(推荐)。
造数据的流程大致如下:

▐ 小结
上述小节介绍了一种,通过直接将数据库查询到的数据转为测试准备数据的方案,该方案的优点如下:
构造数据足够简单快捷
避免了测试数据被外部意外修改,数据变动过程可以通过git记录查到
各个测试之间测试数据隔离
测试执行速度快,绝大部分测试启动在10秒以内
测试数据质量较高,可完全使用线上数据
测试数据相对干净、纯粹,避免测试环境很多脏数据导致测试不稳定
以上的优点主要是相对于集成测试 + @RollBack的传统测试方式
该方案的成本主要在于:创建字段顺序文件。但对于每张表是一次性的,后续增加字段只需追加新增字段即可
可能的不足是:
如果数据库新增字段,可能需要更新对应表文件
如果测试不涉及新增字段,大部分是向前兼容的
当数据量较大时,管理表文件可能有一定成本
-
推荐使用文件行排序,避免插入重复数据,且要使得数据尽量少,仅包含测试需要的行数据

Mock中间件
mock中间件相对较为简单,这里仅把我们的方案做简单介绍。
▐ Mock RPC框架
只需要创建Mock对象,记录测试case情况下,日常或线上该接口的出参即可,推荐用JSON文件保存,让Mock方法根据入参加载对应的出参JSON文件作为结果返回。
▐ Mock Redis
也通过HashMap进行Mock即可,实现复杂度取决于需要覆盖其功能的完整性。
▐ 小结
本文介绍了一种秒级启动的集成测试框架,使用该框架可以方便的修改和完善测试用例,使得测试用例成为测试过程的产物。测试通过之后,也同时沉淀了覆盖多种测试场景的测试用例。可以方便的使用线上数据作为数据来源,保证测试的质量。甚至在遇到线上问题时,可以将这些数据作为数据来源,用测试用例执行来反复重现 & Debug这些问题,同时沉淀线上问题的测试用例,保证后续代码改造或重构不会重新触发该故障。

团队介绍
物流技术基础技术团队,主要技术产品:NBF(New-Retail Business Framework), 提供了服务DevOps,LowCode编排和云原生基础设施能力,旨在成为新零售PaaS平台化和SaaS产品化的技术底座。
¤ 拓展阅读 ¤
3DXR技术 | 终端技术 | 音视频技术
服务端技术 | 技术质量 | 数据算法
相关文章:
秒级启动的集成测试框架
本文介绍了一种秒级启动的集成测试框架,使用该框架可以方便的修改和完善测试用例,使得测试用例成为测试过程的产物。 背景 传统的单元测试,测试的范围往往非常有限,常常覆盖的是一些工具类、静态方法或者较为底层纯粹的类实现&…...
Redux 数据仓库
Redux 数据仓库 解决React 数据管理(状态管理) ,用于中大型,数据比较庞大,组件之间数据交互多的情况下使用。 作者:如果你不知道是否需要使用Redux,那么你就不需要它! 解决组件的数据通信。 …...
[毕设记录]@开题调研:一些产品
我感觉产品能代表落地的一些实际应用,会和研究的角度有些差别,但是需求和兴趣往往是从现实中来的,在上一篇blog里面看外国blog的时候顺着搜搜到了很多国外的智慧校园chatbot解决方案 文章目录 Comm100streebomodern campusUniBuddy Comm100 …...
CSS3中的字体和文本样式
CSS3优化了CSS 2.1的字体和文本属性,同时新增了各种文字特效,使网页文字更具表现力和感染力,丰富了网页设计效果,如自定义字体类型、更多的色彩模式、文本阴影、生态生成内容、各种特殊值、函数等。 1、字体样式 字体样式包括类…...
LVS集群-DR模式【部署高可用LVS-DR集群】
文章目录 2.2 实战:配置LVS-DR集群2.2.1 配置IP(Director Server的部署配置)2.2.2 生成ens33:1配置文件 (Director Server的部署配置)2.2.3 配置LVS-DR规则(Director Server的部署配置)2.2.4 两…...
银河麒麟服务器版v4安装程序缺少依赖包,改为利用手机联网在线安装
1 将安卓手机连接使用usb转typec线连接到服务器的usb口。(linux桌面版)也可以类似的方法手机联网。 2 在手机热点中打开usb共享 3 使用ifconfig命令找到手机被服务器识别成的网卡名 4 使用dhclient “手机网卡名”命令,使服务器能上网。 5 变…...
Maven第一章:Maven安装、验证、使用
Maven第一章:Maven安装、验证、使用 前言 谁适合阅读本教程? Java开发人员:Maven是Java项目管理和构建工具,因此对Java开发人员来说是一个重要的工具。阅读Maven知识可以帮助他们更好地理解如何使用Maven来管理Java项目,包括依赖管理、构建自动化、项目构建和部署等。项目…...
ios 代码上下文截屏之后导致的图片异常问题
业务场景,之前是直接将当前的collectionview截长屏操作,第一次截图会出现黑色部分原因是视图未完全布局,原因是第一次使用了Masonry约束然后再截图的时候进行了frame赋值,可以查看下Masonry约束和frame的冲突,全部修改…...
《嵌入式软\硬件开发难点-2023-10-29》
一、《嵌入式软件开发难点》 内存有限、螺蛳壳里做道肠;处理能力有限,必须做好规划,榨取系统每一份处理能力;现代开发工具和实际工具难实施,资源问题:C/Python/Java;调试跟踪问题较困难&#x…...
基于5G工业CPE打造智慧煤矿无人巡检监测应用
煤炭是我国重要的能源资源,对于煤炭的开采和利用也是我国重要的工业产业部分。得益于5G物联网技术的发展普及,煤矿场景也迎来智能化升级,实现了包括智能采掘、智能调度、无人运输、无人巡检等新型应用,极大提升了煤矿采运产业的效…...
考点之数据结构
概论 时间复杂度和空间复杂度是计算机科学中用来评估算法性能的重要指标。 时间复杂度: 时间复杂度衡量的是算法运行所需的时间。它表示算法执行所需的基本操作数量随着输入大小的增长而变化的趋势。 求法: 通常通过分析算法中基本操作执行的次数来…...
07、SpringCloud -- jmeter 压测
目录 jmeter 入门jmeter 安装测试步骤测试数据模拟多用户操作1、创建http请求2、添加http cookie 管理器3、并发获取当前登录用户数据的效果4、添加多个用户模拟并发请求5、访问方法6、jmeter添加 CSV Data Set Config7、高并发执行访问的效果8、总结流程高并发秒杀压测jmeter …...
省市区三级联动查询redis(通过python脚本导入数据)
最近工作有一个工作需求是实现省市区联动,点击省下拉框,选中一个省,然后再选市,最后选区,当然最重要的首先自然是数据了,没数据怎么测试接口,我数据是在 https://hxkj.vip/demo/echartsMap/ 这里…...
Linux命令(108)之dirname
linux命令之dirname 1.dirname介绍 linux命令dirname是用来获取文件的指定路径 2.dirname用法 dirname [参数] NAME dirname参数 参数说明-z使用NUL而不是换行符分隔输出--help查看帮助信息--version查看版本信息 3.实例 3.1.获取文件的指定路径 命令: dirn…...
SDL事件处理以及线程使用(2)
事件使用 #include <stdio.h> #include <SDL.h>#define FF_QUIT_EVENT (SDL_USEREVENT 1) // 定义自定义事件#undef main int main() {SDL_Window* pWindow NULL;SDL_Init(SDL_INIT_VIDEO);// 创建窗口pWindow SDL_CreateWindow("Event Test Title&…...
DAY38 动态规划 + 509. 斐波那契数 + 70. 爬楼梯 + 746. 使用最小花费爬楼梯
动态规划理论 动态规划,Dynamic Programming, DP, 如果某一问题有很多重叠子问题,使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导…...
Redis快速上手篇七(集群-一台虚拟机六个节点)
http://t.csdnimg.cn/S0NpK与上篇六个虚拟机配置基本一样有不懂可以看上篇配置实例 集群搭建 根据上篇文章,本篇只着重于小方面的配置差别 配置集群一般不要设置密码 1.搭建一台虚拟机后再安装目录下新建文件夹 redis_cluster 2.在文件夹内创建六个文…...
社恐了怎么办?如何改变社交恐惧症?
社恐这个词已经算是普及了,自嘲自己是社恐的人真的挺多的,好像一句我社恐了就能解析很多问题,其实真正的社恐远比我们想象的要痛苦多了,社恐能被更多人认识到本来是件好事,但是过于的用社恐来给自己贴标签,…...
HiQPdf Library for .NET - HTML to PDF Crack
HiQPdf Library for .NET - HTML 到 PDF 转换器 .NET Core,用于 .NET 的 HiQPdf HTML 到 PDF 转换器 :HiQPdf HTML to PDF Library for .NET C# 和 HTML to PDF .NET Core 为您提供了一个现代、快速、灵活且强大的工具,只需几行代码即可创建复…...
ES6中Set集合
ES6中的Set是一种数据结构,类似于数组,但是它的值都是唯一的。它是通过一组有序的、由唯一元素组成的集合实现的,不允许重复项。Set可以用于存储任何类型的数据,包括原始类型和复合类型,如对象和数组。 Set有以下特点…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...
