# treasure **Repository Path**: manong99898/treasure ## Basic Information - **Project Name**: treasure - **Description**: Treasure是一个Java技术生态项目,涵盖了单体、微服务、DDD等架构实践,以兴趣、学习目的、技术积累为理念,逐步完善迭代。主要包含学习成长过程中一些技术点、工作中积累的一些心得,面试中一些业务场景模拟及解决方案一些常见、通用业务的解决方案、合理应用设计模式进行一些业务代码的重构优化、常用小轮子的积累、一些更优雅的编码实现、通用场景封装等内容。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 61 - **Created**: 2023-12-07 - **Last Updated**: 2023-12-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
2020-05-05~至今
P: 持久化对象
implements BaseCrudController
, BaseViewController { /** * 服务 */ @Autowired private IService
iService; /** * 根据Id查询,返回单个实体 * * @param id 数据表主键 * @return {@link ResultVO} 结果 */ @ApiOperation("根据唯一键查询,返回单个对象") @ApiOperationSupport(author = "dingwen") @ApiImplicitParam(value = "唯一键", name = "id", dataTypeClass = Long.class) @GetMapping("/default/{id}") @Override public ResultVO find(@PathVariable Serializable id) { return success(iService.getById(id)); } /** * 分页查询 * 分页参数由分页工具自动从Servlet上下文中获取 * * @param p 查询实体 * @return {@link ResultVO}<{@link PageVO} */ @ApiOperation("分页查询,返回PageVO") @ApiOperationSupport(author = "dingwen") @GetMapping("/default/page") @Override public ResultVO> findPage(@ModelAttribute P p) { PageUtils.startPage(); return page(iService.list(new QueryWrapper<>(p))); } /** * 根据唯一键集查询数据对象 * * @param ids 唯一键集 * @return {@link ResultVO}<{@link List} */ @ApiOperation("根据唯一键集查询,返回对象列表") @ApiOperationSupport(author = "dingwen") @ApiImplicitParam(value = "唯一键集", name = "ids", dataTypeClass = List.class) @GetMapping("/default/{ids}") @Override public ResultVO> find(@PathVariable List ids) { return success(iService.listByIds(ids)); } /** * 获取数据表总记录条数 * * @return {@link ResultVO}<{@link Long}> */ @ApiOperation("获取数据表总记录条数,返回统计数量") @ApiOperationSupport(author = "dingwen") @GetMapping("/default/count") @Override public ResultVO count() { return success(iService.count()); } /** * 通过唯一键查询是否存在 * * true: 存在 * false: 不存在 * * * @param id 唯一键 * @return {@link ResultVO} */ @ApiOperation("通过唯一键查询是否存在,返回布尔值") @ApiOperationSupport(author = "dingwen") @GetMapping("/default/exists/{id}") @Override public ResultVO exists(@PathVariable Serializable id) { return success(ObjectUtil.isEmpty(iService.getById(id))); } /** * 通过对象唯一键进行修改 * * @param p 参数对象 * @return {@link ResultVO}<{@link Boolean}> */ @ApiOperation("通过对象唯一键进行修改,返回布尔值") @ApiOperationSupport(author = "dingwen") @PutMapping("/default") @Override public ResultVO modify(@RequestBody P p) { return genResult(iService.updateById(p)); } /** * 保存 * * @param p 数据对象 * @return {@link ResultVO} */ @ApiOperation("保存一个对象到数据库,返回布尔值") @ApiOperationSupport(author = "dingwen") @PostMapping("/default") @Override public ResultVO create(@RequestBody P p) { return genResult(iService.save(p)); } /** * 批量保存 * * @param ps 对象集 * @return {@link ResultVO} */ @ApiOperation("批量添加,返回布尔值") @ApiOperationSupport(author = "dingwen") @PostMapping("/default/batch") @Override public ResultVO create(@RequestBody List ps) { return genResult(iService.saveBatch(ps)); } /** * 删除单条记录 * * @param id 唯一键 * @return {@link ResultVO} */ @ApiOperation("删除单条记录,返回布尔值") @ApiOperationSupport(author = "dingwen") @DeleteMapping("/default/{id}") @Override public ResultVO remove(@PathVariable Serializable id) { return genResult(iService.removeById(id)); } /** * 根据唯一键集批量删除 * * @param ids 唯一键集 * @return {@link ResultVO}<{@link Boolean}> */ @ApiOperation("批量删除记录,返回布尔值") @ApiOperationSupport(author = "dingwen") @ApiImplicitParam(name = "ids", value = "唯一键集", dataTypeClass = List.class) @DeleteMapping("/default/batch/{ids}") @Override public ResultVO remove(@PathVariable List ids) { return genResult(iService.removeByIds(ids)); } /** * 保存或更新 * * @param p p * @return {@link ResultVO}<{@link Boolean}> */ @ApiOperation("批量删除记录,返回布尔值") @ApiOperationSupport(author = "dingwen") @PostMapping("/default/sa-mos") @Override public ResultVO saveOrUpdate(P p) { return genResult(iService.saveOrUpdate(p)); } } ``` #### 数据权限 > 要求: 足够灵活,足够高效,足够优雅 TODO #### 完整`SQL`日志及耗时日志 > 基于拦截器实现,核心类: `SQL`拼接拦截器`MybatisPlusSqlLogInterceptor`,`SQL`耗时拦截器`MybatisSqlStatementInterceptor` > 添加以下配置启动: ```yaml # mybatisplus 场景启动器 mybatisplus: # 是否开启多租户插件 tenant: false # 是否开启SQL拦截器日志 sqlLog: true # sql耗时统计(毫秒) 低档 consumeLow: 999 # sql耗时统计(毫秒) 中档 consumeMiddle: 5000 # sql耗时统计(毫秒) 高档 consumeHeight: 10000 ``` #### `druid`连接加密 > 使用`DruidEncryptUtils`生产密码,公钥,私钥,再增加对应配置即可 > 多数据源示例配置 ```yaml spring: datasource: dynamic: # 性能分析插件(有性能损耗 不建议生产环境使用) p6spy: false primary: master datasource: master: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/treasure?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT username: root password: ENC([TODO 密码]) driver-class-name: com.mysql.cj.jdbc.Driver public-key: [TODO publicKey] slave: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/treasure_slave?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT username: root password: ENC([TODO 密码]) driver-class-name: com.mysql.cj.jdbc.Driver # 公钥 public-key: [TODO publicKey] druid: filter: config: enabled: true # 加密公钥 connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.druid.publicKey}; ``` #### 通用字符串字段长度校验组件`TableFieldLengthValidHelper` > 对于一些不为空的亦或是规则的校验`Valid API `已经很方便了,但是对于字符串类型长度的校验,若数据表发生了改变需要同步维护实体中的字段校验信息。通用字符串字段长度校验组件就是为了解决这一问题,通过查询数据表的方式来进行字符串的长度校验,提供丰富的日志,确保不会出现因为字段过长倒是的数据库操作错误 #### 同一数据唯一性校验组件 > 当你需要保证表中某一字段唯一而又不想依赖数据库的唯一性约束,在数据提交的时候就完成校验。直接过滤掉这一类错误数据的数据库访问时实用此组件 ### 字典启动器 #### 核心功能 + 通用字典,字典值实现 + 提供丰富的字典API + 支持多层级的,分模块的字典值 + 分布式缓存支持 + Redis缓存与本地缓存配合使用专注提升效率 + Spring Retry整合翻译应用 + 便捷查询字典工具 #### 使用 ##### 引入依赖 ```xml com.dingwen dic-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableDic`注解以开启字典场景功能 ```java @EnableDic ``` #### 默认API #### 快速字典工具类`DictHelper` > Redis + CaffeineCache 专注提升效率 ```java package com.dingwen.treasure.dic.utils; import cn.hutool.extra.spring.SpringUtil; import com.dingwen.treasure.dic.service.impl.DictDataServiceImpl; import java.util.Objects; import java.util.Optional; /** * 字典工具类 * * @author dingwen * @since 2023/8/12 17:35 */ public class DictHelper { /** * 获取字典值翻译 * * @param dictType 字典类型 * @param dictValue 字典值 * @return 翻译 */ public static Optional getDictLabel(String dictType, String dictValue) { DictDataServiceImpl dictDataService = SpringUtil.getBean(DictDataServiceImpl.class); if (Objects.isNull(dictDataService)) { return Optional.empty(); } return Optional.ofNullable(dictDataService.translate(dictType, dictValue)); } /** * 获取字典值 * * @param dictType 字典类型 * @param dictLabel 字典标签 * @return 翻译 */ public static Optional getDictValue(String dictType, String dictLabel) { DictDataServiceImpl dictDataService = SpringUtil.getBean(DictDataServiceImpl.class); if (Objects.isNull(dictDataService)) { return Optional.empty(); } return Optional.ofNullable(dictDataService.revertTranslate(dictType, dictLabel)); } } ``` #### 核心数据模型 #### 分布式缓存实现逻辑图 ### 翻译启动器 > 基于Jackson反序列化进行的字典翻译组件 #### 核心功能 + 基于注解的配置实现翻译 + 通用表字段的翻译 + 本地缓存与数据库查询并存提升性能 + 提供顶层翻译接口方便拓展`ITranslateService` #### 使用 ##### 引入依赖 ```xml com.dingwen translate-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableTranslate`注解以开启通用翻译场景功能 ```java @EnableTranslate ``` ##### 标注注解 ```java // 默认翻译实现,使用自定义名称新字段 @ApiModelProperty(value = "name") @Translate( service = "defaultTranslateServiceImpl", // 翻译实现接口 mapper = "id", // 翻译依据值 key = "file_name", // 翻译值对应表字段 keyExtend = "tre_c_file", // 翻译对应表名称 param = "file_id" // 翻译依据值对应表字段名称 ) private String name; ``` ```java // 自定义翻译实现,例: 字典翻译实现,使用已有字段名称 @Translate( service = "dictTranslateServiceImpl",// 翻译实现接口 key = "d_field_type", // 翻译值对应表字段 mapper = "dictLabel" // 翻译依据值 ) @ApiModelProperty(value = "dictLabel") private String dictLabel; ``` #### 翻译接口`ITranslateService` ```java package com.dingwen.treasure.translate.core.service; /** * 翻译接口 * * @author dingwen * @since 2023/6/9 15:39 */ public interface ITranslateService { /** * 翻译 * * @param key 关键 * @param keyExtend 关键扩展 * @param param 参数 * @param paramExtend 参数扩展 * @return {@link T} */ T translate(P1 key, P2 keyExtend, P3 param, P4 paramExtend); } ``` #### 注解 ```java package com.dingwen.treasure.translate.annotation; import com.dingwen.treasure.translate.core.handler.TranslationHandler; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.lang.annotation.*; /** * 通用翻译注解 * * @author dingwen * @date 2023/06/09 */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) @Documented @JacksonAnnotationsInside @JsonSerialize(using = TranslationHandler.class) public @interface Translate { /** * 执行翻译的服务组件Bean名称 */ String service() default "defaultTranslateServiceImpl"; /** * 映射字段 * 默认取当前字段的值 如果设置了 {@link Translate#mapper()} 则取映射字段的值 */ String mapper() default ""; /** * key */ String key() default ""; /** * key扩展 */ String keyExtend() default ""; /** * 参数 */ String param() default ""; /** * 参数扩展 */ String paramExtend() default ""; } ``` ### Quartz定时任务启动器 > 基于数据库悲观锁实现分布式场景下的定时任务不漏跑,不重复执行问题 #### 核心功能 + 支持分布式调度 + 基于监听异步方式实现的日追踪 + 二次封装丰富的API + 后续提供界面操作 #### 使用 ##### 引入依赖 ```xml com.dingwen quartz-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableQuartz`注解以开启`quartz`定时任务场景功能 ```java @EnableQuartz ``` ##### 继承`AbstractJob`实现自定义任务 ```java /** * TestJob * Quartz禁止并发执行 DisallowConcurrentExecution * @author dingwen * @date 2022/5/11 */ @Slf4j @DisallowConcurrentExecution public class TestJob extends AbstractJob { /** * 执行 */ @Override protected void exactExecution(){ try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { throw new RuntimeException(e); } log.info("TestJob execute ..."); } } ``` ##### 配置定时任务 #### 默认API + 获取所有执行中的任务列表 + 获取定时任务信息列表 + 添加一个定时任务 + 修改运行中的任务信息 + 立即执行 + 分页查询任务执行日志 + 修改定时任务状态 + 删除定时任务 #### 核心数据模型 #### 待办 + 加缓存减少数据库查询次数 ### 系统配置启动器 #### 核心功能 + 通用系统配置实现 + 提供丰富的配置API + 分布式缓存支持 + Redis缓存与本地缓存配合使用专注提升效率 + 便捷查询配置工具 #### 使用 ##### 引入依赖 ```xml com.dingwen config-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableConfig`注解以开启系统通用配置场景功能 ```java @SpringBootApplication(scanBasePackages = "com.dingwen") ``` #### 默认API #### 快速系统配置工具类`ConfigHelper` > Redis + CaffeineCache 专注提升效率 ```java package com.dingwen.treasure.config.utils; import cn.hutool.extra.spring.SpringUtil; import com.dingwen.treasure.config.entity.Config; import com.dingwen.treasure.config.service.impl.ConfigServiceImpl; import java.util.Objects; import java.util.Optional; /** * 系统配置工具 * * @author dingwen * @since 2023/8/12 17:35 */ public class ConfigHelper { /** * 获取配置值 * * @param configKey 配置关键 * @return {@link String} */ public static Optional getVal(String configKey) { Optional configOp = getConfig(configKey); return configOp.map(Config::getConfigVal); } /** * 获取配置 * * @param configKey 配置关键 * @return {@link Config} */ public static Optional getConfig(String configKey) { ConfigServiceImpl configService = SpringUtil.getBean(ConfigServiceImpl.class); if (Objects.isNull(configService)) { return Optional.empty(); } return Optional.ofNullable(configService.getOneByConfigKey(configKey)); } } ``` #### 核心数据模型 #### 分布式缓存实现逻辑图 ### ### Excel启动器 > 基于阿里开源`EasyExcel`进行二次封装,开箱即用 #### 核心功能 + 自定义转换器封装 + 自定义数据校验封装 + 导入、导出异常处理、事务处理 + 多行表头导入 + 大数据量分批次导入 + 简单导出以及模型映射导出 + 模板填充、组合模板填充 + 文件导出下载、文件上传导入 + 导出行高和列宽设置 + 导出图片内容 + Base64 + 字节数组 + 流 + 导出动态表头 + 合并单元格 + 导出超链接、批注、公式 + 表字段翻译 + 字典翻译 + 枚举翻译 ### 文件启动器 > 一套包含前后端的一条龙的通用的文件场景,业务数据基于`MybatisPlus`存储,文件数据可存储与系统本地或任何一直OSS存储 #### 核心功能 + 存储方式 + 系统存储 + MiniIO + 阿里OSS + 任何一种OSS + 业务数据API + 进度条可视化 + 断点续传 + 分片上传 + 图片压缩 + 图片水印、文件水印 + 图片缩略图 + 文件下载,文件压缩下载 + 文件预览,图片预览 ## 核心服务 ### treasure-business > 业务场景模拟,最佳实践 + RabbitMQ保证顺序、可靠投递、可靠消费`MessageController` + 执行次数、耗时优化工具`MarketingController` + JavaScript动态规则校验`JavaScriptController` + 基于Redis实现接口限流`RateLimiterController` + 多线程异步多任务进度条`ProgressBarTaskController` + 行政区划截取替代优化实现`AreaUtilController` + 自定义线程池、异步任务`AsyncController` + 批量插入方案优化对比`BatchSaveController` + `CompletableFuture`案例`CompletableFutureController` + 后台跨域处理方式一`CorsController` + Validate自定义注解进行个性化规则校验 `CustomValidateController` + 自定义注解实现数据脱敏`DesensitizationController` + jackson反序列化自定义对象`DeserializerController` + 自定义注解动态查询数据库进行字典翻译`DictionaryController` + 自定义注解实现通用缓存逻辑 `EasyCacheController` + 枚举类型序列化和反序列化 `EnumConvertController` + 全局异常处理 `ExceptionController` + 国际化 `LocaleController` + 设计模式-状态模式案例(活动营销状态流转)`MarketingController` + Mybatis-PLus 案例 `MybatisPlusController` + 自定义注解实现操作日志记录 `OperationLogRecordController` + 设计模式-观察者模式案例-创建订单`OrderController` + 异步短信,请求削峰 `OweFeeController` + 设计模式-简单工厂+模版方法+策略模式+函数式接口`PayController` + 自定义注解实现防止重复提交`ReSubmitController` + Redis`setnx`版分布式锁案例 + `ResponseBodyAdvice`实现统一返回 `ResponseBodyAdviceController` + 开放接口对接-短信 `SmsController` + `feign`调用案例`TaskFeignController` + 设计模式-责任链-任务生成案例`TaskGenerateController` + 后端枚举类型统一翻译 `EnumController` + 自定义注解实现加解密、脱敏 `SensitiveController` + 微信公众号定制消息推送`WechatPubController` #### RabbitMQ全链路顺序、可靠消费,100%不丢失 > API入口:`MessageController` + 生产者进行可靠性消息投递 + 消费者手动确认 + 消息落库(状态管理、消费顺序控制) + 定时任务补偿 ##### 流程图  #### 优化工具类 > 参考Spring StopWatch 的拓展优化,精确计算执行耗时,执行次数,方便进行优化 > `OptimizeUtilController` ```java /** * OptimizeUtilController: 优化工具测试 * @author dingwen * @since 2022/8/28 */ @Api(tags = "优化工具API") @RestController @Slf4j @RequestMapping("optimize") @RequiredArgsConstructor public class OptimizeUtilController { @ApiOperation(value = "API使用测试") @GetMapping public void test() { optimizeApi(); } @SneakyThrows(Throwable.class) private void optimizeApi() { OptimizeUtil.start("任务1"); TimeUnit.SECONDS.sleep(1); OptimizeUtil.stop("任务1"); for (int i = 0; i < 100; i++) { OptimizeUtil.start("任务2"); for (int j = 0; j < 1000; j++) { OptimizeUtil.start("任务2-1"); OptimizeUtil.stop("任务2-1"); } OptimizeUtil.stop("任务2"); } OptimizeUtil.print("任务2"); OptimizeUtil.print(); } } ```  #### 复杂规则校验 > Redis + JVM 双缓存 ```js // 测试js function add(op1, op2) { return op1 + op2 } add(a, b) ``` ##### 整体思路、功能点  > 通过Java调用JavaScript进行规则校验,实现复杂且灵活可配置的规则校验功能`JavaScriptController` + 服务启动即进行规则缓存、脚本预编译 + 多线程进行规则校验 + 异步、实时返回结果 + 灵活配置的特定业务特定规则 ##### 表结构 ###### 业务规则校验配置表(business_rule) | | | | | | ---------------- | -------- | ---- | --------------------------------------------------- | | 名称 | 类型 | 长度 | 备注 | | rule_id | bigint | | 主键自增雪花id | | rule_name | varchar | 100 | 校验规则名称 | | rule_description | varchar | 200 | 规则描述 | | rule_state | smallint | | 规则状态:0禁用 1启用 | | rule_content | varchar | 255 | 规则内容(JavaScript) | | field_name | varchar | 255 | 校验字段名称(所需要多个字段逗号分隔) | | rule_code | varchar | 100 | 规则Code(保留字段) | | rule_type | smallint | | 规则类型:0必填 1长度 2必填+长度 3敏感词 4正则 | | business_id | bigint | | 业务Id | | create_time | datetime | | 创建时间(由MybatisPlus自动填充) | | update_time | datetime | | 修改时间(由MybatisPlus自动填充) | | deleted | smallint | | 逻辑删除标识,1:存在,2:已删除 | | version | smallint | | 版本号(乐观锁) | | create_by | varchar | 100 | 创建者(也可基于Security、MybatisPlus实现自动填充) | | update_by | varchar | 100 | 更新者(也可基于Security、MybatisPlus实现自动填充) | | remark | varchar | 255 | 备注(保留字段) | #### Redis限流 > 基于`setnx`,通过自定义注解+脚本实现限流(参考若依实现) + 指定key + 凭借key + 基于方法 + 基于IP + 指定时间、次数 ```java /** * redis限流实现API * * @author dingwen * @since 2022/11/17 */ @Api(tags = "redis限流实现API") @RestController @RequestMapping("redis") public class RateLimiterController { @ApiOperation("redis限流测试") @RateLimiter(time = 1, count = 2) @GetMapping("/rate") public Result rateLimiterSimpleTest() { return ResultGenerator.genSuccessResult(); } } ``` #### 多线程任务进度条实现 > 基于`CompletableFuture`和`redis`的多线程任务进度条实现,后端异步进行任务,前端轮询调用进度条进度查询 > + `redis` > + `setnx` 检查任务key是否存在,任务是否在进行中 > + `hash` 存储任务进度信息 > + 指定哈希键值的`increment` > + `CompletableFuture` > + `whenComplete` 子任务完成时更新任务进度 > + `exceptionally` 发生异常时更新任务进度 > + `AbstractProgressBarTask` 抽象任务进度条组件,囊括进度条功能,子类继承实现业务逻辑即可 ##### API调用 ```java /** * 进度条任务API * * @author dingwen * @since 2022/12/07 */ @Api(tags = "进度条任务API") @RestController @RequestMapping("bar") public class ProgressBarTaskController { @Resource(name = "testProgressBarTask") private TestProgressBarTask testProgressBarTask; @ApiOperation(value = "提交进度条任务") @ApiImplicitParams({ @ApiImplicitParam(name = "taskId", value = "任务id"), @ApiImplicitParam(name = "taskType", value = "任务类型") }) @PutMapping() public Result submit(@RequestParam("taskId") String taskId, @RequestParam("taskType") String taskType) { TaskType taskTypeByCode = EnumUtil.getEnumByCode(TaskType.class, taskType); return ResultGenerator.genSuccessResult( testProgressBarTask.execute( taskId, taskTypeByCode, 100, 100 ) ); } @ApiOperation(value = "查询任务进度") @ApiImplicitParams({ @ApiImplicitParam(name = "taskId", value = "任务id"), @ApiImplicitParam(name = "taskType", value = "任务类型") }) @GetMapping("/{taskId}") public Result queryProgress(@PathVariable("taskId") String taskId, @RequestParam("taskType") String taskType) { TaskType taskTypeByCode = EnumUtil.getEnumByCode(TaskType.class, taskType); return ResultGenerator.genSuccessResult(testProgressBarTask.queryProcess(taskId, taskTypeByCode)); } } ``` ##### 功能概览 #### 自定义注解实现加解密、脱敏 `SensitiveController` + 枚举类`SensitiveEnum` + 实体 `SensitiveEntity` + 自定义注解 + SensitiveDecode 解密 + SensitiveEncode 加密 + SensitiveField 字段标识 + SensitiveInfoUtil 加解密、脱敏工具类 + AesEncryptUtil AES工具类 ##### 功能概览 #### 阿里云短信对接 `SmsController` ##### 功能概览 #### 微信公众号定制消息推送`WechatPubController` > 开放平台对接(基于Spring提供定时任务实现): > + 天行数据 > + 百度地图 > + 微信公众号平台 #### common-influxdb > 时序数据库案例 > + [官网](https://docs.influxdata.com/) > + 接口 `IotDataService` ### treasure-slow-sql #### 深分页查询优化 ```sql SELECT * FROM sys_user WHERE id > #{offset} LIMIT 0,#{pageSize}; ``` ```java /** * 优化Api * @author dingwen * @since 2022/8/5 */ @Api(tags = "优化API") @RestController @Slf4j @RequestMapping("optimize") @Validated public class OptimizeController { @Resource private SysUserService userService; /** * 适用于自增id * 数据总条数:4301000 * 自带查询分页性能: * * 第 1 页 10 条关闭count查询优化耗时:4秒 * 第 100,00 页 100 条关闭count查询优化耗时:5秒 * 第 100,000 页 100 条关闭count查询优化耗时:5秒 * * 第 1 页 10 条打开count查询优化耗时:4秒 * 第 100,00 页 100 条打开count查询优化耗时:4秒 * 第 100,000 页 100 条打开count查询优化耗时:4秒 * * * 优化后性能: * * 第 1 页 10 条耗时:30~80毫秒 * 第 100,00 页 100 条耗时:30~80毫秒 * 第 100,000 页 100 条耗时:30~80秒 * * * 再进行统计总记录条数时会遇到瓶颈,正确的处理方式应该是杜绝深分页,不会有用户往下翻到地100页 * 一般选择50页即可 * * @param pageDto 页面dto */ @PostMapping("/deep-page") @ApiOperation("深分页查询优化") public Result> deepPage(@RequestBody PageDto pageDto) { Long current = pageDto.getCurrent(); Long pageSize = pageDto.getPageSize(); Page page = new Page<>(current, pageSize); // 可以选择关闭count查询优化,解决一对多时分页查询总计记录条数不正确问题 page.setOptimizeCountSql(Boolean.TRUE); //return ResultUtil.genResult(userService.page(page)); return ResultGenerator.genSuccessResult(userService.optimizeSelectPage(current, pageSize)); } } ``` ### treasure-kettle > 企业级的数据中台ETL处理服务,提供数据的定时抽取、转换、加载整体解决方案 #### 下载安装 > 官网:http://community.pentaho.com/projects/data-integration > Carte接口文档:https://help.hitachivantara.com/Documentation #### 组成部分 + spoon > window客户端设计器。windows平台可以直接使用官方程序,linux or mac os 平台建议使用网页版本的spoon + pan:执行转换(命令行方式+操作系统定时任务) + kitchen: (命令行方式+操作系统定时任务) + carte: kettle服务,rest接口提供服务(支持主从) #### 资源库 + 文件资源库 + 数据库资源库: 创建数据库,使用`spoon`配置连接后可自动创建表结构,共计46张表  #### 方案思路 > Java集成定时远程调用和远程服务方式调用以及使用`spoon`客户端进行远程调用需要启动`carte`。 > carte.sh指定配置文件启动,单机考虑一主一从。默认用户密码(cluster),可在启动文件中配置 ##### Java集成定时远程调用 > 远程调用执行 > + id: master ip > + port: master port > + dataRepositoryName: 数据库资源库名称 > + user: 集群名称(master中配置) > + password: 集群密码(master中配置) > + transName: 转换文件名称 > + jobName: 作业文件名称 > + path: 路径 ```shell # 转换执行 https://{ip}:{port}/kettle/executeTrans/?rep={dataRepositoryName}&user={userName}&pass={password}&trans={path/transName.ktr} # 作业执行 https://{ip}:{port}/kettle/executeJob/?rep={dataRepositoryName}&user={userName}&pass={password}&job={path/jobName.kjb} # 查看状态监控 https://{ip}:{port}/kettle/status ``` ##### 远程服务方式调用 > 依赖于`carte`服务,可以采用`spoon`客户端触发远程作业,进行定时调用。(无需代码,配置即可实现) ##### spoon 客户端 > windows平台可以直接使用官方程序,linux or mac os 平台建议使用网页版本的spoon ```shell docker search hiromuhota/webspoon docker run -d -p 8080:8080 --name spoon hiromuhota/webspoon ``` #### 其他 ##### Kettle自带监控页面 ##### SpringBoot2.X和Kettle9整合 ```shell # 手动添加如下依赖 mvn install:install-file -DgroupId=组织名称 -DartifactId=坐标 -Dversion=9.3.0.0-428 -Dpackaging=jar -Dfile= jar包名称 ``` ### treasure-task-quartz #### 整体介绍 > 基于`quartz` > 的定时任务实现,API灵活控制,精确日志记录,分布式部署完整的解决方案。注意:当次解决方案在分布式应用场景中时,确保任务不重复执行依赖与`quartz` > 持久化到数据库依赖数据库悲观锁实现。 #### 特点 > + 分布式部署保障不重复执行不漏跑 > + 模版方法:代码可重用性 > + 实时接口调用控制任务执行、停止 > + 接口调用修改任务信息 > + 执行日志记录 #### 表设计 + `QRTZ_BLOB_TRIGGERS` + `QRTZ_CALENDARS` + `QRTZ_CRON_TRIGGERS` + `QRTZ_FIRED_TRIGGERS` + `QRTZ_JOB_DETAILS` + `QRTZ_LOCKS` + `QRTZ_PAUSED_TRIGGER_GRPS` + `QRTZ_SCHEDULER_STATE` + `QRTZ_SIMPLE_TRIGGERS` + `QRTZ_SIMPROP_TRIGGERS` + `QRTZ_TRIGGERS` ##### 定时任务信息表`quartz_info` | 名称 | 类型 | 长度 | 备注 | | ------------------ | -------- | ---- | ---------------------------- | | id | bigint | | 数据库自雪花id | | code | varchar | 255 | 定时任务code标识 | | create_time | datetime | | 创建时间 | | cron_expression | varchar | 255 | cron表达式 | | fail | int | | 失败次数 | | full_class_name | varchar | 255 | 定时任务执行类 全类名,Job类 | | job_data_map | varchar | 255 | jobDataMap json格式 | | job_group_name | varchar | 255 | job组名称 | | job_name | varchar | 255 | job 名称 | | name | varchar | 255 | 定时任务名称 | | state | int | | 是否启用 1-启用 0-禁用 | | success | int | | 成功执行次数 | | trigger_group_name | varchar | 255 | 触发器组名称 | | trigger_name | varchar | 255 | 触发器名称 | | update_time | datetime | | 更新时间 | ##### 定时任务日志表 `quartz_log` | 名称 | 类型 | 长度 | 备注 | | -------------- | -------- | ---- | ------------------------------------ | | id | bigint | | 数据库自雪花id | | quartz_id | bigint | | 任务id关联 | | activate_time | datetime | | 激活时间 | | consumer_time | int | | 任务耗时 | | execute_result | int | | 执行结果:1: 成功0: 失败 | | remark | varchar | 255 | 备注 | ### treasure-manage > 常用后台管理实现 ### treasure-common > 公共模块 > + base: 基础、通用 > + config: 配置 > + core:核心通用组件 > + jpa:jpa场景 > + mybatisplus mybatisplus场景 > + web: web场景 > + knife4j: API文档 > + rabbitmq: RabbitMQ 应用场景 > + redis: Redis 应用场景 ### treasure-admin > 基于`SpringBoot Admin`整合`Spring Security`的监控实现,目前暴露所有端点,权限账户信息通过`nacos`配置指定 ```yaml spring: security: user: name: actuator password: actuator ``` > TODO > + 自定义info、metrics、health、endpoint > + 邮件、钉钉预警 ### treasure-xxl-job-admin > 基于`xxl-job v2.4.0`封装的调度中心 ```yaml xxl: job: accessToken: xxl-job-access-token i18n: zh_CN triggerpool: fast: max: 200 slow: max: 100 logretentiondays: 30 ``` ### `treasure-poi-tl` > poi-tl 官网: https://github.com/Sayi/poi-tl > `word`模板渲染解决方案,拒绝手动维护`xml`文件 ### 钉钉企业微信预警`treasure-dingtalk-ger` > 钉鸽官网: https://github.com/AnswerAIL/dingtalk-spring-boot-starter #### 概览 #### 工具类 `DingerUtils` > 可在全局异常处理处调用`DingerUtils.send(e);` ```java /** * 钉鸽 * * @author dingwen * @since 2023/9/22 14:47 */ public class DingerUtils { public static void send(Exception e) { DingerSender dingerSender = SpringUtil.getBean(DingerSender.class); if (Objects.isNull(dingerSender)) { return; } String msg = "用户:{},userId:{},请求地址:{},入参{},请求体参数:{},发生异常,消息:{},堆栈信息:{}"; String url = ServletUtils.getUrl(); String parameters = ServletUtils.getParameters(); String body = ServletUtil.getBody(ServletUtils.getRequest()); Long userId = SecurityUtils.getUserId(); String username = SecurityUtils.getUsername(); dingerSender.send( MessageSubType.TEXT, DingerRequest.request(StrUtil.format(msg, username,userId,url,parameters,body,e.getMessage(), printStackTraces(e))) ); } /** * 堆栈信息 * * @param e 异常 * @return 异常信息 */ public static String printStackTraces(Exception e) { StackTraceElement[] stackTraces = e.getStackTrace(); StringBuilder builder = new StringBuilder(); builder.append(e.getClass().getName()) .append(": ") .append(e.getLocalizedMessage()) .append("\n"); for (StackTraceElement stackTrace : stackTraces) { String lineMsg = " at "; lineMsg = lineMsg + stackTrace.getClassName() + "(" + stackTrace.getFileName() + ":" + stackTrace.getLineNumber() + ")\n"; builder.append(lineMsg); } return builder.substring(0,300); } } ``` #### 服务启停监听,dinger通知 ```Java package com.dingwen.treasure.gtl.listener; import com.dingwen.treasure.gtl.util.DingerUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; /** * 应用启动事件监听 * * @author dingwen * @since 2023/10/9 18:22 */ @Component @Slf4j public class PreStopListener implements org.springframework.context.ApplicationListener { @Override public void onApplicationEvent(ContextClosedEvent event) { log.info("清廉系统后端服务已停止"); // 可依据环境判断是否执行 DingerUtils.send("清廉系统后端服务已停止"); } } ``` ```java package com.dingwen.treasure.gtl.listener; import com.dingwen.treasure.gtl.util.DingerUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.stereotype.Component; /** * 应用启动事件监听 * * @author dingwen * @since 2023/10/9 18:22 */ @Component @Slf4j public class StartListener implements org.springframework.context.ApplicationListener { @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 可依据环境判断是否执行 DingerUtils.send("清廉系统后端服务已启动"); } } ``` ### 设计模式`treasure-gof` ### 其他临时文档 + business > 优雅代码业务组件实现(MybatisPlus) > 特色业务场景 > + 线程池案例 > + 批量save > + 自定义MVC反序列化方式 > + 缓存业务场景自定义注解:@EasyCache > + 国际化 > + 状态模式+简单工厂模式实现:多阶段灵活控制状态流转 设计模式六大原则: + 单一职责 + 接口隔离(Servlet filter) + 依赖倒置 + 迪米特(最少知道原则) + 里氏替换 (父类抽象方法应保持行为一致,非抽象方法不应去重写) + 开闭原则 + 合成复用 + 状态模式: 对象不同的状态导致不同的行为 + 主要角色: + 抽象状态(接口或抽象类) + 具体状态(抽象状态的子类或实现类) + 环境类 (状态持有着,依据不同的状态出发不同的行为)  > + MybatisPlus:乐观锁、逻辑删除、自动枚举转换、多数据源、Model、自动填充、分页等其他通用API > + 观察者模式:(事件对象、监听)下订单走库存以及日志,以事件驱动 > + 支付案例:简单工厂 + 模版方法 + 策略模式 + 函数式接口 + 责任链模式:为请求创建了一个接收者对象的处理链,,对请求的发送者和接收者进行解耦 > + 基于redis、redisson的分布式锁 > + 防止重复提交自定义注解:@ReSubmit > + (解决数据库和缓存不一致的问题)系统配置:db、redis、自定义缓存(利用redis发布订阅实现三级缓存) > + 基于Mybatis的通用crudController封装已经整合API文档 > + 全局统一返回 > + 全局异常处理 > + 优雅DTO、VO、BO、PO转换 > + feign调用(整合hystrix实现服务降级) > + 系统配置实现(三级分布式缓存)redis 发布订阅实现跨内存刷新 > + AOP 切面实现方法调用前后入参、返回值、耗时等调试日志(全局所有controller)、基于配置灵活开启关闭 > + 消息队列业务模拟(保证顺序消费、保证不重复消费、消息可靠投递、消息可靠消费) > + 操作日志(AOP+Sprint EL 实现同步ElasticSearch)参考美团2021年技术年报实现、基于配置灵活开启关闭 > TODO > + redisson 分布式锁 > + 规范转换Bean mapstruct > + 基于JPA的通用crudController封装 > + 通用crudController封装 mongoDB + gateway > 网关,基于`nacos`的可配置白名单 > TODO > + 统一请求日志 > + 认证 > + 服务间其他信息 + pdf > 基于`freemarker`模版实现的后端生成PDF或在线预览功能 + manage > 后台管理模块 > 特色业务场景 > + 消息队列业务模拟(顺序消费、不重复消费、可靠消费) 死信队列、延时队列 + auth > 认证模块 > TODO > + 权限 > + RBAC > + 参考若依实现 > + 菜单、路由 > TODO > + 文件上传minio + API文档 + knife4j + business-service: 地址:http://127.0.0.1:20908/doc.html + task-service: 地址:http://127.0.0.1:20902/doc.html + 用户名:`test` + 密码:`123` + 监控面板 + spring-boot-admin、spring-boot-starter-security + 地址:http://127.0.0.1:20906/login + 用户名:`actuator` + 密码:`actuator` + 国际化 + 监控 + 定时任务 + API文档 + Aop + ReSubmit(防止重复提交) + EasyCache (缓存) + 通用业务组件 + 简单工厂 + 策略模式 + 模版方法 + Spring 高级 + 观察者模式(监听、事件机制) + 自动注入Map + 监控自定义 预警(钉钉、邮件) + Mybatis Plus + 多数据源 + 逻辑删除 + 乐观锁 + Model + lambda + 通用枚举 + 联合主键`@MppMultiId`、`IMppService`、`MppBaseMapper` + 自动填充新增时间、修改时间 + Mysql + 枚举类型 + Json 类型 + 分布式锁 + 分布式事务 + Sentinel + 链路追踪 + 定时任务 Quartz + 设计模式:模版方法 + Jpa 实现 + 基于数据库悲观锁实现分布式锁,支持多节点部署 + 保证不重复执行 + 保证不漏执行 + Rabbitmq + 可靠消费 + 可靠投递 + 顺序消费 + 全局异常处理 + 权限 > 参考若依实现 + SSO + 系统配置 + 通用日志 + 操作日志 + 系统日志 > 参考美团技术年报、若依。初步实现思路:Aop及Spring EL 表达式实现日志数据组装,通过RabbitMq将数据同步到ElasticSearch + MongoDB + WebFlux + canal + 依赖优化 + 规范转换Bean mapstruct + 全局异常 + 权限(market、ruoyi) + 系统配置 + Redis 实现分布式锁 + 状态模式 + 状态直接可以存在相互依赖关系 + 状态之间可以相互转换,可以反复 + 策略模式 + 多种算法行为选择一个就能满足 + 算法独立 + 自定义MVC反序列化进行Java Bean 数据封装 + 文件存储 + 短信 + spring 批处理 batch + 网关统一日志 + 统一系统配置 > redis & JVM 两级缓存,使用 redis 发布订阅实现,支持分布式 + JDBC 批处理 + DTO、VO、BO 转换 + 定时任务BUG + @EnableAspectJAutoProxy > 在SpringBoot2已经无效,需要通过,`spring.aop.proxy-target-class=false` > 指定为JDK方式实现,默认值为true,即采用CGLIB实现 + TODO + 分布式定时任务框架 xxl_job + 分布式事务 + 分布式锁 + 并发编程 + SQL优化 + 脚本 + 容器化 + ELK + 日志配置 + 启动初始化 + `ApplicationRunner` + `CommandLineRunner` + http://127.0.0.1:20900 网关 + 分布式文件存储 minio + 调试日志(入参、返回值、耗时)es + 后端渲染生产PDF (freemarker) + mybatis 场景整合 (动态标签等常用技巧备忘) + 文件预览 + excel 通用封装 (基于 hutool 、 poi) 参考若依 + sql 优化 + JVM + SQL 窗口函数 + 参数范围校验注解 + 字典 + 高德地图 + 日志配置 + 枚举 + 序列化、反序列化 + excel + docker 部署 + 若依数据权限 > 存储过程没有返回值 procedure call > 必须有返回值 function 直接调用 + token 刷新 + 全局拦截器 + nacos 刷新 + 慢sql监控 + 用户在线统计 + 站内信息 + 字典 aop + 观察者模式 + 状态模式 + 享元模式 + 单例模式 + 构建者模式 + 原型模式 + mongodb 索引优化 + lambda return + 常量定义 + feign 调用 localDatetime反序列化问题 + 工厂方法模式 应用场景 + 抽象工厂模式 应用场景 + JUC 中断三种方式 + 工作流 + validator 分组校验 + 字典 + 根据字典配置动态生成枚举类型 + 字典动态翻译 + 文件视频格式等问题预览 + 依赖模块优化 + 消息可靠性 + juc + spring cloud + kkfile + ffmepg + 可靠消费 不丢失 重复 实战 + 缓存双写实战 + 代码生成 + 动态数据源 druid 监控 + 数据权限 租户 若依 + xss + author2 + mapstruct + HashMap + ConcurrentHashMap + druid 数据源 + 跨域(Cross Origin Resource Sharing) + 发生在前端 + 浏览器的同源策略:协议、主机、端口 + 三种后端解决方式 + @CrossOrigin + Cross Filter + WebMvcConfigure + UML:Unified Modeling Language + 类图(两个矩形:顶类名称,上属性,下方法) + 属性: 权限 名称: 类型 + 方法: 权限 方法名称(参数列表): 返回值类型 + 权限: + ` ` default + `-` private + `+` public + `#` protected + 关系: + 关联关系: + 引用:实线实心三角形箭头指向被引用的一方 + 双向关联:实线 + 自关联: 实线实心三角形箭头指向自己 + 聚合关系:整体和部分的关系,强烈的聚合关系,部分可以离开整体 + 实线空心菱形指向整体 + 组合关系: 整体和部分的关系,更强烈的聚合关系,部分不可以离开整体(头、嘴) + 实线实心菱形指向整体 + 依赖关系: 耦合度最弱的一种关联关系(调用,引用) + 虚线虚线箭头指向被依赖的类 + 继承关系(泛化关系) + 实线空心三角形指向父类 + 实现关系 + 虚线空心三角形箭头指向接口 + nullSafeEquals + 枚举优化 + redis 队列、map + 大文件上传、切片、多线程、断点续传 + Spring cache + @Cacheable + InitializingBean + xss + @CacheEvict + @EventListener + 微服务 过滤器认证 market + treasure 开放平台 + 交换平台 + webservice + websocket + pig4 + nacos内置 + 数据权限 + 代码生成 + js 规则引擎 + webflux + security 方式认证授权 + 开放平台 4种授权 三方登录 + xss + @inner + webservice + websocket + @PositiveOrZero + base controller + redis 限流 + 通用返回优化 + 自增主键,分页优化方案 git config --local http.postBuffer 157286400 + ER + 详细设计 + 技术文档 + @Configuration(proxyBeanMethods = false) + true: 走代理,配置类中各个方法相互依赖 + false: 不走代理,配置类中各个方法不依赖,可提高性能 + pom优化 + kettle + 缓存 + 上下文待优化 + 登录待优化 + TokenService待优化 + StringJoiner + 数据脱敏考虑隐私权限、用户权限 + 数据权限 + 开放平台 + 集成三方登录 + 短信、天气 + 本地缓存 + 如何停止一个线程 + rpc + docker 部署 + 脚本 启动等 + influxdb 时序数据库 + navicat 模型 + java -jar -Dfile.encoding=utf-8 -DTREASURE_NACOS_NAMESPACE=treasure treasure-business.jar + docker build -t treasure-business:v1.0 . + docker run -p 20903:20903 --name treasure-business -d treasure-business:v1.0 + 开放平台 oauth + 第三放登录 + 高德地图导入行政区划 + 若依数据权限 【PLus】 + 验证码登录 + 枚举 + 若依 @Anonymous + 异常国际化处理 + git 指定某次提交合并到指定分支【先切换到目标分支 在执行git cherry-pick 8888189f】 + `XXXController` > 功能动词 + `obtainXXX`获得 + `discardXXX`删除 + `XXXManager`、`IXXXManager`、`IXXXManagerImpl`【Optional】 > 功能动词+For使用场景 + `obtainDeptTree`【获得部门树】 + `XXXService`、`IXXXService`、`IXXXServiceImpl` 【Optional】 > 功能动词+By条件+For使用场景 + `queryDeptById`【通过部门id查询部门信息】 + `queryDeptsForMini` 【小程序端查询部门列表】 + `queryDeptsForWeb` 【Web端查询部门列表】 + `queryDeptPage` 【部门列表分页查询】 + `modifyDept`【修改部门信息】 + `createDept`【创建一个部门】 + `createDepts`【创建多个部门】 + `removeDeptById`【通过部门id删除部门】 + `removeDeptByIds`【通过部门ids删除部门】 + `XXXMapper` + `insertDept` 【插入一个部门】 + `insertDepts` 【批量插入部门】 + `updateDeptById` 【通过部门id修改部门信息】 + `deleteDeptById`【通过部门id删除部门】 + `deleteDeptByIds`【通过部门ids删除部门】 + `selectDeptsByRLikeName` 【通过部门名称右模糊查询部门列表】 + `selectDeptPage` 【分页查询部门列表】 + `XXXProcessor` 处理 + `XXXHolder` 持有 + `XXXFactory` 工厂 + `XXXProvider` 提供者 + `XXXRegistor` 注册 + `XXXEngine` 核心处理逻辑 + `XXXTask` 任务 + `XXXContext` 上下文 + `XXXHandler`、`XXXCallback`、`XXXTrigger`、`XXXListener` + `XXXAware` 感知 + `XXXMetric`指标 + `XXXPool`池 + `XXXChain` 链 + `XXXFilter` 过滤 + `XXXInterceptor` 拦截器 + `XXXEvaluator` 判断条件是否成立 + `XXXStrategy` 策略 + `XXXAdapter`适配器 + `XXXEvent` 事件 + `XXXBuilder` 构建 + `XXXTemplate` 模版 + `XXXProxy` 代理 + `XXXConverter` 转换 + `XXXRessolver`解析 + `XXXParser`解析器 + `XXXUtils` 工具类 + `XXXHelper` 帮助类 + `XXXConstant` 常量 + `XXXGenerator` 生成 ## 场景启动器使用聚合`scene` ### Maven环境隔离 #### 引入打包插件 ```xml org.apache.maven.plugins maven-resources-plugin ${project.build.sourceEncoding} @ false ${maven-resources-plugin.version} ``` #### `resource` ```xml src/main/resources true **/*.xlsx **/*.xml **/*.docx src/main/resources false **/*.xlsx **/*.xml **/*.docx ``` #### 配置使用 ### 10.2 `disruptor`的 ` log4j2`高性能异步日志 > 整体性能有显著提升,适用C端的大量日志场景 #### 添加依赖 ```xml com.lmax disruptor ${disruptor.version} org.springframework.boot spring-boot-starter-log4j2 ``` #### 排除相关`logback`冲突包 ```xml org.springframework.boot spring-boot-starter-web spring-boot-starter-logging org.springframework.boot ``` > 整体进行排除 ```xml org.springframework.boot spring-boot-starter-logging * * ``` #### 配置 + 使用 ## 后续计划 + 通用业务场景拆分封装 + Cloud系、Dubbo系 + 监控、告警 + 前端部分 + Vue3 + Uniapp + Flutter + ELK + 容器化 ## 待完成任务 + 数据归档组件 + 数据变更记录组件 + 缓存预热进一步优化 + httpclient5 优化 + 京东async封装 + fc-async + 微服务 cloud + eureka + ribbon... + 自定义ribbon负载均衡 + `alibaba`微服务系 + 设计模式系 + file场景改造优化 + 分片上传,断点续传,秒传 + 基于nacaos动态配置策略 ## 联系我 ## 文档版本 > 2023-12-05 15:26:49
ps) { return genResult(iService.saveBatch(ps)); } /** * 删除单条记录 * * @param id 唯一键 * @return {@link ResultVO} */ @ApiOperation("删除单条记录,返回布尔值") @ApiOperationSupport(author = "dingwen") @DeleteMapping("/default/{id}") @Override public ResultVO remove(@PathVariable Serializable id) { return genResult(iService.removeById(id)); } /** * 根据唯一键集批量删除 * * @param ids 唯一键集 * @return {@link ResultVO}<{@link Boolean}> */ @ApiOperation("批量删除记录,返回布尔值") @ApiOperationSupport(author = "dingwen") @ApiImplicitParam(name = "ids", value = "唯一键集", dataTypeClass = List.class) @DeleteMapping("/default/batch/{ids}") @Override public ResultVO remove(@PathVariable List ids) { return genResult(iService.removeByIds(ids)); } /** * 保存或更新 * * @param p p * @return {@link ResultVO}<{@link Boolean}> */ @ApiOperation("批量删除记录,返回布尔值") @ApiOperationSupport(author = "dingwen") @PostMapping("/default/sa-mos") @Override public ResultVO saveOrUpdate(P p) { return genResult(iService.saveOrUpdate(p)); } } ``` #### 数据权限 > 要求: 足够灵活,足够高效,足够优雅 TODO #### 完整`SQL`日志及耗时日志 > 基于拦截器实现,核心类: `SQL`拼接拦截器`MybatisPlusSqlLogInterceptor`,`SQL`耗时拦截器`MybatisSqlStatementInterceptor` > 添加以下配置启动: ```yaml # mybatisplus 场景启动器 mybatisplus: # 是否开启多租户插件 tenant: false # 是否开启SQL拦截器日志 sqlLog: true # sql耗时统计(毫秒) 低档 consumeLow: 999 # sql耗时统计(毫秒) 中档 consumeMiddle: 5000 # sql耗时统计(毫秒) 高档 consumeHeight: 10000 ``` #### `druid`连接加密 > 使用`DruidEncryptUtils`生产密码,公钥,私钥,再增加对应配置即可 > 多数据源示例配置 ```yaml spring: datasource: dynamic: # 性能分析插件(有性能损耗 不建议生产环境使用) p6spy: false primary: master datasource: master: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/treasure?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT username: root password: ENC([TODO 密码]) driver-class-name: com.mysql.cj.jdbc.Driver public-key: [TODO publicKey] slave: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/treasure_slave?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT username: root password: ENC([TODO 密码]) driver-class-name: com.mysql.cj.jdbc.Driver # 公钥 public-key: [TODO publicKey] druid: filter: config: enabled: true # 加密公钥 connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.druid.publicKey}; ``` #### 通用字符串字段长度校验组件`TableFieldLengthValidHelper` > 对于一些不为空的亦或是规则的校验`Valid API `已经很方便了,但是对于字符串类型长度的校验,若数据表发生了改变需要同步维护实体中的字段校验信息。通用字符串字段长度校验组件就是为了解决这一问题,通过查询数据表的方式来进行字符串的长度校验,提供丰富的日志,确保不会出现因为字段过长倒是的数据库操作错误 #### 同一数据唯一性校验组件 > 当你需要保证表中某一字段唯一而又不想依赖数据库的唯一性约束,在数据提交的时候就完成校验。直接过滤掉这一类错误数据的数据库访问时实用此组件 ### 字典启动器 #### 核心功能 + 通用字典,字典值实现 + 提供丰富的字典API + 支持多层级的,分模块的字典值 + 分布式缓存支持 + Redis缓存与本地缓存配合使用专注提升效率 + Spring Retry整合翻译应用 + 便捷查询字典工具 #### 使用 ##### 引入依赖 ```xml com.dingwen dic-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableDic`注解以开启字典场景功能 ```java @EnableDic ``` #### 默认API #### 快速字典工具类`DictHelper` > Redis + CaffeineCache 专注提升效率 ```java package com.dingwen.treasure.dic.utils; import cn.hutool.extra.spring.SpringUtil; import com.dingwen.treasure.dic.service.impl.DictDataServiceImpl; import java.util.Objects; import java.util.Optional; /** * 字典工具类 * * @author dingwen * @since 2023/8/12 17:35 */ public class DictHelper { /** * 获取字典值翻译 * * @param dictType 字典类型 * @param dictValue 字典值 * @return 翻译 */ public static Optional getDictLabel(String dictType, String dictValue) { DictDataServiceImpl dictDataService = SpringUtil.getBean(DictDataServiceImpl.class); if (Objects.isNull(dictDataService)) { return Optional.empty(); } return Optional.ofNullable(dictDataService.translate(dictType, dictValue)); } /** * 获取字典值 * * @param dictType 字典类型 * @param dictLabel 字典标签 * @return 翻译 */ public static Optional getDictValue(String dictType, String dictLabel) { DictDataServiceImpl dictDataService = SpringUtil.getBean(DictDataServiceImpl.class); if (Objects.isNull(dictDataService)) { return Optional.empty(); } return Optional.ofNullable(dictDataService.revertTranslate(dictType, dictLabel)); } } ``` #### 核心数据模型 #### 分布式缓存实现逻辑图 ### 翻译启动器 > 基于Jackson反序列化进行的字典翻译组件 #### 核心功能 + 基于注解的配置实现翻译 + 通用表字段的翻译 + 本地缓存与数据库查询并存提升性能 + 提供顶层翻译接口方便拓展`ITranslateService` #### 使用 ##### 引入依赖 ```xml com.dingwen translate-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableTranslate`注解以开启通用翻译场景功能 ```java @EnableTranslate ``` ##### 标注注解 ```java // 默认翻译实现,使用自定义名称新字段 @ApiModelProperty(value = "name") @Translate( service = "defaultTranslateServiceImpl", // 翻译实现接口 mapper = "id", // 翻译依据值 key = "file_name", // 翻译值对应表字段 keyExtend = "tre_c_file", // 翻译对应表名称 param = "file_id" // 翻译依据值对应表字段名称 ) private String name; ``` ```java // 自定义翻译实现,例: 字典翻译实现,使用已有字段名称 @Translate( service = "dictTranslateServiceImpl",// 翻译实现接口 key = "d_field_type", // 翻译值对应表字段 mapper = "dictLabel" // 翻译依据值 ) @ApiModelProperty(value = "dictLabel") private String dictLabel; ``` #### 翻译接口`ITranslateService` ```java package com.dingwen.treasure.translate.core.service; /** * 翻译接口 * * @author dingwen * @since 2023/6/9 15:39 */ public interface ITranslateService { /** * 翻译 * * @param key 关键 * @param keyExtend 关键扩展 * @param param 参数 * @param paramExtend 参数扩展 * @return {@link T} */ T translate(P1 key, P2 keyExtend, P3 param, P4 paramExtend); } ``` #### 注解 ```java package com.dingwen.treasure.translate.annotation; import com.dingwen.treasure.translate.core.handler.TranslationHandler; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.lang.annotation.*; /** * 通用翻译注解 * * @author dingwen * @date 2023/06/09 */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) @Documented @JacksonAnnotationsInside @JsonSerialize(using = TranslationHandler.class) public @interface Translate { /** * 执行翻译的服务组件Bean名称 */ String service() default "defaultTranslateServiceImpl"; /** * 映射字段 * 默认取当前字段的值 如果设置了 {@link Translate#mapper()} 则取映射字段的值 */ String mapper() default ""; /** * key */ String key() default ""; /** * key扩展 */ String keyExtend() default ""; /** * 参数 */ String param() default ""; /** * 参数扩展 */ String paramExtend() default ""; } ``` ### Quartz定时任务启动器 > 基于数据库悲观锁实现分布式场景下的定时任务不漏跑,不重复执行问题 #### 核心功能 + 支持分布式调度 + 基于监听异步方式实现的日追踪 + 二次封装丰富的API + 后续提供界面操作 #### 使用 ##### 引入依赖 ```xml com.dingwen quartz-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableQuartz`注解以开启`quartz`定时任务场景功能 ```java @EnableQuartz ``` ##### 继承`AbstractJob`实现自定义任务 ```java /** * TestJob * Quartz禁止并发执行 DisallowConcurrentExecution * @author dingwen * @date 2022/5/11 */ @Slf4j @DisallowConcurrentExecution public class TestJob extends AbstractJob { /** * 执行 */ @Override protected void exactExecution(){ try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { throw new RuntimeException(e); } log.info("TestJob execute ..."); } } ``` ##### 配置定时任务 #### 默认API + 获取所有执行中的任务列表 + 获取定时任务信息列表 + 添加一个定时任务 + 修改运行中的任务信息 + 立即执行 + 分页查询任务执行日志 + 修改定时任务状态 + 删除定时任务 #### 核心数据模型 #### 待办 + 加缓存减少数据库查询次数 ### 系统配置启动器 #### 核心功能 + 通用系统配置实现 + 提供丰富的配置API + 分布式缓存支持 + Redis缓存与本地缓存配合使用专注提升效率 + 便捷查询配置工具 #### 使用 ##### 引入依赖 ```xml com.dingwen config-spring-boot-starter 1.0.0-SNAPSHOT ``` ##### 启动项配置 > 在启动类上添加`@EnableConfig`注解以开启系统通用配置场景功能 ```java @SpringBootApplication(scanBasePackages = "com.dingwen") ``` #### 默认API #### 快速系统配置工具类`ConfigHelper` > Redis + CaffeineCache 专注提升效率 ```java package com.dingwen.treasure.config.utils; import cn.hutool.extra.spring.SpringUtil; import com.dingwen.treasure.config.entity.Config; import com.dingwen.treasure.config.service.impl.ConfigServiceImpl; import java.util.Objects; import java.util.Optional; /** * 系统配置工具 * * @author dingwen * @since 2023/8/12 17:35 */ public class ConfigHelper { /** * 获取配置值 * * @param configKey 配置关键 * @return {@link String} */ public static Optional getVal(String configKey) { Optional configOp = getConfig(configKey); return configOp.map(Config::getConfigVal); } /** * 获取配置 * * @param configKey 配置关键 * @return {@link Config} */ public static Optional getConfig(String configKey) { ConfigServiceImpl configService = SpringUtil.getBean(ConfigServiceImpl.class); if (Objects.isNull(configService)) { return Optional.empty(); } return Optional.ofNullable(configService.getOneByConfigKey(configKey)); } } ``` #### 核心数据模型 #### 分布式缓存实现逻辑图 ### ### Excel启动器 > 基于阿里开源`EasyExcel`进行二次封装,开箱即用 #### 核心功能 + 自定义转换器封装 + 自定义数据校验封装 + 导入、导出异常处理、事务处理 + 多行表头导入 + 大数据量分批次导入 + 简单导出以及模型映射导出 + 模板填充、组合模板填充 + 文件导出下载、文件上传导入 + 导出行高和列宽设置 + 导出图片内容 + Base64 + 字节数组 + 流 + 导出动态表头 + 合并单元格 + 导出超链接、批注、公式 + 表字段翻译 + 字典翻译 + 枚举翻译 ### 文件启动器 > 一套包含前后端的一条龙的通用的文件场景,业务数据基于`MybatisPlus`存储,文件数据可存储与系统本地或任何一直OSS存储 #### 核心功能 + 存储方式 + 系统存储 + MiniIO + 阿里OSS + 任何一种OSS + 业务数据API + 进度条可视化 + 断点续传 + 分片上传 + 图片压缩 + 图片水印、文件水印 + 图片缩略图 + 文件下载,文件压缩下载 + 文件预览,图片预览 ## 核心服务 ### treasure-business > 业务场景模拟,最佳实践 + RabbitMQ保证顺序、可靠投递、可靠消费`MessageController` + 执行次数、耗时优化工具`MarketingController` + JavaScript动态规则校验`JavaScriptController` + 基于Redis实现接口限流`RateLimiterController` + 多线程异步多任务进度条`ProgressBarTaskController` + 行政区划截取替代优化实现`AreaUtilController` + 自定义线程池、异步任务`AsyncController` + 批量插入方案优化对比`BatchSaveController` + `CompletableFuture`案例`CompletableFutureController` + 后台跨域处理方式一`CorsController` + Validate自定义注解进行个性化规则校验 `CustomValidateController` + 自定义注解实现数据脱敏`DesensitizationController` + jackson反序列化自定义对象`DeserializerController` + 自定义注解动态查询数据库进行字典翻译`DictionaryController` + 自定义注解实现通用缓存逻辑 `EasyCacheController` + 枚举类型序列化和反序列化 `EnumConvertController` + 全局异常处理 `ExceptionController` + 国际化 `LocaleController` + 设计模式-状态模式案例(活动营销状态流转)`MarketingController` + Mybatis-PLus 案例 `MybatisPlusController` + 自定义注解实现操作日志记录 `OperationLogRecordController` + 设计模式-观察者模式案例-创建订单`OrderController` + 异步短信,请求削峰 `OweFeeController` + 设计模式-简单工厂+模版方法+策略模式+函数式接口`PayController` + 自定义注解实现防止重复提交`ReSubmitController` + Redis`setnx`版分布式锁案例 + `ResponseBodyAdvice`实现统一返回 `ResponseBodyAdviceController` + 开放接口对接-短信 `SmsController` + `feign`调用案例`TaskFeignController` + 设计模式-责任链-任务生成案例`TaskGenerateController` + 后端枚举类型统一翻译 `EnumController` + 自定义注解实现加解密、脱敏 `SensitiveController` + 微信公众号定制消息推送`WechatPubController` #### RabbitMQ全链路顺序、可靠消费,100%不丢失 > API入口:`MessageController` + 生产者进行可靠性消息投递 + 消费者手动确认 + 消息落库(状态管理、消费顺序控制) + 定时任务补偿 ##### 流程图  #### 优化工具类 > 参考Spring StopWatch 的拓展优化,精确计算执行耗时,执行次数,方便进行优化 > `OptimizeUtilController` ```java /** * OptimizeUtilController: 优化工具测试 * @author dingwen * @since 2022/8/28 */ @Api(tags = "优化工具API") @RestController @Slf4j @RequestMapping("optimize") @RequiredArgsConstructor public class OptimizeUtilController { @ApiOperation(value = "API使用测试") @GetMapping public void test() { optimizeApi(); } @SneakyThrows(Throwable.class) private void optimizeApi() { OptimizeUtil.start("任务1"); TimeUnit.SECONDS.sleep(1); OptimizeUtil.stop("任务1"); for (int i = 0; i < 100; i++) { OptimizeUtil.start("任务2"); for (int j = 0; j < 1000; j++) { OptimizeUtil.start("任务2-1"); OptimizeUtil.stop("任务2-1"); } OptimizeUtil.stop("任务2"); } OptimizeUtil.print("任务2"); OptimizeUtil.print(); } } ```  #### 复杂规则校验 > Redis + JVM 双缓存 ```js // 测试js function add(op1, op2) { return op1 + op2 } add(a, b) ``` ##### 整体思路、功能点  > 通过Java调用JavaScript进行规则校验,实现复杂且灵活可配置的规则校验功能`JavaScriptController` + 服务启动即进行规则缓存、脚本预编译 + 多线程进行规则校验 + 异步、实时返回结果 + 灵活配置的特定业务特定规则 ##### 表结构 ###### 业务规则校验配置表(business_rule) | | | | | | ---------------- | -------- | ---- | --------------------------------------------------- | | 名称 | 类型 | 长度 | 备注 | | rule_id | bigint | | 主键自增雪花id | | rule_name | varchar | 100 | 校验规则名称 | | rule_description | varchar | 200 | 规则描述 | | rule_state | smallint | | 规则状态:0禁用 1启用 | | rule_content | varchar | 255 | 规则内容(JavaScript) | | field_name | varchar | 255 | 校验字段名称(所需要多个字段逗号分隔) | | rule_code | varchar | 100 | 规则Code(保留字段) | | rule_type | smallint | | 规则类型:0必填 1长度 2必填+长度 3敏感词 4正则 | | business_id | bigint | | 业务Id | | create_time | datetime | | 创建时间(由MybatisPlus自动填充) | | update_time | datetime | | 修改时间(由MybatisPlus自动填充) | | deleted | smallint | | 逻辑删除标识,1:存在,2:已删除 | | version | smallint | | 版本号(乐观锁) | | create_by | varchar | 100 | 创建者(也可基于Security、MybatisPlus实现自动填充) | | update_by | varchar | 100 | 更新者(也可基于Security、MybatisPlus实现自动填充) | | remark | varchar | 255 | 备注(保留字段) | #### Redis限流 > 基于`setnx`,通过自定义注解+脚本实现限流(参考若依实现) + 指定key + 凭借key + 基于方法 + 基于IP + 指定时间、次数 ```java /** * redis限流实现API * * @author dingwen * @since 2022/11/17 */ @Api(tags = "redis限流实现API") @RestController @RequestMapping("redis") public class RateLimiterController { @ApiOperation("redis限流测试") @RateLimiter(time = 1, count = 2) @GetMapping("/rate") public Result rateLimiterSimpleTest() { return ResultGenerator.genSuccessResult(); } } ``` #### 多线程任务进度条实现 > 基于`CompletableFuture`和`redis`的多线程任务进度条实现,后端异步进行任务,前端轮询调用进度条进度查询 > + `redis` > + `setnx` 检查任务key是否存在,任务是否在进行中 > + `hash` 存储任务进度信息 > + 指定哈希键值的`increment` > + `CompletableFuture` > + `whenComplete` 子任务完成时更新任务进度 > + `exceptionally` 发生异常时更新任务进度 > + `AbstractProgressBarTask` 抽象任务进度条组件,囊括进度条功能,子类继承实现业务逻辑即可 ##### API调用 ```java /** * 进度条任务API * * @author dingwen * @since 2022/12/07 */ @Api(tags = "进度条任务API") @RestController @RequestMapping("bar") public class ProgressBarTaskController { @Resource(name = "testProgressBarTask") private TestProgressBarTask testProgressBarTask; @ApiOperation(value = "提交进度条任务") @ApiImplicitParams({ @ApiImplicitParam(name = "taskId", value = "任务id"), @ApiImplicitParam(name = "taskType", value = "任务类型") }) @PutMapping() public Result submit(@RequestParam("taskId") String taskId, @RequestParam("taskType") String taskType) { TaskType taskTypeByCode = EnumUtil.getEnumByCode(TaskType.class, taskType); return ResultGenerator.genSuccessResult( testProgressBarTask.execute( taskId, taskTypeByCode, 100, 100 ) ); } @ApiOperation(value = "查询任务进度") @ApiImplicitParams({ @ApiImplicitParam(name = "taskId", value = "任务id"), @ApiImplicitParam(name = "taskType", value = "任务类型") }) @GetMapping("/{taskId}") public Result queryProgress(@PathVariable("taskId") String taskId, @RequestParam("taskType") String taskType) { TaskType taskTypeByCode = EnumUtil.getEnumByCode(TaskType.class, taskType); return ResultGenerator.genSuccessResult(testProgressBarTask.queryProcess(taskId, taskTypeByCode)); } } ``` ##### 功能概览 #### 自定义注解实现加解密、脱敏 `SensitiveController` + 枚举类`SensitiveEnum` + 实体 `SensitiveEntity` + 自定义注解 + SensitiveDecode 解密 + SensitiveEncode 加密 + SensitiveField 字段标识 + SensitiveInfoUtil 加解密、脱敏工具类 + AesEncryptUtil AES工具类 ##### 功能概览 #### 阿里云短信对接 `SmsController` ##### 功能概览 #### 微信公众号定制消息推送`WechatPubController` > 开放平台对接(基于Spring提供定时任务实现): > + 天行数据 > + 百度地图 > + 微信公众号平台 #### common-influxdb > 时序数据库案例 > + [官网](https://docs.influxdata.com/) > + 接口 `IotDataService` ### treasure-slow-sql #### 深分页查询优化 ```sql SELECT * FROM sys_user WHERE id > #{offset} LIMIT 0,#{pageSize}; ``` ```java /** * 优化Api * @author dingwen * @since 2022/8/5 */ @Api(tags = "优化API") @RestController @Slf4j @RequestMapping("optimize") @Validated public class OptimizeController { @Resource private SysUserService userService; /** * 适用于自增id * 数据总条数:4301000 * 自带查询分页性能: * * 第 1 页 10 条关闭count查询优化耗时:4秒 * 第 100,00 页 100 条关闭count查询优化耗时:5秒 * 第 100,000 页 100 条关闭count查询优化耗时:5秒 * * 第 1 页 10 条打开count查询优化耗时:4秒 * 第 100,00 页 100 条打开count查询优化耗时:4秒 * 第 100,000 页 100 条打开count查询优化耗时:4秒 * * * 优化后性能: * * 第 1 页 10 条耗时:30~80毫秒 * 第 100,00 页 100 条耗时:30~80毫秒 * 第 100,000 页 100 条耗时:30~80秒 * * * 再进行统计总记录条数时会遇到瓶颈,正确的处理方式应该是杜绝深分页,不会有用户往下翻到地100页 * 一般选择50页即可 * * @param pageDto 页面dto */ @PostMapping("/deep-page") @ApiOperation("深分页查询优化") public Result> deepPage(@RequestBody PageDto pageDto) { Long current = pageDto.getCurrent(); Long pageSize = pageDto.getPageSize(); Page page = new Page<>(current, pageSize); // 可以选择关闭count查询优化,解决一对多时分页查询总计记录条数不正确问题 page.setOptimizeCountSql(Boolean.TRUE); //return ResultUtil.genResult(userService.page(page)); return ResultGenerator.genSuccessResult(userService.optimizeSelectPage(current, pageSize)); } } ``` ### treasure-kettle > 企业级的数据中台ETL处理服务,提供数据的定时抽取、转换、加载整体解决方案 #### 下载安装 > 官网:http://community.pentaho.com/projects/data-integration > Carte接口文档:https://help.hitachivantara.com/Documentation #### 组成部分 + spoon > window客户端设计器。windows平台可以直接使用官方程序,linux or mac os 平台建议使用网页版本的spoon + pan:执行转换(命令行方式+操作系统定时任务) + kitchen: (命令行方式+操作系统定时任务) + carte: kettle服务,rest接口提供服务(支持主从) #### 资源库 + 文件资源库 + 数据库资源库: 创建数据库,使用`spoon`配置连接后可自动创建表结构,共计46张表  #### 方案思路 > Java集成定时远程调用和远程服务方式调用以及使用`spoon`客户端进行远程调用需要启动`carte`。 > carte.sh指定配置文件启动,单机考虑一主一从。默认用户密码(cluster),可在启动文件中配置 ##### Java集成定时远程调用 > 远程调用执行 > + id: master ip > + port: master port > + dataRepositoryName: 数据库资源库名称 > + user: 集群名称(master中配置) > + password: 集群密码(master中配置) > + transName: 转换文件名称 > + jobName: 作业文件名称 > + path: 路径 ```shell # 转换执行 https://{ip}:{port}/kettle/executeTrans/?rep={dataRepositoryName}&user={userName}&pass={password}&trans={path/transName.ktr} # 作业执行 https://{ip}:{port}/kettle/executeJob/?rep={dataRepositoryName}&user={userName}&pass={password}&job={path/jobName.kjb} # 查看状态监控 https://{ip}:{port}/kettle/status ``` ##### 远程服务方式调用 > 依赖于`carte`服务,可以采用`spoon`客户端触发远程作业,进行定时调用。(无需代码,配置即可实现) ##### spoon 客户端 > windows平台可以直接使用官方程序,linux or mac os 平台建议使用网页版本的spoon ```shell docker search hiromuhota/webspoon docker run -d -p 8080:8080 --name spoon hiromuhota/webspoon ``` #### 其他 ##### Kettle自带监控页面 ##### SpringBoot2.X和Kettle9整合 ```shell # 手动添加如下依赖 mvn install:install-file -DgroupId=组织名称 -DartifactId=坐标 -Dversion=9.3.0.0-428 -Dpackaging=jar -Dfile= jar包名称 ``` ### treasure-task-quartz #### 整体介绍 > 基于`quartz` > 的定时任务实现,API灵活控制,精确日志记录,分布式部署完整的解决方案。注意:当次解决方案在分布式应用场景中时,确保任务不重复执行依赖与`quartz` > 持久化到数据库依赖数据库悲观锁实现。 #### 特点 > + 分布式部署保障不重复执行不漏跑 > + 模版方法:代码可重用性 > + 实时接口调用控制任务执行、停止 > + 接口调用修改任务信息 > + 执行日志记录 #### 表设计 + `QRTZ_BLOB_TRIGGERS` + `QRTZ_CALENDARS` + `QRTZ_CRON_TRIGGERS` + `QRTZ_FIRED_TRIGGERS` + `QRTZ_JOB_DETAILS` + `QRTZ_LOCKS` + `QRTZ_PAUSED_TRIGGER_GRPS` + `QRTZ_SCHEDULER_STATE` + `QRTZ_SIMPLE_TRIGGERS` + `QRTZ_SIMPROP_TRIGGERS` + `QRTZ_TRIGGERS` ##### 定时任务信息表`quartz_info` | 名称 | 类型 | 长度 | 备注 | | ------------------ | -------- | ---- | ---------------------------- | | id | bigint | | 数据库自雪花id | | code | varchar | 255 | 定时任务code标识 | | create_time | datetime | | 创建时间 | | cron_expression | varchar | 255 | cron表达式 | | fail | int | | 失败次数 | | full_class_name | varchar | 255 | 定时任务执行类 全类名,Job类 | | job_data_map | varchar | 255 | jobDataMap json格式 | | job_group_name | varchar | 255 | job组名称 | | job_name | varchar | 255 | job 名称 | | name | varchar | 255 | 定时任务名称 | | state | int | | 是否启用 1-启用 0-禁用 | | success | int | | 成功执行次数 | | trigger_group_name | varchar | 255 | 触发器组名称 | | trigger_name | varchar | 255 | 触发器名称 | | update_time | datetime | | 更新时间 | ##### 定时任务日志表 `quartz_log` | 名称 | 类型 | 长度 | 备注 | | -------------- | -------- | ---- | ------------------------------------ | | id | bigint | | 数据库自雪花id | | quartz_id | bigint | | 任务id关联 | | activate_time | datetime | | 激活时间 | | consumer_time | int | | 任务耗时 | | execute_result | int | | 执行结果:1: 成功0: 失败 | | remark | varchar | 255 | 备注 | ### treasure-manage > 常用后台管理实现 ### treasure-common > 公共模块 > + base: 基础、通用 > + config: 配置 > + core:核心通用组件 > + jpa:jpa场景 > + mybatisplus mybatisplus场景 > + web: web场景 > + knife4j: API文档 > + rabbitmq: RabbitMQ 应用场景 > + redis: Redis 应用场景 ### treasure-admin > 基于`SpringBoot Admin`整合`Spring Security`的监控实现,目前暴露所有端点,权限账户信息通过`nacos`配置指定 ```yaml spring: security: user: name: actuator password: actuator ``` > TODO > + 自定义info、metrics、health、endpoint > + 邮件、钉钉预警 ### treasure-xxl-job-admin > 基于`xxl-job v2.4.0`封装的调度中心 ```yaml xxl: job: accessToken: xxl-job-access-token i18n: zh_CN triggerpool: fast: max: 200 slow: max: 100 logretentiondays: 30 ``` ### `treasure-poi-tl` > poi-tl 官网: https://github.com/Sayi/poi-tl > `word`模板渲染解决方案,拒绝手动维护`xml`文件 ### 钉钉企业微信预警`treasure-dingtalk-ger` > 钉鸽官网: https://github.com/AnswerAIL/dingtalk-spring-boot-starter #### 概览 #### 工具类 `DingerUtils` > 可在全局异常处理处调用`DingerUtils.send(e);` ```java /** * 钉鸽 * * @author dingwen * @since 2023/9/22 14:47 */ public class DingerUtils { public static void send(Exception e) { DingerSender dingerSender = SpringUtil.getBean(DingerSender.class); if (Objects.isNull(dingerSender)) { return; } String msg = "用户:{},userId:{},请求地址:{},入参{},请求体参数:{},发生异常,消息:{},堆栈信息:{}"; String url = ServletUtils.getUrl(); String parameters = ServletUtils.getParameters(); String body = ServletUtil.getBody(ServletUtils.getRequest()); Long userId = SecurityUtils.getUserId(); String username = SecurityUtils.getUsername(); dingerSender.send( MessageSubType.TEXT, DingerRequest.request(StrUtil.format(msg, username,userId,url,parameters,body,e.getMessage(), printStackTraces(e))) ); } /** * 堆栈信息 * * @param e 异常 * @return 异常信息 */ public static String printStackTraces(Exception e) { StackTraceElement[] stackTraces = e.getStackTrace(); StringBuilder builder = new StringBuilder(); builder.append(e.getClass().getName()) .append(": ") .append(e.getLocalizedMessage()) .append("\n"); for (StackTraceElement stackTrace : stackTraces) { String lineMsg = " at "; lineMsg = lineMsg + stackTrace.getClassName() + "(" + stackTrace.getFileName() + ":" + stackTrace.getLineNumber() + ")\n"; builder.append(lineMsg); } return builder.substring(0,300); } } ``` #### 服务启停监听,dinger通知 ```Java package com.dingwen.treasure.gtl.listener; import com.dingwen.treasure.gtl.util.DingerUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; /** * 应用启动事件监听 * * @author dingwen * @since 2023/10/9 18:22 */ @Component @Slf4j public class PreStopListener implements org.springframework.context.ApplicationListener { @Override public void onApplicationEvent(ContextClosedEvent event) { log.info("清廉系统后端服务已停止"); // 可依据环境判断是否执行 DingerUtils.send("清廉系统后端服务已停止"); } } ``` ```java package com.dingwen.treasure.gtl.listener; import com.dingwen.treasure.gtl.util.DingerUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.stereotype.Component; /** * 应用启动事件监听 * * @author dingwen * @since 2023/10/9 18:22 */ @Component @Slf4j public class StartListener implements org.springframework.context.ApplicationListener { @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 可依据环境判断是否执行 DingerUtils.send("清廉系统后端服务已启动"); } } ``` ### 设计模式`treasure-gof` ### 其他临时文档 + business > 优雅代码业务组件实现(MybatisPlus) > 特色业务场景 > + 线程池案例 > + 批量save > + 自定义MVC反序列化方式 > + 缓存业务场景自定义注解:@EasyCache > + 国际化 > + 状态模式+简单工厂模式实现:多阶段灵活控制状态流转 设计模式六大原则: + 单一职责 + 接口隔离(Servlet filter) + 依赖倒置 + 迪米特(最少知道原则) + 里氏替换 (父类抽象方法应保持行为一致,非抽象方法不应去重写) + 开闭原则 + 合成复用 + 状态模式: 对象不同的状态导致不同的行为 + 主要角色: + 抽象状态(接口或抽象类) + 具体状态(抽象状态的子类或实现类) + 环境类 (状态持有着,依据不同的状态出发不同的行为)  > + MybatisPlus:乐观锁、逻辑删除、自动枚举转换、多数据源、Model、自动填充、分页等其他通用API > + 观察者模式:(事件对象、监听)下订单走库存以及日志,以事件驱动 > + 支付案例:简单工厂 + 模版方法 + 策略模式 + 函数式接口 + 责任链模式:为请求创建了一个接收者对象的处理链,,对请求的发送者和接收者进行解耦 > + 基于redis、redisson的分布式锁 > + 防止重复提交自定义注解:@ReSubmit > + (解决数据库和缓存不一致的问题)系统配置:db、redis、自定义缓存(利用redis发布订阅实现三级缓存) > + 基于Mybatis的通用crudController封装已经整合API文档 > + 全局统一返回 > + 全局异常处理 > + 优雅DTO、VO、BO、PO转换 > + feign调用(整合hystrix实现服务降级) > + 系统配置实现(三级分布式缓存)redis 发布订阅实现跨内存刷新 > + AOP 切面实现方法调用前后入参、返回值、耗时等调试日志(全局所有controller)、基于配置灵活开启关闭 > + 消息队列业务模拟(保证顺序消费、保证不重复消费、消息可靠投递、消息可靠消费) > + 操作日志(AOP+Sprint EL 实现同步ElasticSearch)参考美团2021年技术年报实现、基于配置灵活开启关闭 > TODO > + redisson 分布式锁 > + 规范转换Bean mapstruct > + 基于JPA的通用crudController封装 > + 通用crudController封装 mongoDB + gateway > 网关,基于`nacos`的可配置白名单 > TODO > + 统一请求日志 > + 认证 > + 服务间其他信息 + pdf > 基于`freemarker`模版实现的后端生成PDF或在线预览功能 + manage > 后台管理模块 > 特色业务场景 > + 消息队列业务模拟(顺序消费、不重复消费、可靠消费) 死信队列、延时队列 + auth > 认证模块 > TODO > + 权限 > + RBAC > + 参考若依实现 > + 菜单、路由 > TODO > + 文件上传minio + API文档 + knife4j + business-service: 地址:http://127.0.0.1:20908/doc.html + task-service: 地址:http://127.0.0.1:20902/doc.html + 用户名:`test` + 密码:`123` + 监控面板 + spring-boot-admin、spring-boot-starter-security + 地址:http://127.0.0.1:20906/login + 用户名:`actuator` + 密码:`actuator` + 国际化 + 监控 + 定时任务 + API文档 + Aop + ReSubmit(防止重复提交) + EasyCache (缓存) + 通用业务组件 + 简单工厂 + 策略模式 + 模版方法 + Spring 高级 + 观察者模式(监听、事件机制) + 自动注入Map + 监控自定义 预警(钉钉、邮件) + Mybatis Plus + 多数据源 + 逻辑删除 + 乐观锁 + Model + lambda + 通用枚举 + 联合主键`@MppMultiId`、`IMppService`、`MppBaseMapper` + 自动填充新增时间、修改时间 + Mysql + 枚举类型 + Json 类型 + 分布式锁 + 分布式事务 + Sentinel + 链路追踪 + 定时任务 Quartz + 设计模式:模版方法 + Jpa 实现 + 基于数据库悲观锁实现分布式锁,支持多节点部署 + 保证不重复执行 + 保证不漏执行 + Rabbitmq + 可靠消费 + 可靠投递 + 顺序消费 + 全局异常处理 + 权限 > 参考若依实现 + SSO + 系统配置 + 通用日志 + 操作日志 + 系统日志 > 参考美团技术年报、若依。初步实现思路:Aop及Spring EL 表达式实现日志数据组装,通过RabbitMq将数据同步到ElasticSearch + MongoDB + WebFlux + canal + 依赖优化 + 规范转换Bean mapstruct + 全局异常 + 权限(market、ruoyi) + 系统配置 + Redis 实现分布式锁 + 状态模式 + 状态直接可以存在相互依赖关系 + 状态之间可以相互转换,可以反复 + 策略模式 + 多种算法行为选择一个就能满足 + 算法独立 + 自定义MVC反序列化进行Java Bean 数据封装 + 文件存储 + 短信 + spring 批处理 batch + 网关统一日志 + 统一系统配置 > redis & JVM 两级缓存,使用 redis 发布订阅实现,支持分布式 + JDBC 批处理 + DTO、VO、BO 转换 + 定时任务BUG + @EnableAspectJAutoProxy > 在SpringBoot2已经无效,需要通过,`spring.aop.proxy-target-class=false` > 指定为JDK方式实现,默认值为true,即采用CGLIB实现 + TODO + 分布式定时任务框架 xxl_job + 分布式事务 + 分布式锁 + 并发编程 + SQL优化 + 脚本 + 容器化 + ELK + 日志配置 + 启动初始化 + `ApplicationRunner` + `CommandLineRunner` + http://127.0.0.1:20900 网关 + 分布式文件存储 minio + 调试日志(入参、返回值、耗时)es + 后端渲染生产PDF (freemarker) + mybatis 场景整合 (动态标签等常用技巧备忘) + 文件预览 + excel 通用封装 (基于 hutool 、 poi) 参考若依 + sql 优化 + JVM + SQL 窗口函数 + 参数范围校验注解 + 字典 + 高德地图 + 日志配置 + 枚举 + 序列化、反序列化 + excel + docker 部署 + 若依数据权限 > 存储过程没有返回值 procedure call > 必须有返回值 function 直接调用 + token 刷新 + 全局拦截器 + nacos 刷新 + 慢sql监控 + 用户在线统计 + 站内信息 + 字典 aop + 观察者模式 + 状态模式 + 享元模式 + 单例模式 + 构建者模式 + 原型模式 + mongodb 索引优化 + lambda return + 常量定义 + feign 调用 localDatetime反序列化问题 + 工厂方法模式 应用场景 + 抽象工厂模式 应用场景 + JUC 中断三种方式 + 工作流 + validator 分组校验 + 字典 + 根据字典配置动态生成枚举类型 + 字典动态翻译 + 文件视频格式等问题预览 + 依赖模块优化 + 消息可靠性 + juc + spring cloud + kkfile + ffmepg + 可靠消费 不丢失 重复 实战 + 缓存双写实战 + 代码生成 + 动态数据源 druid 监控 + 数据权限 租户 若依 + xss + author2 + mapstruct + HashMap + ConcurrentHashMap + druid 数据源 + 跨域(Cross Origin Resource Sharing) + 发生在前端 + 浏览器的同源策略:协议、主机、端口 + 三种后端解决方式 + @CrossOrigin + Cross Filter + WebMvcConfigure + UML:Unified Modeling Language + 类图(两个矩形:顶类名称,上属性,下方法) + 属性: 权限 名称: 类型 + 方法: 权限 方法名称(参数列表): 返回值类型 + 权限: + ` ` default + `-` private + `+` public + `#` protected + 关系: + 关联关系: + 引用:实线实心三角形箭头指向被引用的一方 + 双向关联:实线 + 自关联: 实线实心三角形箭头指向自己 + 聚合关系:整体和部分的关系,强烈的聚合关系,部分可以离开整体 + 实线空心菱形指向整体 + 组合关系: 整体和部分的关系,更强烈的聚合关系,部分不可以离开整体(头、嘴) + 实线实心菱形指向整体 + 依赖关系: 耦合度最弱的一种关联关系(调用,引用) + 虚线虚线箭头指向被依赖的类 + 继承关系(泛化关系) + 实线空心三角形指向父类 + 实现关系 + 虚线空心三角形箭头指向接口 + nullSafeEquals + 枚举优化 + redis 队列、map + 大文件上传、切片、多线程、断点续传 + Spring cache + @Cacheable + InitializingBean + xss + @CacheEvict + @EventListener + 微服务 过滤器认证 market + treasure 开放平台 + 交换平台 + webservice + websocket + pig4 + nacos内置 + 数据权限 + 代码生成 + js 规则引擎 + webflux + security 方式认证授权 + 开放平台 4种授权 三方登录 + xss + @inner + webservice + websocket + @PositiveOrZero + base controller + redis 限流 + 通用返回优化 + 自增主键,分页优化方案 git config --local http.postBuffer 157286400 + ER + 详细设计 + 技术文档 + @Configuration(proxyBeanMethods = false) + true: 走代理,配置类中各个方法相互依赖 + false: 不走代理,配置类中各个方法不依赖,可提高性能 + pom优化 + kettle + 缓存 + 上下文待优化 + 登录待优化 + TokenService待优化 + StringJoiner + 数据脱敏考虑隐私权限、用户权限 + 数据权限 + 开放平台 + 集成三方登录 + 短信、天气 + 本地缓存 + 如何停止一个线程 + rpc + docker 部署 + 脚本 启动等 + influxdb 时序数据库 + navicat 模型 + java -jar -Dfile.encoding=utf-8 -DTREASURE_NACOS_NAMESPACE=treasure treasure-business.jar + docker build -t treasure-business:v1.0 . + docker run -p 20903:20903 --name treasure-business -d treasure-business:v1.0 + 开放平台 oauth + 第三放登录 + 高德地图导入行政区划 + 若依数据权限 【PLus】 + 验证码登录 + 枚举 + 若依 @Anonymous + 异常国际化处理 + git 指定某次提交合并到指定分支【先切换到目标分支 在执行git cherry-pick 8888189f】 + `XXXController` > 功能动词 + `obtainXXX`获得 + `discardXXX`删除 + `XXXManager`、`IXXXManager`、`IXXXManagerImpl`【Optional】 > 功能动词+For使用场景 + `obtainDeptTree`【获得部门树】 + `XXXService`、`IXXXService`、`IXXXServiceImpl` 【Optional】 > 功能动词+By条件+For使用场景 + `queryDeptById`【通过部门id查询部门信息】 + `queryDeptsForMini` 【小程序端查询部门列表】 + `queryDeptsForWeb` 【Web端查询部门列表】 + `queryDeptPage` 【部门列表分页查询】 + `modifyDept`【修改部门信息】 + `createDept`【创建一个部门】 + `createDepts`【创建多个部门】 + `removeDeptById`【通过部门id删除部门】 + `removeDeptByIds`【通过部门ids删除部门】 + `XXXMapper` + `insertDept` 【插入一个部门】 + `insertDepts` 【批量插入部门】 + `updateDeptById` 【通过部门id修改部门信息】 + `deleteDeptById`【通过部门id删除部门】 + `deleteDeptByIds`【通过部门ids删除部门】 + `selectDeptsByRLikeName` 【通过部门名称右模糊查询部门列表】 + `selectDeptPage` 【分页查询部门列表】 + `XXXProcessor` 处理 + `XXXHolder` 持有 + `XXXFactory` 工厂 + `XXXProvider` 提供者 + `XXXRegistor` 注册 + `XXXEngine` 核心处理逻辑 + `XXXTask` 任务 + `XXXContext` 上下文 + `XXXHandler`、`XXXCallback`、`XXXTrigger`、`XXXListener` + `XXXAware` 感知 + `XXXMetric`指标 + `XXXPool`池 + `XXXChain` 链 + `XXXFilter` 过滤 + `XXXInterceptor` 拦截器 + `XXXEvaluator` 判断条件是否成立 + `XXXStrategy` 策略 + `XXXAdapter`适配器 + `XXXEvent` 事件 + `XXXBuilder` 构建 + `XXXTemplate` 模版 + `XXXProxy` 代理 + `XXXConverter` 转换 + `XXXRessolver`解析 + `XXXParser`解析器 + `XXXUtils` 工具类 + `XXXHelper` 帮助类 + `XXXConstant` 常量 + `XXXGenerator` 生成 ## 场景启动器使用聚合`scene` ### Maven环境隔离 #### 引入打包插件 ```xml org.apache.maven.plugins maven-resources-plugin ${project.build.sourceEncoding} @ false ${maven-resources-plugin.version} ``` #### `resource` ```xml src/main/resources true **/*.xlsx **/*.xml **/*.docx src/main/resources false **/*.xlsx **/*.xml **/*.docx ``` #### 配置使用 ### 10.2 `disruptor`的 ` log4j2`高性能异步日志 > 整体性能有显著提升,适用C端的大量日志场景 #### 添加依赖 ```xml com.lmax disruptor ${disruptor.version} org.springframework.boot spring-boot-starter-log4j2 ``` #### 排除相关`logback`冲突包 ```xml org.springframework.boot spring-boot-starter-web spring-boot-starter-logging org.springframework.boot ``` > 整体进行排除 ```xml org.springframework.boot spring-boot-starter-logging * * ``` #### 配置 + 使用 ## 后续计划 + 通用业务场景拆分封装 + Cloud系、Dubbo系 + 监控、告警 + 前端部分 + Vue3 + Uniapp + Flutter + ELK + 容器化 ## 待完成任务 + 数据归档组件 + 数据变更记录组件 + 缓存预热进一步优化 + httpclient5 优化 + 京东async封装 + fc-async + 微服务 cloud + eureka + ribbon... + 自定义ribbon负载均衡 + `alibaba`微服务系 + 设计模式系 + file场景改造优化 + 分片上传,断点续传,秒传 + 基于nacaos动态配置策略 ## 联系我 ## 文档版本 > 2023-12-05 15:26:49
Quartz禁止并发执行 DisallowConcurrentExecution
自带查询分页性能:
优化后性能: