32 Star 120 Fork 19

dingwen / treasure

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

Treasure

2020-05-05~至今

初衷

积累,成长

分支规划

目前分支规划存在一定的不合理性,将在后期进行完善优化.所有的架构都是基于业务进行演化的.

工具类 > 工具包 > 功能模块 > 场景启动器 > 功能服务 > 分布式

  • master 微服务架构 业务场景模拟,学习案例
    • SpringBoot 2.3.2.RELEASE
    • SpringCloud Hoxton.SR8
    • Nacos
    • OpenFeign 2.2.5.RELEASE
    • Hystrix 2.2.5.RELEASE
    • gateway
  • study 单体架构、学习案例
    • SpringBoot 2.3.12.RELEASE
  • scene 单体架构、最佳实践、插件化、模块化、配置化
    • SpringBoot 2.7.5
    • Spring Security 2.7.5
  • cloud 规划中
    • SpringBoot 2.4.2
    • SpringCloud 2020.0.6
    • eureka 3.0.6
    • hystrix 2.2.10.RELEASE
    • openfeign 3.0.7
    • ribbon 2.2.10.RELEASE
  • cloud alibaba 规划中
    • nacos
    • gateway
    • sentinel

简介

Treasure是一个Java技术生态项目,涵盖了单体、微服务、DDD等架构实践,以兴趣、学习目的、技术积累为理念,逐步完善迭代。主要包含学习成长过程中一些技术点、工作中积累的一些心得,面试中一些业务场景模拟及解决方案一些常见、通用业务的解决方案、合理应用设计模式进行一些业务代码的重构优化、常用小轮子的积累、一些更优雅的编码实现、通用场景封装等内容

模块

scene分支

为了方便随取随用,没有做过多的聚合,各个模块相对独立

com.dingwen
├── scene[综合场景启动器使用示例]                                               				
│     └── pom.xml[ Maven依赖]                                      									
├── starter[启动器集]                                            				  						
│       └── base-spring-boot-starter[基础场景启动器]                        						
│       └── api-docs-spring-boot-starter[API文档再封装场景启动器]                  		
│       └── screw-spring-boot-starter[数据库文档生成场景启动器]                     		
│       └── webplus-spring-boot-starter[Web再封装场景启动器 ]                  				
│       └── redis-spring-boot-starter[Redis场景启动器]                       				
│       └── caffeine-spring-boot-starter[本地缓存启动器]                  							
│       └── async-spring-boot-starter[异步场景启动器]                     							
│       └── enums-spring-boot-starter[枚举场景启动器]                   								 
│       └── mybatisplus-spring-boot-starter[MybatisPlus场景启动器]       						
│       └── log-spring-boot-starter[日志场景启动器]                          				
│       └── logv-spring-boot-starter[日志查看启动器]                        						
│       └── email-spring-boot-starter[电子邮件场景启动器]                      				
│       └── mongo-spring-boot-starter[MongoDB场景启动器]                     				
│       └── xxl-job-spring-boot-starter[xxljob场景启动器]                    				
│       └── translate-spring-boot-starter[翻译场景启动器]               								
│       └── oss-spring-boot-starter[文件存储场景启动器]                     						
│       └── pipeline-spring-boot-starter[责任链Pipeline场景启动器]               			
│       └── bar-spring-boot-starter[进度条场景启动器]                     						
│       └── dic-spring-boot-starter[字典场景启动器]                     								
│       └── excel-spring-boot-starter[Excel场景启动器]                  							
│       └── config-spring-boot-starter[系统配置场景启动器]                  						 
│       └── quartz-spring-boot-starter[Quartz定时任务场景启动器]                  			 
│       └── file-spring-boot-starter[文件场景启动器]                    								
│       └── change-log-spring-boot-starter[变更记录场景启动器]                   			
│       └── db-backup-spring-boot-starter[数据归档场景启动器]  
│       └── event-spring-boot-starter[事件场景启动器]
│       └── 规划中[数据脱敏场景启动器]                                            			
│       └── jwt-spring-boot-starter[JWT场景启动器]                                   
│       └── security-plus-spring-boot-starter[Security场景启动器]  
│       └── auth-spring-boot-starter[认证场景启动器]  
│       └── dcache-spring-boot-starter[二级缓存启动器]  
│       └── kkFile-docker[文件预览场景启动器]                                         
│       └── 规划中[监控场景启动器]                                            				 
│       └── 规划中[审核场景启动器]                                            					 
│       └── 规划中[Flowable启动器]                                            				 
│       └── 规划中[Camunda启动器]                                            					 
│       └── 规划中[微信公众号开发启动器]                                            		
│       └── 规划中[钉钉开发启动器]                                            					
│       └── 规划中[重要表单变更日志启动器]                                            	
│       └── 规划中[elasticsearch启动器]    
│       └── 规划中[Dingger消息告警二次封装] 
├── pom.xml[Maven依赖]                                            														     

study分支

com.dingwen 
├── treasure-canal-client[canal客户端 [80]]            						 
├── treasure-kettle[kettle集成企业级解决方案 [9999]]                   						 
├── treasure-websocket[websocket方案 [8081] [8080]]               					    
├── treasure-sms4j[通用短信解决方案 [8080]]                   						   
├── treasure-poi-tl[word模板渲染解决方案 [8080]]                  						   
├── treasure-jimu-report[开源报表解决方案 [8080]]              					 
├── treasure-dingtalk-ger[钉钉,企业微信预警机器人解决方案 [8080]]			  				     
├── 规划中[认证解决方案 [8080]]								  						  
├── 规划中[单点登录解决方案 [8080]]								 						     
├── 规划中[动态表单解决方案 [8080]]								  						  
├── treasure-gof[大话设计模式]										    
├── pom.xml[Maven依赖]                         						   

master分支

com.cdn  
com.dingwen 
├── treasure-auth[认证服务 [20902]]                       														
├── treasure-business[业务服务 [20903]]                   													  
├── treasure-admin[监控服务 [20901]]                     														 
├── treasure-common[通用模块]                    													 
│       └── common-pom[依赖管理模块]                  												  
│       └── common-base[基础模块]                													
│       └── common-beansearcher[对象搜索]         											
│       └── common-config[基础配置]             												 
│       └── common-core[ 核心模块]      													           
│       └── common-jpa[持久层JPA]           													        
│       └── common-jwt[JWT令牌]                 													  
│       └── common-knifej[接口文档]                 											
│       └── common-model[通用MODEL]                    										       
│       └── common-mongodb[MongoDB]                 											 
│       └── common-mybatisplus[持久层Mybatisplus]             											  
│       └── common-rabbitmq[RabbitMQ]                											   
│       └── common-redis[Redis]                    											   
│       └── common-security[安全模块]              												 
│       └── common-sensitive[自定义注解实现数据脱敏]              											 
│       └── common-web[WEB模块]                     											    
│       └── common-tkmybatis[tkmybatis模块]               											 
│       └── common-minio[minio文件存储]                  											  
│       └── common-easyexcel[excel 文件导入导出]              										     
│       └── common-influxdb[时序数据库案例]                 											
│       └── common-open-api[open api 案例]                											   
│               └── open-api-baidu-map[百度地图]         										     
│               └── open-api-sms[阿里云短信]            											 
│               └── open-api-tx[天行数据基础服务]            													 
│               └── open-api-wechat-pub[微信公众号]            									    
│               └── open-api-baidu-map[百度地图]             										  
│               └── open-api-tianxing-rainbow[天行数据彩虹屁]          								  
├── treasure-gateway[网关服务 [20904]]                 													     
├── treasure-log[日志服务 [20905]]                     													       
├── treasure-manage[后台管理 [20906]]                 													     
├── treasure-file-generate[ 文件生成服务 [20907]]               												
├── treasure-task-quartz[定时任务(Quarzt实现) [20908]]                								
├── treasure-file[文件服务 [20909]]                     													      
├── treasure-code-generate[代码生成服务 [20910]]                  											  
├── treasure-slow-sql[慢SQL [20911]]                     													  
├── treasure-xxl-job-admin[xxl-job-admin [20933]]              												
├── logs[日志]                         													         		    
├── sql[sql]                               													         	     
├── img[图片]                                 													            
├── pom.xml[公共依赖]                               													      

cloud

SpringCloud 实践

alibaba_cloud

阿里系微服务生态实践

ddd

DDD架构实践

架构图

参考若依

规划中

概览

API文档

在线文档

技术点

Redis Mysql MongoDB Canal Postgresql ElasticSearch Alibaba Druid
Spring SpringMVC SpringBoot Spring Security Spring-retry mybatis-plus-join-boot SpringBoot Admin
Mybatis MybatisPlus TK Mybatis JPA
Mapstruct MapstructPlus Hutool Screw BeanSearcher EasyExcel knife4j p6spy
ip2region Guava commons-lang3 Lombok Maven
xxl-job Quartz
Nacos SpringCloud Alibaba GateWay Feign Hystrix Ribbon
RabbitMQ RocketMQ Kafka
Kettle ELK
Thymeleaf Layui
Minio Aliyun OSS JWT
Websocket sms4j
Bistoury poi-tl jimu-report dingtalk-spring-boot-starter Arthas

通用应用场景Starter封装

接口文档地址: https://apifox.com/apidoc/shared-8d3d332d-b936-4ec1-8d14-516d89a8f85d

Maven增加一下配置即可


<servers>
    <!-- 阿里云私服开始 -->
    <server>
        <id>rdc-releases</id>
        <username>62c3e2f6a908b6a4db54fa26</username>
        <password>5V8N7KJ]rvtt</password>
    </server>
    <server>
        <id>rdc-snapshots</id>
        <username>62c3e2f6a908b6a4db54fa26</username>
        <password>5V8N7KJ]rvtt</password>
    </server>
    <!-- 阿里云私服结束 -->
</servers>

        <!-- 相当于拦截器访问改地址是映射的配置 -->
<mirrors>
<!-- 阿里云制品仓库 -->
<mirror>
    <id>mirror</id>
    <mirrorOf>central,jcenter,!rdc-releases,!rdc-snapshots</mirrorOf>
    <name>mirror</name>
    <url>https://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

</mirrors>


<profiles>
<!-- 阿里云制品仓库 -->
<profile>
    <id>rdc</id>
    <properties>
        <altReleaseDeploymentRepository>
            rdc-releases::default::https://packages.aliyun.com/maven/repository/2380560-release-WXL1gl/
        </altReleaseDeploymentRepository>
        <altSnapshotDeploymentRepository>
            rdc-snapshots::default::https://packages.aliyun.com/maven/repository/2380560-snapshot-vKCASA/
        </altSnapshotDeploymentRepository>
    </properties>
</profile>
</profiles>

        <!-- 激活配置 -->
<activeProfiles>
<activeProfile>rdc</activeProfile>
</activeProfiles>

基础场景启动器

基于SpringBoot2.1.7JDK1.8封装的基础场景启动器

核心功能

  • 基础异常
  • 通用工具类
  • logback配置优化,异步日志优化

使用

引入依赖
        <!--基础场景启动器-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>base-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableBase注解以开启基础场景功能

@EnableBase

核心工具类

  • AspectUtils: 切面工具类

  • BeanCopyUtils: bean深拷贝工具(基于 cglib 性能优异)

  • CheckerUtils: Lambda形式对象校验包

  • DateUtils: 日期工具类

  • LaExUtils: Lambdas受检异常封装处理

  • AddressUtils: 地址工具类

  • RegionUtils: IP离线定位

  • MessageUtils: 国际化消息

  • ReflectUtils: 反射工具

  • SqlUtils: sql操作工具类

  • StringUtils: 字符串操作工具

  • StreamUtils: stream工具类

    • distinct() List复杂类型去重+简单类型去重
    • group2Map(Collection<E> collection, Integer groupSize) List任务分组
    • ...
  • IdUtils: ID工具类

  • Validatetils: 编程式灵活校验工具

  • SpringUtils: Spring工具类(基于Hutool拓展,获取代理对象)+发布事件持久化拓展

  • OptimizeUtils : 优化工具类

  • PinYin4jUtils: 文字转汉语拼音

  • MapstructUtils: 对象映射拷贝工具再封装

使用案例

CheckerUtils
 Checker<SysUser> checker = Checkers.<SysUser>lambdaCheck()
                    .notNull(SysUser::getName)
                    .ne(SysUser::getAge, 0)
                    .custom(item -> item.getAge() > queryByDb(item.getId()), "年龄异常");
            checker.check(sysUser);
OptimizeUtils

参考Spring StopWatch 的拓展优化,精确计算执行耗时,执行次数,方便进行优化 OptimizeUtilController

/**
 *  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();
    }
}

优化工具类

SpringUtils

publishEvent(event),发布订阅事件拓展,提供基础事件对象BaseEvent,同一发布事件入口,进行事件持久化方便进行监控和重发.当然监听实现方需要考虑幂等实现

将提供event-spring-boot-starter实现IEvent接口实现事件对象持久化,重发等功能

   /**
    * 发布事件,将指定的应用事件发布到Spring事件传播机制中。
    * @param event 应用事件对象,代表一个具体的事件实例,将被Spring应用上下文中的事件监听器处理。
    */
   public static void publishEvent(BaseEvent event){
       // 将事件对象持久化到数据库,方便实现重发
       Map<String, IEvent> events = SpringUtil.getBeansOfType(IEvent.class);
       events.forEach((eName,ev) -> ev.saveEvent(event));
       // 将事件发布到Spring应用上下文中
       SpringUtil.publishEvent(event);
   }

可靠的事件订阅机制

参考: 事件场景启动器

获取当前用户服务 ICurrentUserService

提供顶层接口,供外部实现.从而实现与权限模块解耦. 注意: 次接口仅仅支持单实现,不支持策略

web场景启动器

SpringBoot Web的二次封装

核心功能

  • 序列化反序列化配置

    • Date
    • LocalDateTime
    • Long 解决大数字前端精度丢失问题
  • Debug方法级别调试日志

  • 静态资源映射配置化实现

  • Xss防护配置化实现

  • 构建可重复读取inputStream的request

  • 分页工具 PageUtils

  • 支持国际化的统一异常处理 GlobalExceptionHandler

  • 支持国际化的统一接口返回 ResultVOGenerator

    • 增加tranceId返回: 实现每次请求可溯源
  • 优雅校验实现

  • 允许的值集校验 AllowableValues

  • 禁止的值集校验 BanValues

  • 中文字符校验 Chinese

  • 身份证号码校验 IdCard

  • 枚举值校验 EnumValues

  • 多个字段必须有一个不为空 ChooseRequired

  • 手机号码校验 Mobile

  • 数字校验 Numbers

  • 当指定字段满足某值时当前字段不能为空 WhenRequired

  • 分组校验 ValidGroup

  • 编程式灵活校验 ValidateUtils

  • ServletUtils 若依拓展Servlet工具

  • Controller基础接口抽象

    • BaseCrudController
    • BaseViewController
  • 基础查询对象封装,支持数据权限

  • 全局日志请求耗时过滤器GlobalLogFilter

  • 自适应浏览器的国际化方案

使用

引入依赖
<!--webplus场景-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>webplus-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableWebplus注解以开启web场景功能场景功能

@EnableWebplus

配置webplus

dingwen:
  treasure:
    # webplus
    webplus:
      # 开启Debug
      debug: true
      # 静态资源映射处理
      handlers:
        - handler: "doc.html"
          locations: "classpath:/META-INF/resources/"
        - handler: "swagger-ui.html"
          locations: "classpath:/META-INF/resources/"
        - handler: "/webjars/**"
          locations: "classpath:/META-INF/resources/webjars/"

概览

链路追踪日志

基于过滤器+AOP+InheritableThreadLocal实现的可配置的链路追踪日志方案,后期可对接ELK加工处理

全局异常优雅处理

区分不同的环境,通过全局异常捕获,统一返回国际化的友好的异常消息.

全局可支配过滤器

  • 全局日志请求耗时GlobalLogFilter
  • 构建可重复读取的请求对象RepeatableFilter
  • 发布式链路追踪日志TraceIdFilter
  • 防止XSS攻击XssFilter

自适应浏览器的国际化方案

支持异常消息和valid校验. I18nConfig,ValidateConfig

    @ApiOperation("测试国际化校验消息")
    @ApiImplicitParam(name = "msg", value = "消息", dataTypeClass = String.class)
    @GetMapping("/valid")
    public ResultVO<String> testInternationalization(@RequestParam @Size(min = 1,max = 6,message = "{treasure.webplus.valid.test.msg.size}") String msg) {
        return success(msg);
    }

全局异常处理拓展消息告警 GlobalExceptionMsgSender

使用方只需要实现GlobalExceptionMsgSender接口即可,当发生异常是会自动处理异常消息发送.业务系统可以自定义消息告警的方式.首推飞书的机器人告警.

成熟的开源解决方案: https://gitee.com/jaemon/dingtalk-spring-boot-starter.git

自定义文件视图WebPlusView

继承自org.springframework.web.servlet.view.AbstractView实现自定义文件视图

异常消息告警场景启动器

TODO

API文档场景启动器

基于Knif4j封装的API文档场景启动器

使用

引入依赖
        <!--api文档生成启动器-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>api-docs-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableApiDocs注解,以开启接口文档功能

@EnableApiDocs

配置

dingwen:
  treasure:
    # API接口文档生成
    api:
      docs:
        # 标题
        title: "Ding Wen Service Api"
        # 描述
        description: "This is Interface Desc"
        # 服务地址
        url: "http://127.0.0.1"
        # 版本号
        version: "1.0.0"
        # 分组名称
        group: prod
        # 内部API请求头值
        headervalue: "DINGWEN-API-VALUE"
        # 内部APi请求名称
        headername: "DINGWEN-API-NAME"
        # 文档联系人名称
        contactname: "dingwen"
        # 文档联系人站点
        contacturl: "https://treasure.dingwen.top"
        # 文档联系人邮箱
        contactemail: "dingwen0314@163.com"

概览

异步通用场景启动器

实现SpringBoot中线程池动态化配置

核心功能

  • 线程池参数详细动态化配置
  • 实时执行日志可配置打印

使用

引入依赖
        <!--异步场景-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>async-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableApiDocs注解,以开启异步场景功能,注意: 包名称是:com.dingwen.treasure.async.annotation下的

@EnableAsync

配置线程池

dingwen:
  treasure:
    # async
    async:
      # 是否开启线程池实时日志打印
      logPrint: true
      pool:
        # 核心线程数
        - core: 8
          # 最大线程数
          max: 16
          # 线程空闲时间
          keepAliveTime: 60
          # 缓冲队列大小
          queueCapacity: 2000
          # 线程池前缀
          poolNamePrefix: treasure-async-
          # 线程池对象名称
          poolBeanName: logvExecutor
          # 线程池拒绝策略
          poolPolicy: CallerRunsPolicy
代码片段
    // 注解式
    @Resource(name = "fileExecutor")
    @Lazy
    private ThreadPoolTaskExecutor fileExecutor;
    
    
    // 编程式
    ThreadPoolTaskExecutor logVExecutor = SpringUtil.getBean("logvExecutor", ThreadPoolTaskExecutor.class);

日志通用场景启动器

实现方法级别的日志,请求级别的日志,业务级别的日志配置化,拓展化,可视化以及代码定位

核心功能

方法日志

方法日志细分为整体日志MeLog,参数日志ParamMeLog,返回结果日志ResultMeLog,方法异常日志ThrowingMeLog可灵活配置实现 可基于日志格式化接口定制实现,已提供默认实现 提供回调接口可灵活拓展处理日志

  • 方法日志
    • 参数日志
    • 返回结果日志
    • 异常日志
请求日志

可整体配置所有请求的日志记录规则,提供回调接口可进行定制化

操作日志

基于注解实现的操作日志,依附于Spring事件监听机制解耦,监听对应日志实现及可实现定制化

登录日志

Spring事件监听机制解耦,监听对应日志实现及可实现定制化

使用

引入依赖
        <!--日志场景-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>log-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableLog注解以开启日志通用场景功能

@EnableLog

日志配置

dingwen:
  treasure:
    # 请求日志
    log:
      request:
        # 是否开启请求日志
        reEnable: true
        #  是否记录请求体内容
        reEnableBody: false
      method:
        global-log-level: debug
        # 全局综合日志代码定位
        global-log-position: unknown
        # 全局综合日志格式化
        global-log-formatter: com.dingwen.treasure.log.method.format.DefaultMeLogFormatter
        # 全局综合日志回调
        global-log-callback: com.dingwen.treasure.log.method.callback.DefaultMeLogCallback
        # 全局参数日志级别
        global-param-log-level: debug
        # 全局参数日志代码定位
        global-param-log-position: unknown
        # 全局参数日志格式化
        global-param-log-formatter: com.dingwen.treasure.log.method.format.DefaultMeParamLogFormatter
        # 全局参数日志回调
        global-param-log-callback: com.dingwen.treasure.log.method.callback.DefaultMeLogCallback
        # 全局结果日志级别
        global-result-log-level: debug
        # 全局结果日志代码定位
        global-result-log-position: unknown
        # 全局结果日志格式化
        global-result-log-formatter: com.dingwen.treasure.log.method.format.DefaultMeResultLogFormatter
        # 全局结果日志回调
        global-result-log-callback: com.dingwen.treasure.log.method.callback.DefaultMeLogCallback
        # 全局异常日志回调
        global-throwing-log-callback: com.dingwen.treasure.log.method.callback.DefaultMeLogCallback
代码片段
    /**
     * 测试方法日志
     *
     * @param name 名字
     * @return {@link ResultVO}<{@link String}>
     */
    @GetMapping("/method-test")
    @ApiOperation(value = "测试方法日志")
    @ApiImplicitParam(name = "name", value = "名称", dataTypeClass = String.class)
    @MeLog(value = "方法日志测试业务")
    public ResultVO<String> testMeLog(@RequestParam String name) {
        return success();
    }


    /**
     * 测试操作日志
     *
     * @param name 名称
     * @return {@link ResultVO}<{@link String}>
     */
    @GetMapping("/operate-test")
    @ApiOperation(value = "测试操作日志")
    @ApiImplicitParam(name = "name", value = "名称", dataTypeClass = String.class)
    @OperateLogAnnotation(module = "场景", desc = "test")
    public ResultVO<String> testOperateLog(@RequestParam String name) {
        return success();
    }
    

概览

日志文件查看启动器

基于Websocket、SpringBoot2.x、layui实现的可配置的Web版日志查看器

核心功能

  • 可选基础目录生成文件树
  • 日志文件编码配置化
  • 追加新日志自动滚动

优化点

  • 日志查看页增加文件下载
  • 文件树按照创建时间降序
  • 大量的日志输出导致内存飙升
  • 大量的客户端链接导致CPU飙高
  • UI美化
  • 异常提示完善

使用

引入依赖
        <!--日志查看场景-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>logv-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableLogv注解以开启日志查看场景功能

@EnableLogv

配置接口文档放行、线程池、监控项

dingwen:
  treasure:
    # webplus
    webplus:
      debug: true
      handlers:
        - handler: "doc.html"
          locations: "classpath:/META-INF/resources/"
        - handler: "swagger-ui.html"
          locations: "classpath:/META-INF/resources/"
        - handler: "/webjars/**"
          locations: "classpath:/META-INF/resources/webjars/"
    # async
    async:
      logPrint: true
      pool:
        - core: 8
          max: 16
          keepAliveTime: 60
          queueCapacity: 2000
          poolNamePrefix: treasure-async-
          poolBeanName: logvExecutor
          poolPolicy: CallerRunsPolicy
# 监控项
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always
    shutdown:
      enabled: true

访问地址

默认启动端口: 2023

概览

高效的通用枚举处理启动器

从前端到服务乃至数据库到枚举解决方案,零代码,开箱即用 已加本地缓存,速度杆杆滴

主要功能

  • 统一枚举实现
  • 统一枚举响应
  • 统一枚举MVC转换
  • 统一枚举序列化
  • 统一枚举反序列化
  • 统一枚举接口实现
  • 获取所有枚举时支持分页
  • 统一异常处理
  • 简单枚举大道至简

内置接口

  • 获取服务端所有枚举 /common/enums
  • 分页查询枚举 /common/enums/page
  • 指定枚举类全路径查询执行枚举 /common/enums/{enumClassName}

使用

枚举持久化依赖各自持久层实现。 MybatisPlus 3.5.2后版本只需要在实体类中存储数据库值的字段标注 @EnumValue即可

引入依赖

        <!--枚举场景-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>enums-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

开启功能

在启动类上添加@EnableEnums注解以开启枚举功能

创建自己的枚举

/**
 *  逻辑删除
 *  @author dingwen
 *  @since 2022/6/14
 */
@Getter
@AllArgsConstructor
public enum LogicDelete implements IBaseEnum<Integer> {
    /**
     * 已删除
     */
    DELETED(0, "删除"),
    /**
     *存在
     */
    EXISTED(1, "存在");
    /**
     * 状态值
     */
    @EnumValue
    private final Integer code;

    /**
     * 状态描述
     */
    private final String desc;

}

配置项

dingwen:
  treasure:
    # enums
    enums:
      # 枚举类扫描包
      packagepath: "com.dingwen"
      # 枚举类所在类路径
      classpath: "/**/*.class"

注意点

IBaseEnum中已提供枚举比较方法以及转换方法可直接使用

概览

更高效的获取枚举

以空间换时间的方式实现.

枚举场景初始化时会将所有枚举缓存到map,使用时再通过key获取即可,省去了循环查找的过程.在枚举数量较多的场景下效率较高.

大道至简的枚举

package com.dingwen.treasure.scene.enums;

import com.dingwen.treasure.enums.core.IBaseEnum;

/**
 * 配置环境简单枚举
 *
 * @author dingwen
 * @since 2024/1/18 15:22
 */
public enum ProfileSimpleEnum implements IBaseEnum<String> {
    /**
     * 生产环境
     */
    PROD,
    /**
     * 开发环境
     */
    DEV,
    /**
     * 测试环境
     */
    TEST
}

IBaseEnum核心方法

  • eq(T code): 判断枚举值是否相等
  • from(Class<E> eClass, Object code): 通过枚举code枚举转换
  • fastFrom(Class<E> eClass, Object code): 更高效的通过枚举code枚举转换
  • fastDescFrom(Class<E> eClass, Object code): 更高效的通过枚举code枚举转换获取描述信息

数据库文档生成启动器

基于Screw、Freemarker实现的支持word,markdown,html文件生成的数据库文档生器

核心功能

  • 包含可配置的详细注释生成
  • 表名称,前缀,后缀等细粒度可配置化
  • 文档名称,标题可配置化
  • 同时支持work,markdown,html版本数据库设计文档

使用

引入依赖
        <!--数据库文档生成场景启动器-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>screw-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableScrew注解以开启数据库表文档生成功能

@EnableScrew

配置文档参数

dingwen:
  treasure:
  # 数据库文档生成
  screw:
  # 数据源连接地址
  jdbcUrl: ${spring.datasource.dynamic.datasource.master.url}
  # 数据源用户名
  username: ${spring.datasource.dynamic.datasource.master.username}
  # 数据源连接密码
  password: ${spring.datasource.dynamic.datasource.master.password}
  # 驱动类名称
  driverClassName: ${spring.datasource.dynamic.datasource.master.driver-class-name}
  # 是否读取表注释信息
  tableRemark: true
  # 文档版本号
  docVersion: 1.0.0
  # 文件输出目录
  fileOutputDir: @project.name@/src/main/resources/static

访问地址

概览

MongoDB场景启动器

基于MongoTemplate分装的类似MybatisPLuslambda形式的增删改查API

核心功能

  • 复杂条件灵活构造查询
  • 分页查询
  • 模糊查询
  • 排序
  • 集合查询
  • 新增
  • 修改
  • 删除

使用

引入依赖

<!--mongo场景启动器-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>mongo-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableScrew注解以开启mongo场景功能

@EnableMongo
编写实体类

/**
 * 请求日志
 *
 * @author dingwen
 * @since 2023/7/24 13:12
 */
@ApiModel(value = "RequestLog", description = "请求日志实体")
@Document("tre_c_request_log")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class RequestLog extends BaseMongoEntity {

    @ApiModelProperty(value = "请求日志id")
    @Id
    private String reLogId;


    @ApiModelProperty(value = "请求时间")
    @Field("reTime")
    private LocalDateTime reTime;

    @ApiModelProperty(value = "请求IP")
    @Field("reIp")
    @Indexed
    private String reIp;

    @ApiModelProperty(value = "IP属地")
    @Field("reAddress")
    private String reAddress;

    @ApiModelProperty(value = "耗时")
    @Field("consumeTime")
    private Long consumeTime;

    @ApiModelProperty(value = "请求体信息")
    @Field("resBody")
    private String resBody;

    @ApiModelProperty(value = "响应体信息")
    @Field("respBody")
    private String respBody;

    @ApiModelProperty(value = "请求地址")
    @Field("reUrl")
    @Indexed
    private String reUrl;

    @ApiModelProperty(value = "请求头信息")
    @Field("reqHeaders")
    private String reqHeaders;
}
编写接口
/**
 * 请求日志服务
 *
 * @author dingwen
 * @since 2023/7/24 13:36
 */
public interface IRequestLogService extends MongoService<RequestLog> {
}
编写实现类

/**
 * 请求日志服务
 *
 * @author dingwen
 * @since 2023/7/24 13:38
 */
@Service
public class RequestLogServiceImpl extends MongoServiceImpl<RequestLog> implements IRequestLogService {
}
调用

/**
 * MongoAPI
 *
 * @author dingwen
 * @since 2023/7/24 13:42
 */
@Api(tags = "MongoDB API")
@RestController
@Slf4j
@RequestMapping("common/mongo")
public class MongoController implements BaseViewController {

    @Resource
    private IRequestLogService requestLogService;


    /**
     * 请求日志列表
     *
     */
    @ApiOperation(value = "请求日志列表")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "reIp", value = "请求IP", dataTypeClass = String.class),
            @ApiImplicitParam(name = "reAddress", value = "IP属地", dataTypeClass = String.class),
            @ApiImplicitParam(name = "reUrl", value = "请求地址", dataTypeClass = String.class)
    })
    @GetMapping("request-logs")
    public ResultVO<PageVO<RequestLog>> getRequestLogPage(@RequestParam(required = false) String reIp,
                                                          @RequestParam(required = false) String reAddress,
                                                          @RequestParam(required = false) String reUrl) {
        LambdaQueryWrapper<RequestLog> query = Wrappers.<RequestLog>lambdaQuery()
                                                       .like(StrUtil.isNotBlank(reIp), RequestLog::getReIp, reIp)
                                                       .like(StrUtil.isNotBlank(reAddress), RequestLog::getReAddress, reAddress)
                                                       .like(StrUtil.isNotBlank(reUrl), RequestLog::getReUrl, reUrl)
                                                       .orderByDesc(RequestLog::getCreateTime);

        Page<RequestLog> page = requestLogService.page(query, PageUtils.getPageNum(), PageUtils.getPageSize());
        return page(page.getRecords(), Convert.toInt(page.getTotal()));
    }
}

支持的条件类型

  • eq: 等于
  • ne: 不等于
  • le: 小于等于
  • lt: 小于
  • ge: 大于等于
  • gt: 大于
  • bw: 在...之间
  • in: 包含
  • nin: 不包含
  • like: 全模糊查询
  • left_like: 左模糊查询
  • right_like: 右模糊查询

概览

xxl-job定时人场景启动器

基于xxl-jobv2.4.0版本的执行器封装,实现配置化、插件化使用

核心功能

参考官网 xxl-job

使用

引入依赖
<!--xxl-job场景启动器-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>xxl-job-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableXxlJob注解以开启xxl-job定时任务场景功能

@EnableXxlJob

配置xxl-job参数

dingwen:
  treasure:
    #xxl-job定时任务场景
    xxljob:
      # 执行器开关
      enabled: true
      adminAddresses: http://127.0.0.1:8003
      #调度中心应用名
      adminAppName: treasure-xxl-job-admin
      # 执行器通讯TOKEN
      accessToken: xxl-job-access-token
      # 执行器配置
      executor:
        appName: scene
        #执行器日志文件保存天数:大于3生效
        logRetentionDays: 10
        # 执行器运行日志文件存储磁盘路径 【注意不要和项目本身log路径冲突】
        logPath: logs/xxljob
        # 执行器端口号
        port: 9999

概览

本地缓存启动器

基于caffeine实现的配置化的本地缓存

核心功能

  • 配置化实现,无需反锁的整合配置
  • 基于Spring cache实现
    • @Cacheable: 将方法的结果缓存起来,下一次方法执行参数相同时,将不执行方法,返回缓存中的结果
    • @CacheEvict: 移除指定缓存
    • @CachePut: 更新缓存
    • @Caching: 可以指定相同类型的多个缓存注解,例如根据不同的条件
    • @CacheConfig: 类级别注解,可以设置一些共通的配置,@CacheConfig(cacheNames=""), 代表该类下的方法均使用这个cacheNames
  • 提供丰富的API,可供前端页面展示
    • 获取所有的缓存
    • 获取所有的缓存项
    • 获取缓存详情
    • 清除缓存
    • 缓存数据下载
  • 缓存工具类CacheHelper
  • 自定义缓存VisualCaffeineCache

使用

引入依赖
        <!--caffeine场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>caffeine-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableCaffeine注解以开启caffeine本地缓存场景功能

@EnableCaffeine
缓存配置项
dingwen:
  treasure:
    # caffeine
    caffeine:
      caches:
      	  # 缓存Spring Bean名称
        - name: testCache
          # 有效时间,单位秒
          invalidTime: 60
          # 最大缓存数量
          maximumSize: 100
        - name: fileCache
          invalidTime: 60
          maximumSize: 1000
使用
package com.dingwen.treasure.scene.controller.caffeine;

/**
 * Caffeine
 *
 * @author dingwen
 * @since 2023/5/22 16:20
 */
@Api(tags = "本地缓存API")
@RestController
@Slf4j
@RequestMapping("common/caffeine")
@CacheConfig(cacheManager = "caffeineCacheManager")
public class CaffeineController implements BaseViewController {

    @Resource(name = "testCache")
    @Lazy
    private CaffeineCache testCache;


    /**
     * 缓存测试
     *
     * @return {@link ResultVO}<{@link String}>
     */
    @ApiOperation("本地缓存测试")
    @Cacheable(cacheNames = "testCache")
    @GetMapping("/test")
    public ResultVO<String> cache() {
        testCache.putIfAbsent("test", "test");
        String test = testCache.get("test", String.class);
        log.info("test:{}", test);
        return success(test);
    }

通用缓存工具类CacheHelper

封装同一的现查询本地缓存,若存在则返回,不存在则查询数据库返回且同时加入本地缓存

package com.dingwen.treasure.caffeine.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.cache.Cache;
import org.springframework.cache.caffeine.CaffeineCache;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;

/**
 * 缓存工具类
 *
 * @author dingwen
 * @since 2023/5/30 16:01
 */
public class CacheHelper {

    /**
     * 实体初始化
     */
    private static final CacheHelper CACHEHELPER = new CacheHelper();

    /**
     * 构造器私有
     */
    private CacheHelper() {
    }


    /**
     * 获取实例
     *
     * @return {@link CacheHelper}
     */
    public static CacheHelper getInstance() {
        return CACHEHELPER;
    }

    /**
     * 缓存Map
     *
     * @param cache 缓存
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    public <T> Map<String, T> cacheToMap(Cache cache) {
        Object obj = cache.getNativeCache();
        Map<String, Object> map = new HashMap<>(16);
        Field[] fields = obj.getClass().getDeclaredFields();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(obj));
            }
        } catch (Exception e) {
            return null;
        }
        return (Map<String, T>) map.get("cache");
    }


    /**
     * 本地缓存通用逻辑处理
     *
     * @param key           缓存key id
     * @param keyPrefix     缓存key 前缀
     * @param keySuffix     缓存key 后缀
     * @param clazz         缓存值字节码
     * @param func          实际执行函数
     * @param caffeineCache 缓存池
     * @return {@link V}
     */
    public <K, V> V cacheAndGet(K key, String keyPrefix, String keySuffix,
                                Class<V> clazz, Function<K, V> func,
                                CaffeineCache caffeineCache) {
        if (Objects.isNull(key)
                || Objects.isNull(keyPrefix)
                || Objects.isNull(keySuffix)
                || Objects.isNull(clazz)
                || Objects.isNull(caffeineCache)) {
            if (Objects.isNull(func)) {
                return null;
            }
            return func.apply(key);
        }
        String cacheKey = StrUtil.format("{}_{}_{}", keyPrefix, key, keySuffix);
        V vCache = caffeineCache.get(cacheKey, clazz);
        if (Objects.nonNull(vCache)) {
            return vCache;
        }
        V v = func.apply(key);
        caffeineCache.put(cacheKey, v);
        return v;
    }

    /**
     * caches and gets
     *
     * @param keys          缓存keys ids
     * @param keyPrefix     缓存key 前缀
     * @param keySuffix     缓存key 后缀
     * @param clazz         缓存值字节码
     * @param func          实际执行函数
     * @param caffeineCache 缓存池
     * @return {@link V}
     */
    public <K, V> List<V> cachesAndGets(List<K> keys, String keyPrefix, String keySuffix,
                                        Class<V> clazz, Function<List<K>, Map<K, V>> func,
                                        CaffeineCache caffeineCache) {
        if (CollUtil.isEmpty(keys)
                || Objects.isNull(keyPrefix)
                || Objects.isNull(keySuffix)
                || Objects.isNull(clazz)
                || Objects.isNull(caffeineCache)) {
            if (Objects.isNull(func)) {
                return null;
            }
            return new ArrayList<>(func.apply(keys).values());
        }
        // 缓存的结果
        List<V> cacheValues = new ArrayList<>(keys.size());
        // 没有在缓存中的key
        List<K> noCacheKeys = new LinkedList<>();
        for (K key : keys) {
            String cacheKey = buildCacheKey(key, keyPrefix, keySuffix);
            V vCache = caffeineCache.get(cacheKey, clazz);
            if (Objects.nonNull(vCache)) {
                cacheValues.add(vCache);
            } else {
                // 缓存未命中
                noCacheKeys.add(key);
            }
        }
        // 查询参数完全命中,直接返回
        if (CollUtil.isNotEmpty(noCacheKeys)) {
            return cacheValues;
        }
        // 未命中缓存的数据
        Map<K, V> noCacheValues = func.apply(noCacheKeys);
        if (CollUtil.isEmpty(noCacheValues)) {
            return cacheValues;
        }
        // 添加新查询到的数据到缓存中
        noCacheValues.forEach((K key, V value) -> {
            String cacheKey = buildCacheKey(key, keyPrefix, keySuffix);
            caffeineCache.put(cacheKey, value);
            cacheValues.add(value);
        });
        return cacheValues;

    }

    /**
     * 构建缓存key
     *
     * @param key       key
     * @param keyPrefix key prefix
     * @param keySuffix key suffix
     * @return {@link String}
     */
    public <K> String buildCacheKey(K key, String keyPrefix, String keySuffix) {
        return StrUtil.format("{}_{}_{}", keyPrefix, key, keySuffix);
    }
}

概览

自定义缓存VisualCaffeineCache

显示更丰富的内容

Redis启动器

基于RedisTemplate封装的启动器

核心功能

  • 常用API
    • 对象缓存
    • 过期时间
    • 缓存是否存在
    • 删除缓存
    • List,Set,Map缓存操作
    • 发布订阅
    • 自增自减
    • void removeZset(final String key, Double scoreStart, Double scoreEnd)
    • <T> Set<T> getZsetCacheZet(final String key,Double scoreStart,Double scoreEnd)
  • 常用功能
    • 通用逻辑轻松缓存
    • 限流
    • 防重
    • 分布式锁
  • 监控信息
    • Redis信息
    • 缓存信息
    • 缓存删除
  • 缓存预热统一封装 RedisCache
  • 分布式锁组件 RedisShareLockComponent
  • 基于zset实现延迟队列
  • CAS基于lua脚本实现
  • 延迟双删DelayRemoves

使用

引入依赖
        <!--redis场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>redis-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableRedis注解以开启redis场景功能

@EnableRedis
配置项
dingwen:
  treasure:
    # redis
    redis:
      easy:
        # 开启easy cache
        cache: true
      rate:
        # 开启限流
        limiter: true
      re:
        # 开启防重复提交
        submit: true
      # 缓存项配置  
      caches:
        - keyPrefix: "re_submit:"
          remark: "防止重复提交"
        - keyPrefix: "common_lock:"
          remark: "通用锁"
使用案例
package com.dingwen.treasure.scene.controller.redis;

/**
 * Redis场景API
 *
 * @author dingwen
 * @since 2023/5/22 11:11
 */
@Api(tags = "Redis缓存API")
@RestController
@Slf4j
@RequestMapping("common/redis")
public class RedisController implements BaseViewController {

    /**
     * redis 服务
     */
    @Resource
    private RedisService redisService;

    @Resource
    private RedisProperties redisProperties;


    /**
     * 得到Redis信息
     *
     * @return {@link ResultVO}
     */
    @ApiOperation("获取Redis信息")
    @GetMapping("/info")
    public ResultVO getInfo() {
        return success(redisService.getInfo());
    }

    /**
     * 获取缓存项
     *
     * @return {@link ResultVO}<{@link List}<{@link CacheVO}>>
     */
    @ApiOperation(value = "获取缓存项")
    @GetMapping
    public ResultVO<List<CacheVO>> getCaches() {
        List<CacheBO> caches = redisProperties.getCaches();
        List<CacheVO> cacheVOS = BeanCopyUtils.copyList(caches, CacheVO.class);
        return success(cacheVOS);
    }


    /**
     * 获取指定前缀的所有key
     *
     * @param keyPrefix 关键前缀
     * @return {@link ResultVO}<{@link Collection}<{@link String}>>
     */
    @ApiOperation(value = "获取指定前缀的所有key")
    @ApiImplicitParam(name = "keyPrefix", value = "key前缀", dataTypeClass = String.class)
    @GetMapping("/{keyPrefix}")
    public ResultVO<Collection<String>> getKeys(@PathVariable("keyPrefix") String keyPrefix) {
        return success(redisService.getKeys(keyPrefix + RedisConstant.KEY_ALL));
    }


    /**
     * 获取缓存值
     *
     * @param keyPrefix 关键前缀
     * @param key       关键
     * @return {@link ResultVO}<{@link CacheVO}>
     */
    @ApiOperation(value = "获取指定key的缓存值")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "keyPrefix", value = "key前缀", dataTypeClass = String.class),
            @ApiImplicitParam(name = "key", value = "key", dataTypeClass = String.class)
    })
    @GetMapping("/{keyPrefix}/{key}")
    public ResultVO<CacheVO> getCache(@PathVariable("keyPrefix") String keyPrefix, @PathVariable("key") String key) {
        String cacheValue = redisService.getCacheObject(key);
        CacheVO cacheVO = CacheVO.builder()
                                 .keyPrefix(keyPrefix)
                                 .cacheKey(key)
                                 .cacheValue(cacheValue)
                                 .build();
        return success(cacheVO);
    }


    /**
     * 清除指定key前缀的所有缓存
     *
     * @param keyPrefix 关键前缀
     * @return {@link ResultVO}<{@link String}>
     */
    @ApiOperation(value = "清除指定key前缀的所有缓存")
    @ApiImplicitParam(name = "keyPrefix", value = "key前缀", dataTypeClass = String.class)
    @PutMapping("/{keyPrefix}")
    public ResultVO<String> cleanCaches(@PathVariable("keyPrefix") String keyPrefix) {
        redisService.removeKeys(keyPrefix + RedisConstant.KEY_ALL);
        return success();
    }

    /**
     * 清除指定key的缓存
     *
     * @param key 关键
     * @return {@link ResultVO}<{@link String}>
     */
    @ApiOperation(value = "清除指定key的缓存")
    @ApiImplicitParam(name = "key", value = "key", dataTypeClass = String.class)
    @DeleteMapping("/{key}")
    public ResultVO<String> cleanCache(@PathVariable("key") String key) {
        redisService.deleteObject(key);
        return success();
    }

    /**
     * 清洗所有缓存
     *
     * @return {@link ResultVO}<{@link String}>
     */
    @ApiOperation(value = "清除所有缓存")
    @DeleteMapping
    public ResultVO<String> cleanAllCache() {
        redisService.removeKeys(RedisConstant.KEY_ALL);
        return success();
    }


    /**
     * 轻松缓存
     *
     * @param easyCacheSubmitVO 轻松缓存提交内容
     * @return {@link ResultVO}<{@link String}>
     */
    @PostMapping("/easy-cache")
    @ApiOperation("轻松缓存测试")
    @EasyCache(keyParams = {
            "#easyCacheSubmitVO.getId()",
            "#easyCacheSubmitVO.getName()"},
            time = 100,
            returnType = ResultVO.class)
    public ResultVO<EasyCacheSubmitVO> easyCache(@RequestBody EasyCacheSubmitVO easyCacheSubmitVO) {
        return success(easyCacheSubmitVO);
    }

    /**
     * 速率限制
     *
     * @return {@link ResultVO}<{@link String}>
     */
    @ApiOperation("redis限流测试")
    @GetMapping("/rate-limit")
    @RateLimiter(time = 1, count = 2)
    public ResultVO<String> rateLimit() {
        return success();
    }

    /**
     * 防止重复提交测试
     */
    @ApiOperation("防止重复提交测试")
    @GetMapping("/re-submit")
    @ReSubmit(message = "登录重复提交请求", isDeleteKey = false, time = 90)
    public void resubmit() {
    }

    /**
     * 锁 </br>
     * redisson方案;<a href="https://redisson.org">...</a>
     *
     * @return {@link ResultVO}<{@link String}>
     */
    @ApiOperation("lock测试")
    @GetMapping("/lock")
    public ResultVO<String> lock() {
        boolean ifAbsent = redisService.setIfAbsent(RedisKeyConstant.LOCK_PREFIX.concat("test"), "lock test",
                1L, TimeUnit.MINUTES);
        // 剩余时间
        long expire = redisService.getExpire(RedisKeyConstant.LOCK_PREFIX.concat("test"), TimeUnit.SECONDS);
        if (ObjectUtil.isNotNull(ifAbsent) && ifAbsent) {
            return success();
        }
        String message = StrUtil.format("频繁的操作,请{}秒后重试", expire);
        return failure(message);
    }


}

缓存预热

继承抽象类AbstractRedisCache完成自身预热逻辑,由统一预热组件进行调用

分布式锁组件 RedisShareLockComponent


/**
 * redis 分布式锁组件
 *
 * @author dingwen
 * @since 2023/12/5 14:57
 */
@Component
@Slf4j
public class RedisShareLockComponent {
    @Resource
    private RedisProperties redisProperties;
    @Resource
    private RedisService redisService;

    /**
     * 加锁å
     *
     * @param lockKey   锁key
     * @param requestId 请求id
     * @param time      时间
     * @param timeUnit  时间单位
     * @return boolean
     */
    public boolean lock(String lockKey, String requestId, Long time, TimeUnit timeUnit) {
        // 参数检查
        lockParamsCheck(lockKey, requestId, time, timeUnit);
        // 当前时间
        long currentTime = System.currentTimeMillis();
        // 超时时间
        long outTime = currentTime + redisProperties.getShareLockTimeOut();
        boolean lockResult = false;

        // 加锁
        while (currentTime < outTime) {
            lockResult = redisService.setIfAbsent(lockKey, requestId, time, timeUnit);
            if (lockResult) {
                log.info("[Redis模块]\t[分布式锁],加锁成功:lockKey:{},requestId:{},time:{},timeUnit:{}",
                        lockKey, requestId, time, timeUnit);
                return true;
            }
            ThreadUtil.sleep(100);
            currentTime = System.currentTimeMillis();
        }
        return lockResult;
    }

    /**
     * 加锁参数检查
     *
     * @param lockKey   lock key
     * @param requestId request id
     * @param time      time
     * @param timeUnit  time unit
     */
    private void lockParamsCheck(String lockKey, String requestId, Long time, TimeUnit timeUnit) {
        if (StrUtil.isBlank(lockKey) || StrUtil.isBlank(requestId) || time <= 0 || Objects.isNull(timeUnit)) {
            log.error("[Redis模块]\t[分布式锁],加锁参数错误:lockKey:{},requestId:{},time:{},timeUnit:{}",
                    lockKey, requestId, time, timeUnit);
            throw new ShareLockException("加锁参数异常");
        }
    }


    /**
     * 解锁
     *
     * @param lockKey   锁keu
     * @param requestId 请求id
     * @return boolean
     */
    public boolean unLock(String lockKey, String requestId) {
        if (StrUtil.isBlank(lockKey) || StrUtil.isBlank(requestId)) {
            throw new ShareLockException("解锁参数异常");
        }
        try {
            String cacheRequestId = redisService.getCacheObject(lockKey);
            if (requestId.equals(cacheRequestId)) {
                redisService.deleteObject(cacheRequestId);
                return true;
            }
        } catch (Exception e) {
            log.error("[Redis模块]\t[分布式锁],解锁失败。lockKey:{},requestId:{}", lockKey, requestId, e);
        }
        return false;
    }


    /**
     * 尝试加锁方法
     *
     * @param lockKey   锁key
     * @param requestId 请求id
     * @param time      时间
     * @param timeUnit  时间单位
     * @return boolean
     */
    public boolean tryLock(String lockKey, String requestId, Long time, TimeUnit timeUnit) {
        // 参数检查
        lockParamsCheck(lockKey, requestId, time, timeUnit);
        return redisService.setIfAbsent(lockKey, requestId, time, timeUnit);
    }
}

基于zset实现延迟队列 DelayTaskComponent

当我们有期望一个任务再某一个时间点再去执行,此时业务相对比较简单,不想引入Mq组件时可以考虑实用Redis实现延迟队列。

基于redis的zset实现,zset天生具有score的特性。可以根据score放入,而且可以通过range进行排序获取,以及删除指定的值。从业务上,我们可以再新增任务的时候放入,再通过定时任务进行拉取,要注意的一点就是拉取的时候要有分布式锁,保证不进行重复拉取就可以了。

CAS基于lua脚本实现

延迟双删 DelayRemoves

使用延时双删,保证缓存数据库一致性问题

概览

电子邮件启动器

基于sun.mail实现的可配置的电子邮件使用场景

使用

引入依赖
        <!--邮件场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>email-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableEmail注解以开启电子邮件场景功能

@EnableEmail

配置项

dingwen:
  treasure:
    # 电子邮件
    email:
      enabled: true
      # SMTP服务器域名
      host: smtp.163.com
      port: 465
      # 是否需要用户名密码验证
      auth: true
      # 发送方,遵循RFC-822标准
      from: dingwen0314@163.com
      # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
      user: dingwen0314@163.com
      # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
      pass: TODO
      # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
      starttlsEnable: true
      # 使用SSL安全连接
      sslEnable: true
      # SMTP超时时长,单位毫秒,缺省值不超时
      timeout: 0
      # Socket连接超时值,单位毫秒,缺省值不超时
      connectionTimeout: 0

使用

    /**
     * 发送电子邮件
     */
    @Test
    public void sendEmail() {
        String messageId = MailUtils.sendText("1981723769@qq.com", "test", "测试邮件内容");
        System.out.println(messageId);
    }

API概览

进度条启动器

基于Redis实现的多线程任务调度的进度条任务场景

核心功能

  • 提供模板类,封装通用逻辑
  • 提供丰富API接口
    • 任务提交
    • 查询进度

使用

引入依赖
        <!--进度条场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>bar-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableBar注解以开启进度条场景功能

@EnableBar

概览

PipeLine启动器

工厂模式 + 策略模式 + 门面模式 + 模板方法 实现的定制化配置化规则引擎

核心功能

  • 抽象上下文,实现通用逻辑PipelineContext
  • 异步执行管道支持
  • 外部配置化的管道流
  • 注解式的管道前后进行过滤操作
  • 详尽的日志追踪
  • 提供测试案例

使用

引入依赖
        <!--pipeline场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>pipeline-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnablePipeline注解以开启pipeLine场景功能

@EnablePipeline

配置项

dingwen:
  treasure:
    # pipeline
    pipeline:
      routes:
       # 管道上下文名称 key
        testContext:
          # 第一个执行的管道
          - onePipeLine
          # 第二个执行的管道
          - twoPipeLine
// 配置管道过滤器
@PipeFilters(filters = {
        @PipeFilter(beanName = "beforeOneFilter",exePoint = PipelineFilterExePoint.ALL),
        @PipeFilter(beanName = "beforeTwoFilter",exePoint = PipelineFilterExePoint.BEFORE),
})

核心类

  • PipelineContext管道上下文
  • Pipeline管道接口
  • PipelineFilter管道过滤器接口
  • @PipeFilter过滤器注解
  • PipelineFactory管道执行工厂
  • PipelineExecutor管道执行器

概览

OSS启动器

基于亚马逊S3封装的支持阿里云,Minio等多种存储方式的对象存储通用业务场景启动器

使用

引入依赖
        <!--对象存储场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>oss-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableOss注解以开启oss场景功能

@EnableOss

配置项

dingwen:
  treasure:
    # oss
    oss:
      # 对象存储服务的URL
      endpoint: http://127.0.0.1:9000
      # key
      access-key: admin
      # 密钥
      secret-key: 1234567890
      # 路径风格
      path-style-access: true
      # 最大线程数
      max-connections: 100
      # 区域
      region: 

OssTemplate操作API

mybatis-plus启动器

基于MybatisPlus的二次封装

核心功能

  • 统一实体

  • 统一查询对象

  • 场景异常封装处理

  • 逻辑删除

  • 枚举处理

  • 通用字段填充

    • 创建人
    • 创建时间
    • 修改人
    • 修改时间
  • 多租户

  • 数据权限

  • 通用选项查询组件

  • 完整SQL日志打印

  • SQL执行耗时

  • 通用字符串字段长度校验组件

  • 同一数据唯一性校验组件

  • 获取表信息工具

  • 字段比较工具

  • 通用查询组件 QueryUtils [支持数据权限]

    • FULL: 全模糊查询
    • EQ: 全等于查询
    • LEFT: 全以什么结尾模糊查询查询
    • RIGHT: 全以什么开头模糊查询查询
    • IN: 在集合查询
    • NOT_IN: 不在集合查询
    • RANGE: 范围内查询
    • DESC: 降序
    • ASC: 升序
  • 数据冗余解决方案IRedundancyMaintainService

使用

引入依赖

<!--mybatisplus场景启动器-->
<dependency>
    <groupId>com.dingwen</groupId>
    <artifactId>mybatisplus-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
启动项配置

在启动类上添加@EnableMybatisPlus注解以开启mybatisplus场景功能

@EnableMybatisPlus

选项查询通用组件

当你需要查询某个数据作为下拉选项时可使用此组件,支持四级选项、数据权限、中间表关联。已实现本地缓存优化。

三级关联查询
@Test
    void testOpForLevel3() {
        List<OpParam> opParams = new ArrayList<>();

        OpParam unitOpParam = OpParam
                .builder()
                .labelField("unit_name")
                .valueField("unit_id")
                .tableName("hom_u_unit")
                .build();

        OpParam deptOpParam = OpParam
                .builder()
                .labelField("t1.dept_name")
                .valueField("t1.dept_id")
                .tableName("sys_dept t1")
                .leftJoinField(", t2.dept_id")
                .leftJoinSql("left join hom_u_unit_lnk_dept t2 on t1.dept_id = t2.dept_id")
                .parentField("t2.unit_id")
                .build();

        OpParam usertOpParam = OpParam
                .builder()
                .labelField("user_name")
                .valueField("user_id")
                .tableName("sys_user")
                .parentField("dept_id")
                .build();
        opParams.add(unitOpParam);
        opParams.add(deptOpParam);
        opParams.add(usertOpParam);
        List<Option> options = OptionHelper.get(opParams);
        log.info(JSONUtil.toJsonPrettyStr(options));
    }
两级父子查询

    @Test
    void testOpForLevel2() {
        List<OpParam> opParams = new ArrayList<>();

        OpParam deptOpParam = OpParam
                .builder()
                .labelField("dept_name")
                .valueField("dept_id")
                .tableName("sys_dept")
                .build();

        OpParam usertOpParam = OpParam
                .builder()
                .labelField("user_name")
                .valueField("user_id")
                .tableName("sys_user")
                .parentField("dept_id")
                .build();
        opParams.add(deptOpParam);
        opParams.add(usertOpParam);
        List<Option> options = OptionHelper.get(opParams);
        log.info(JSONUtil.toJsonPrettyStr(options));
    }
一级简单查询
    @Test
    void testOpForLevel1() {
        List<OpParam> opParams = new ArrayList<>();


        OpParam deptOpParam = OpParam
                .builder()
                .labelField("dept_name")
                .valueField("dept_id")
                .tableName("sys_dept")
                .build();

        opParams.add(deptOpParam);
        List<Option> options = OptionHelper.get(opParams);
        log.info(JSONUtil.toJsonPrettyStr(options));
    }

基础Controller CRUD


package com.dingwen.web.mybatisplus;
/**
 * BaseCrudController MybatisPlus实现 </br>
 * <p> P: 持久化对象</p>
 *
 * @author dingwen
 * @since 2023/3/19 15:55
 */
public abstract class AbstractBaseControllerMybatisPlusImpl<P extends BaseEntity>
        implements BaseCrudController<P>, BaseViewController {

    /**
     * 服务
     */
    @Autowired
    private IService<P> 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));
    }


    /**
     * 分页查询 </br>
     * 分页参数由分页工具自动从Servlet上下文中获取
     *
     * @param p 查询实体
     * @return {@link ResultVO}<{@link PageVO}
     */
    @ApiOperation("分页查询,返回PageVO")
    @ApiOperationSupport(author = "dingwen")
    @GetMapping("/default/page")
    @Override
    public ResultVO<PageVO<P>> 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<List<P>> find(@PathVariable List<Serializable> ids) {
        return success(iService.listByIds(ids));
    }

    /**
     * 获取数据表总记录条数
     *
     * @return {@link ResultVO}<{@link Long}>
     */
    @ApiOperation("获取数据表总记录条数,返回统计数量")
    @ApiOperationSupport(author = "dingwen")
    @GetMapping("/default/count")
    @Override
    public ResultVO<Long> count() {
        return success(iService.count());
    }

    /**
     * 通过唯一键查询是否存在 </br>
     * <ol>
     *     <li>true: 存在</li>
     *     <li>false: 不存在</li>
     * </ol>
     *
     * @param id 唯一键
     * @return {@link ResultVO}
     */
    @ApiOperation("通过唯一键查询是否存在,返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @GetMapping("/default/exists/{id}")
    @Override
    public ResultVO<Boolean> 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<Boolean> 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<Boolean> create(@RequestBody List<P> ps) {
        return genResult(iService.saveBatch(ps));
    }

    /**
     * 删除单条记录
     *
     * @param id 唯一键
     * @return {@link ResultVO}
     */
    @ApiOperation("删除单条记录,返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @DeleteMapping("/default/{id}")
    @Override
    public ResultVO<Boolean> 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<Boolean> remove(@PathVariable List<Serializable> 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<Boolean> saveOrUpdate(P p) {
        return genResult(iService.saveOrUpdate(p));
    }
}

数据权限

要求: 足够灵活,足够高效,足够优雅

TODO

完整SQL日志及耗时日志

基于拦截器实现,核心类: SQL拼接拦截器MybatisPlusSqlLogInterceptor,SQL耗时拦截器MybatisSqlStatementInterceptor

添加以下配置启动:

    # mybatisplus 场景启动器
    mybatisplus:
      # 是否开启多租户插件
      tenant: false
      # 是否开启SQL拦截器日志
      sqlLog: true
      # sql耗时统计(毫秒) 低档
      consumeLow: 999
      # sql耗时统计(毫秒) 中档
      consumeMiddle: 5000
      # sql耗时统计(毫秒) 高档
      consumeHeight: 10000

druid连接加密

使用DruidEncryptUtils生产密码,公钥,私钥,再增加对应配置即可

多数据源示例配置

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 已经很方便了,但是对于字符串类型长度的校验,若数据表发生了改变需要同步维护实体中的字段校验信息。通用字符串字段长度校验组件就是为了解决这一问题,通过查询数据表的方式来进行字符串的长度校验,提供丰富的日志,确保不会出现因为字段过长倒是的数据库操作错误

统一数据唯一性校验组件

当你需要保证表中某一字段唯一而又不想依赖数据库的唯一性约束,在数据提交的时候就完成校验。直接过滤掉这一类错误数据的数据库访问时实用此组件

> 若接口入口校验方法由父类继承而来会导致校验失效,可以重写父类方法再手动调用校验即可

表信息获取工具TableInfoUtils

通过SqlRunner获取数据库表字段定义,注释等信息及表注释等信息。目前只支持Mysql。已做本地缓存优化

  • getTableName(T ojb):获取表名称
  • getTableComment(String tableName,String database):获取表注释信息
  • getTableFieldInfos(T obj): 获取表字段信息

实体字段比较工具CompareUtils

为实现关键数据的变更历史提供支持。例如:名称:【】,描述:【】,发生了变更。由【】变更为【】。

通用查询组件 QueryUtil

查询对象继承 BaseQuery,标注对应注解


/**
 * 字典查询对象
 *
 * @author dingwen
 * @since 2023/6/8 10:04
 */
@ApiModel(value = "字典查询对象")
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class DictQuery extends BaseQuery implements Serializable {

    @ApiModelProperty(value = "字典名称")
    @QueryType(value = QueryMode.FULL)
    private String dictName;

    @ApiModelProperty(value = "字典类型")
    @QueryType(value = QueryMode.EQ)
    private String dictType;

    @ApiModelProperty(value = "字典状态")
    @QueryType(value = QueryMode.EQ)
    private DicStatus status;

    @ApiModelProperty(value = "字典值标签")
    @QueryType(value = QueryMode.EQ)
    private String dicLabel;

    @ApiModelProperty(value = "字典值父id")
    @QueryType(value = QueryMode.EQ)
    private Long dictDataParentId;

    @ApiModelProperty("字典所属模块")
    @QueryType(value = QueryMode.EQ)
    private String dictModule;

    @ApiModelProperty("创建时间")
    @QueryType(value = QueryMode.DESC)
    private Date createTime;

    private static final long serialVersionUID = -6210670520489664106L;
}

支持的查询类型


/**
 *  查询方式
 *  @author dingwen
 *  @since 2022/6/14
 */
@Getter
@AllArgsConstructor
public enum QueryMode implements IBaseEnum<String> {
    /**
     * 全模糊
     */
    FULL("FULL", "包含模糊查询"),
    /**
     * 等于
     */
    EQ("EQ", "等于"),
    /**
     * 以什么结尾模糊查询
     */
    LEFT("LEFT", "以什么结尾模糊查询"),
    /**
     * 以什么开头模糊查询
     */
    RIGHT("RIGHT", "以什么开头模糊查询"),
    /**
     * 在集合
     */
    IN("IN", "在集合查询"),
    /**
     * 不在集合
     */
    NOT_IN("NOT_IN", "不在集合查询"),

    /**
     * 范围内查询
     */
    RANGE("RANGE", "范围内查询"),
    /**
     * 降序
     */
    DESC("DESC", "降序"),
    /**
     * 升序
     */
    ASC("ASC", "升序");
    /**
     * 状态值
     */
    @EnumValue
    private final String code;

    /**
     * 状态描述
     */
    private final String desc;

}

使用

  /**
     * 字典分页查询
     *
     * @param dictQuery dict类型查询
     * @return {@link PageVO}<{@link DictVO}>
     */
    @ApiOperation("字典分页查询")
    @ApiImplicitParams(
            {
                    @ApiImplicitParam(name = "dictName", value = "字典名称", dataTypeClass = String.class),
                    @ApiImplicitParam(name = "dictType", value = "字典类型", dataTypeClass = String.class),
                    @ApiImplicitParam(name = "dictModule", value = "字典所属模块", dataTypeClass = String.class),
                    @ApiImplicitParam(name = "status", value = "字典状态", dataTypeClass = Enum.class)
            })
    @GetMapping("/pages")
    public ResultVO<PageVO<DictVO>> dictPage(@ModelAttribute DictQuery dictQuery) {
        QueryWrapper<Dict> queryWrapper = QueryUtils.buildQueryWrapper(dictQuery);
        Page<Dict> dictPage = dictManager.queryDictPage(queryWrapper);
        return page(converter.convert(dictPage,DictVO.class), dictPage.getTotal());
    }

数据冗余解决方案IRedundancyMaintainService

表设计时,为了保证性能会考虑冗余一些字段从而提升性能.但是这样带来了当冗余的原始数据更新的时候还显示旧数据的问题.本章节所述及解决这一问题.

方案一

不冗余字段,使用本地缓存+Redis缓存等方式提升性能.也可参考本项目中的translate-spring-boot-starter翻译场景的使用.

方案二

设计冗余字段,在原始数据发生修改的时候进行同步更新.更新的逻辑相对通用,使用Spring的事件机制进行抽象复用.

当冗余字段发生变更的时候发布事件


/**
 * 数据冗余维护事件
 *
 * @author dingwen
 * @since 2024/3/11 11:06
 */
@Getter
@Setter
public class RedundancyMaintainEvent extends ApplicationEvent {

    /**
     * 更新的条件字段获取方法
     */
    private SFunction<?, ?> conditionFieldFunc;

    /**
     * 更新条件值
     */
    private Object conditionVal;

    /**
     * 实际更新的字段获取方法
     */
    private SFunction<?, ?> updateFieldFunc;

    /**
     * 更新条件值
     */
    private Object updateVal;


    /**
     * 变更的实体对应的全类名称
     */
    private String updateFullClassName;

    private static final long serialVersionUID = -4069028139785256372L;

    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public RedundancyMaintainEvent(Object source) {
        super(source);
    }
}

依赖冗余数据的服务需要实现IRedundancyMaintainService服务,然后由监听器RedundancyMaintainListener去统一匹配调用对应的修改服务进行冗余数据的同步处理

BaseEntity统一实体

提供统一的通用字段

Lombok注解@FieldNameConstants : 可以通过常量访问属性

字典启动器

核心功能

  • 通用字典,字典值实现
  • 提供丰富的字典API
  • 支持多层级的,分模块的字典值
  • 分布式缓存支持
  • Redis缓存与本地缓存配合使用专注提升效率
  • Spring Retry整合翻译应用
  • 便捷查询字典工具

使用

引入依赖
  <!--通用字典场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>dic-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableDic注解以开启字典场景功能

@EnableDic

默认API

快速字典工具类DictHelper

Redis + CaffeineCache 专注提升效率

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<String> 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<String> 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
  • 注解TranslateResult形式的方法返回值自动翻译

使用

引入依赖
        <!--翻译场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>translate-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableTranslate注解以开启通用翻译场景功能

@EnableTranslate
标注注解
// 默认翻译实现,使用自定义名称新字段


    @ApiModelProperty(value = "name")
    @Translate(
            service = "defaultTranslateServiceImpl", // 翻译实现接口
            mapper = "id", // 翻译依据值
            key = "file_name", // 翻译值对应表字段
            keyExtend = "tre_c_file", // 翻译对应表名称
            param = "file_id" // 翻译依据值对应表字段名称
    )
    private String name;
// 自定义翻译实现,例: 字典翻译实现,使用已有字段名称
    @Translate(
            service = "dictTranslateServiceImpl",// 翻译实现接口
            key = "d_field_type", // 翻译值对应表字段
            mapper = "dictLabel" // 翻译依据值
    )
    @ApiModelProperty(value = "dictLabel")
    private String dictLabel;

翻译接口ITranslateService

package com.dingwen.treasure.translate.core.service;

/**
 * 翻译接口
 *
 * @author dingwen
 * @since 2023/6/9 15:39
 */
public interface ITranslateService<T, P1, P2, P3, P4> {
    /**
     * 翻译
     *
     * @param key         关键
     * @param keyExtend   关键扩展
     * @param param       参数
     * @param paramExtend 参数扩展
     * @return {@link T}
     */
    T translate(P1 key, P2 keyExtend, P3 param, P4 paramExtend);
}

注解Translate

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";

    /**
     * 映射字段 </br>
     * 默认取当前字段的值 如果设置了 {@link Translate#mapper()} 则取映射字段的值
     */
    String mapper() default "";

    /**
     * key
     */
    String key() default "";

    /**
     * key扩展
     */
    String keyExtend() default "";

    /**
     * 参数
     */
    String param() default "";

    /**
     * 参数扩展
     */
    String paramExtend() default "";


}

注解TranslateResult

在返回方式使用该注解,配合Translate可以实现自动翻译


/**
 * 系统配置管理组件
 * @author dingwen
 * @since 2024/2/1 13:19
 */
@Component
public class ConfigManagerImpl implements IConfigManager {

    @Resource
    private IConfigService configService;

    @Resource
    private Converter converter;


    @Override
    @TranslateResult
    public List<ConfigVO> getAllConfigs() {
        return converter.convert(configService.list(),ConfigVO.class);
    }
}

Quartz定时任务启动器

基于数据库悲观锁实现分布式场景下的定时任务不漏跑,不重复执行问题

核心功能

  • 支持分布式调度
  • 基于监听异步方式实现的日追踪
  • 二次封装丰富的API
  • 后续提供界面操作
  • 本地环境可灵活配置决定是否执行定时任务

使用

引入依赖
  <!--Quartz定时任务场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>quartz-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableQuartz注解以开启quartz定时任务场景功能

@EnableQuartz
继承AbstractJob实现自定义任务
/**
 *  TestJob </br>
 * <p> Quartz禁止并发执行 DisallowConcurrentExecution</p>
 *  @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缓存与本地缓存配合使用专注提升效率
  • 便捷查询配置工具

使用

引入依赖
  <!--通用系统配置场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>config-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableConfig注解以开启系统通用配置场景功能

@SpringBootApplication(scanBasePackages = "com.dingwen")

默认API

快速系统配置工具类ConfigHelper

Redis + CaffeineCache 专注提升效率

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<String> getVal(String configKey) {
        Optional<Config> configOp = getConfig(configKey);
        return configOp.map(Config::getConfigVal);

    }

    /**
     * 获取配置
     *
     * @param configKey 配置关键
     * @return {@link Config}
     */
    public static Optional<Config> 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
    • 字节数组
  • 导出动态表头
  • 合并单元格
  • 导出超链接、批注、公式
  • 表字段翻译
  • 字典翻译translate-spring-boot-starter
  • 枚举翻译
  • 自定义转换表达式翻译 ExcelExpProperty
  • 多线程导出导入优化
  • 平铺导出自动合并相同单元格 ExcelAutoMergeHandler
  • 导入校验封装+错误文件下载【OSS】
  • 自动计算宽度
  • 注解封装导出逻辑

文件启动器

一套包含前后端的一条龙的通用的文件场景,业务数据基于MybatisPlus存储,文件数据可存储与系统本地或任何一直OSS存储

核心功能

  • 存储方式
    • 系统存储
    • MiniIO
    • 阿里OSS
    • 任何一种OSS
  • 业务数据API
  • 进度条可视化
  • 断点续传
  • 分片上传
  • 图片压缩
  • 图片水印、文件水印
  • 图片缩略图
  • 文件下载,文件压缩下载
  • 文件预览,图片预览

数据归档启动器

适用场景: 一张表中有1000w的数据,但是可能其中有800w是历史数据(冷数据),我们可能在业务上已经不再使用这些数据,如果放在业务表中,可能会影响我们业务的效率,所以我们可以将其归档到另一张表中,将其变成冷数据

**流程:**数据归档的流程大概是: 1)从原数据表获取需要归档的数据;2)将这部分数据插入归档的表中;3)将元数据表中这部分数据删除

**注意点:**我们数据归档中,事务的提交应该采用手动事务提交,如果使用大事务的情况下,可能会导致事务超时等一系列的问题!还有,我们需要实现可控归档,需要达到我们可以手动控制是否归档、停止,并且还能动态配置归档范围

概览

使用

引入依赖
  <!--数据归档场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>db-backup-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableDbBackup注解以开启场景功能

@EnableDbBackup
其他配置
dingwen:
	treasure:
		db:
			backup:
				maxLoopCount: TODO // 备份最大循环次数
				 
				backUpDataRules: TODO // 备份规则:  开始归档的id;结束归档的id;一次查询的条数
实现IBackupDataService接口
/**
 * 测试用户表备份
 * @author dingwen
 * @since 2024/1/3 14:42
 */
@Service
@Slf4j
public class TreUserBackupData extends AbstractBackupData<Object, BackUpDataRule> {

    @Resource
    private DbBackupProperties dbBackupProperties;

    @Override
    public BackupDataScene getScene() {
        return BackupDataScene.TRE_USER_FORWARD;
    }

    /**
     * back up data rule
     */
    private BackUpDataRule backUpDataRule;

    @Override
    public Boolean needStop() {
        return backUpDataRule.getStopFlag();
    }

    @Override
    public BackUpDataRule getRule() {
        Map<String, BackUpDataRule> stringBackUpDataRuleMap = Optional.ofNullable(dbBackupProperties).map(DbBackupProperties::getBackUpDataRules).orElse(null);
        if(CollUtil.isEmpty(stringBackUpDataRuleMap)){
            return null;
        }
        backUpDataRule = stringBackUpDataRuleMap.get(BackupDataScene.TRE_USER_FORWARD);
        return backUpDataRule;
    }

    @Override
    public BackUpDataRule changeOffSet(BackUpDataRule backupDataRule) {
        backupDataRule.setBeginId(backupDataRule.getEndId());
        Long endId = backupDataRule.getBeginId() + backupDataRule.getQuerySize();
        backupDataRule.setEndId(endId);
        return backupDataRule;
    }

    @Override
    public List<Object> queryData(BackUpDataRule backUpDataRule) {
        log.info("[数据归档模块]\t[用户数据查询]");
        return Collections.emptyList();
    }

    @Override
    public void insertData(List<Object> datas) {
        log.info("[数据归档模块]\t[用户数据插入]");
    }

    @Override
    public void deleteData(List<Object> datas) {
        log.info("[数据归档模块]\t[用户数据删除]");
    }
}

API

BackupDataFactory.BACKUP_DATA_SERVICE.get(TODO).exeBackUpData();

数据变更记录启动器

针对字段更新的变更日志的通用实现

核心功能

  • 实体映射到表字段变更比对
  • 实体外部关联表字段翻译
  • 实体枚举字段翻译
  • 自定义拓展字段

概览

数据模型

使用

引入依赖
  <!--变更日志场景-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>change-log-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableChangeLog 注解以开启场景功能

@EnableChangeLog 
其他配置

配置数据表信息获取数据库名称以及忽略的字段

dingwen:
	treasure:
		change:
			log: 
				dataBase: TODO //数据库
				ignores: TODO // 忽略的字段

API

在需要使用的地方组装事件对象进行发布即可

    @Test
    public void testChangelog(){
        DictData newData = new DictData();
        newData.setDictType("c_config_type");
        newData.setStatus(DicStatus.ENABLED.getCode());
        DictData oldData = new DictData();
        oldData.setDictType("d_field_type");
        oldData.setStatus(DicStatus.DISABLED.getCode());
        ChangeLogMetricEvent event = new ChangeLogMetricEvent("testChangelog");
        event.setChangeLogType(ChangeLogType.UPDATE);
        event.setDataInfo("用户基础信息");
        event.setDataIdName("dict_data_id");
        event.setDataId(newData.getDictDataId());
        event.setDataIdDescription("主键-字典值id");
        event.setNewData(newData);
        event.setOldData(oldData);
        Map<String, Function<Object, Object>> fieldConverts = new HashMap<>(2);
        fieldConverts.put("dictType", dictService::convert);
        fieldConverts.put("status", o -> IBaseEnum.fastDescFrom(DicStatus.class,((IBaseEnum)o).getCode()));
        event.setFieldConverts(fieldConverts);

        List<TableFieldInfo> extFields = new ArrayList<>(2);
        extFields.add(TableFieldInfo.builder().columnName("dictModule").columnComment("字典所属模块").build());
        event.setExtFields(extFields);
        SpringUtil.publishEvent(event);
    }

事件场景启动器

常规的Spring事件订阅机制实现存在不能重发,可靠性,幂等性等问题.故对事件机制进行拓展解决上述问题.

业务方只需要继承BaseEvent事件及BaseListener再通过SpringUtils发布事件就可以实现该功能

类图概览

数据模型

核心功能

采用持久化的方式保证重试可靠功能,当Spring环境中没有找到对应的实现时则会按照默认的方式进行.

初次之外也提供了基于Redis分布式锁方式的幂等不重复执行可靠性实现.

/**
 * 抽象事件监听器 </br>
 * <p>
 *     TODO 事务控制
 *     TODO 分布式锁实现
 * </p>
 * @author dingwen
 * @since 2024/3/27 11:12
 */
@Slf4j
public abstract class BaseListener<T extends BaseEvent>   implements ApplicationListener<T> {

    /**
     * 处理业务方法
     *
     * @param baseEvent base event
     */
    abstract protected void handler(T baseEvent);

    /**
     * 当应用程序发生事件时调用此方法 </br>
     * <p>
     *     此方法并不能保证幂等,也不能保证多线程条件下不重复执行.
     *     若有此需求请考虑使用分布式锁控制的实现
     * </p>
     *
     * @param event event
     */
    @Override
    public void onApplicationEvent(T event) {
        log.info("[base]抽象事件监听器,开始执行,事件对象:{}", JSONUtil.toJsonStr(event));
        Map<String, IEvent> eventServices = SpringUtils.getBeansOfType(IEvent.class);
        if(CollUtil.isEmpty(eventServices) || Objects.isNull(event.getEventId())){
            log.warn("[base]抽象事件监听器,缺失事件对象id或未找到事件服务,将已普通方式运行,不能保证监听执行成功以及重发功能");
            handler(event);
            return;
        }
        eventServices.forEach((eName,eService)->{
            if(eService.isNeedExecute(event.getEventId())){
                log.info("[base]抽象事件监听器,开始执行,eventId:{}", event.getEventId());
                handler(event);
                log.info("[base]抽象事件监听器,执行成功,进行状态修复,eventId:{}", event.getEventId());
                eService.succeed(event.getEventId());
            }
        });

    }
}


并发安全的实现


/**
 * 安全的,能保证幂等的,不重复执行的,并发安全的监听器实现 </br>
 * <p>
 * 后期可采用动态代理优化
 * </p>
 *
 * @author dingwen
 * @since 2024/3/27 15:10
 */
@Slf4j
public abstract class AbstractSafeBaseListener<T extends BaseEvent> extends AbstractBaseListener<T> {

    @Override
    public void onApplicationEvent(T event) {
        log.info("[base]并发安全的抽象事件监听器,开始执行,事件对象:{}", JSONUtil.toJsonStr(event));
        EventProperties eventProperties = SpringUtils.getBean(EventProperties.class);
        Assert.notNull(eventProperties, "事件场景启动器关键配置缺失");
        RedisShareLockComponent shareLockComponent = SpringUtils.getBean(RedisShareLockComponent.class);
        if (Objects.isNull(shareLockComponent)) {
            log.warn("[event] [安全的监听器],关键组件缺失,将使用不具备安全功能的监听器实现");
            super.onApplicationEvent(event);
            return;
        }
        String lockKey = EventConstant.LOCK_EVENT_PREFIX.concat(Convert.toStr(event.getEventId()));
        String requestId = IdUtils.fastUUID();
        Long lockTime = ObjectUtil.defaultIfNull(eventProperties.getLockTime(), 10L);
        TimeUnit lockTimeUnit = ObjectUtil.defaultIfNull(eventProperties.getLockTimeUnit(), TimeUnit.SECONDS);
        try {
            boolean lock = shareLockComponent.lock(lockKey, requestId, lockTime, lockTimeUnit);
            if (Boolean.FALSE.equals(lock)) {
                log.warn("[event] [安全的监听器],资源抢占,取消执行,lockKey:{},eventId:{}", lockKey, event.getEventId());
                return;
            }
            super.onApplicationEvent(event);
        } catch (Exception e) {
            log.error("[event] [安全的监听器],执行失败,错误消息:{},lockKey:{},eventId:{}", e.getMessage(), lockKey,
                    event.getEventId(), e);
        } finally {
            shareLockComponent.unLock(lockKey, requestId);
        }
    }
}

使用

引入依赖
        <!--事件场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>event-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableEvent 注解以开启场景功能

@EnableEvent 
其他配置

若是并发安全的实现则需要配置锁相关参数

dingwen:
	treasure:
		    # 事件
    event:
      # 时间
      lock-time: 10
      # 时间单位: seconds
      lock-time-unit: seconds

API

  • 事件重发: boolean retry(Long eventId)
  • 事件删除
  • 事件列表 Page<Event> queryEventPage(QueryWrapper<Event> queryWrapper)

Jwt场景启动器

通过灵活的配置,实现Jwt的生成,校验,刷新等.

使用

引入依赖
        <!--jwt场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>jwt-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableJwt注解以开启场景功能

@EnableJwt 
其他配置
dingwen:
	treasure:
    # jwt
    jwt:
      # 头信息: 默认值: authorization
      header: "Authorization"
      # 令牌前缀: 默认值 Bearer
      token-prefix: "Bearer "
      # 令牌密钥: 最少长度 32
      secret-key: "38329cc9d1b1496da21700d02ecd0690c348930073f"
      # App端过期时间 (单位分钟)
      app-expire-time: 5
      # Web端过期时间 (单位分钟)
      web-expire-time: 20
      # App 刷新时间 秒
      app-refresh-time: 10
      # Web 刷新事件 秒
      web-refresh-time: 120
      # 令牌签发者
      issuer: "treasure"
      # 令牌签发主题
      subject: "jwt"

API Jwt

测试API JwtController

  • GET [生成App端JwtToken]: common/jwt/apps
  • GET [生成Web端JwtToken]: common/jwt/webs
  • GET [判断是否需要刷新]: common/jwt/needs
  • POST [验证JwtToken]: common/jwt/verifies
  • POST [刷新JwtToken]: common/jwt/refresh

关于异常处理

若令牌异常(包括未携带令牌,或者非法的令牌又或是过期的令牌)都会抛出JwtVerifyException异常,并返回 401错误

安全场景启动器security-plus-spring-boot-starter

Spring Security的二次封装

使用

引入依赖
        <!--安全场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>security-plus-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableSecurityPlus注解以开启场景功能

@EnableSecurityPlus 
其他配置
dingwen:
	treasure:
    # 安全
    security:
      plus:
        # 是否开启安全管控
        enable-security-plus: true
        # 是否开启jwt安全管控
        enable-jwt-filter: true
        # 是否开启接口基本动态权限控制  
        enable-dynamic-security: true
        matchers:
          # 允许进行匿名访问的url
          anonymous:
           - /
           - /common/auths/webs/logins
           - /common/auths/captcha
        login:
          # 退出登录地址
          logoutProcessingUrl: common/auths/logout

资源权限数据模型

资源权限内置API [规划中...]

  • 新增资源权限
  • 修改资源权限 [缓存管理...]
  • 删除资源权限[缓存管理...]
  • 资源权限列表
  • 开启/关闭资源权限管控[缓存管理...]
  • 新增资源
  • 修改资源[缓存管理...]
  • 删除资源[缓存管理...]
  • 开启/关闭资源管控[缓存管理...]
  • 权限下的资源列表

核心特色功能

  • 统一配置
  • Jwt令牌实现
  • 动态接口权限
    • 权限校验规则
      • 排除所有 ExcludesAuthorityStrategy
      • 包含所有 IncludesAuthorityStrategy
      • 包含单个 IncludeAuthorityStrategy
      • 自定义规则
    • Caffeine + Redis二级缓存
  • 统一的令牌管理 TokenService
    • 刷新令牌
    • 生成令牌
    • 校验令牌等
  • 认证环境对象+工具类进一步抽象 SecurityPlusGrantedAuthority, SecurityPlusUtils
  • 认证失败,认证入口,异常统一进行国际化封装结果返回处理

动态权限加载拓展接口AbstractDynamicAttributeService

package com.dingwen.treasure.auth.support.security;

import cn.hutool.core.collection.CollUtil;
import com.dingwen.treasure.auth.manager.IAuthResManager;
import com.dingwen.treasure.auth.model.bo.AuthResBO;
import com.dingwen.treasure.auth.model.po.AuthRes;
import com.dingwen.treasure.auth.service.IAuthResService;
import com.dingwen.treasure.security.plus.enums.AuthorityStrategy;
import com.dingwen.treasure.security.plus.support.dynamic.AbstractDynamicAttributeService;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * DynamicAttributeServiceImpl : 动态资源权限服务
 *
 * @author dingwen
 * @since 2024/4/22 14:21
 */
@Component
public class DynamicAttributeServiceImpl extends AbstractDynamicAttributeService {

    @Resource
    private IAuthResManager authResManager;

    @Resource
    private IAuthResService authResService;


    @Override
    public Map<String, List<ConfigAttribute>> loadSourceAttributes() {
        List<AuthResBO> authResList = authResManager.getEnabledAuthRes();
        if (CollUtil.isEmpty(authResList)) {
            return Collections.emptyMap();
        }
        return buildAttributes(authResList);
    }

    @Override
    public AuthorityStrategy getAuthorityStrategy(FilterInvocation filterInvocation) {
        HttpServletRequest httpRequest = filterInvocation.getHttpRequest();
        String requestURI = httpRequest.getRequestURI();
        String method = httpRequest.getMethod();
        // 多级缓存处理
        AuthRes authRes = authResService.queryOne(requestURI,method);
        if (Objects.nonNull(authRes)) {
            return authRes.getAuthorityStrategy();
        }
        return AuthorityStrategy.INCLUDES;
    }

    /**
     * 构建权限map
     *
     * @param authResList 资源权限业务对象
     * * @return 权限信息
     */
    private Map<String, List<ConfigAttribute>> buildAttributes(List<AuthResBO> authResList) {
        Map<String, List<ConfigAttribute>> result = new HashMap<>(authResList.size());
        for (AuthResBO ar : authResList) {
            List<ConfigAttribute> configAttributes = ar
                    .getAttributes()
                    .stream()
                    .map(SecurityConfig::new)
                    .collect(Collectors.toList());
            result.put(authResService.buildSourceKey(ar.getRequestUri(), ar.getRequestMethod()), configAttributes);
        }
        return result;
    }

}

测试API AuthController

  • POST [刷新令牌]: common/auths/refresh
  • 在线用户统计 [规划中...]
  • 踢人 [规划中...]

认证场景启动器 auth-spring-boot-starter

基于安全场景启动器完成认证(但不局限于此方式),兼容SaToken

核心功能

  • 接口权限动态设定 [支持拓展规则]
  • 用户分组+层级
  • 角色分组+层级+互斥+继承
  • 菜单+按钮+权限灵活控制
  • 租户
    • 表字段方式
    • 动态数据源方式

简化版权限模型

使用
引入依赖
        <!--认证场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>auth-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableAuth注解以开启场景功能

@EnableAuth 
其他配置
dingwen:
	treasure:
    # 认证
    auth:
      # 登录密码相关配置
      password-properties:
        # 是否开启密码加密传输 (sm2)
        enabled: true
        # 私钥
        privateKey: "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQga+98CaPB0t83RhgbzPSxNCbwhluKaOcWSWXMJ9mHi7KgCgYIKoEcz1UBgi2hRANCAAQYqj8QyJqBOTHfb0orFU7I4wlg/FGzLEdTjMvz1UjDosEZ/8RHv0VQHsulvaQFkmoUnq1rsaLpW0vgzsCdmza+"
        # 公钥
        publicKey: "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEGKo/EMiagTkx329KKxVOyOMJYPxRsyxHU4zL89VIw6LBGf/ER79FUB7Lpb2kBZJqFJ6ta7Gi6VtL4M7AnZs2vg=="
      # 验证码相关配置
      captcha-properties:
        # 验证码开关
        enabled: false
        # 验证干扰类型
        captchaDisturbType: LINE_CAPTCHA
        #  默认验证码宽度 200
        width: 200
        # 默认验证码高度 100
        height: 100
        # 默认验证码字符个数 5
        codeCount: 5
        # 默认验证码干扰数 15
        disturbCount: 15
        # 验证码有效期: 默认两分钟
        time: 2
        # 验证码有效期: 默认分钟
        unit: MINUTES
      # 登录配置
      login-properties:
        # 是否开启错误次数限制
        enableErrLimit: true
        # 最大重试次数: 默认值 5
        maxRetries: 5
        # 账户锁定时间
        lockTime: 5
        # 账户锁定单位
        lockTimeUnit: MINUTES
        # 登录错误锁定前缀
        loginErrorPrefix: "account:login:err:{}"

验证码组件ICaptchaManager

  • 数字验证码
  • 图形验证码
  • 行为验证码[规划中...]

动态权限组件 DynamicAttributeServiceImpl

支持Spring Security动态权限

登录 ILoginStrategy

  • 短信验证码登录[规划中...]
  • 电子邮箱验证码登录[规划中...]
  • 用户名密码登录
  • 手机号密码登录[规划中...]
  • 三方登录[规划中...]

测试API AuthController

  • GET [获取验证码]: common/auths/captcha [默认1分钟只能调用10次]
  • POST [Web端登录]: common/auths/webs/logins

二级缓存场景启动器

基于本地缓存场景启动器和Redis场景启动器,Caffeine + Redis + Spring Cache 实现的二级缓存

使用

引入依赖
        <!--二级缓存场景启动器-->
        <dependency>
            <groupId>com.dingwen</groupId>
            <artifactId>dcache-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
启动项配置

在启动类上添加@EnableDCache注解以开启场景功能

@EnableDCache 
其他配置
dingwen:
	treasure:
    # 二级缓存
    dcache

核心功能

  • 支持分布式跨JVM进程刷新
  • com.dingwen.treasure.dcache.core.DoubleCache : 二级缓存
  • com.dingwen.treasure.dcache.core.DoubleCacheManager: 缓存管理器
  • com.dingwen.treasure.dcache.core.RefreshCacheListener: 字典缓存监听通过redis订阅发布实现缓存刷新

使用案例

    @Cacheable(cacheNames = "authResCache",key = "#requestUri+':'+#requestMethod",cacheManager = "doubleCacheManager")
    @Override
    public AuthRes queryOne(String requestUri, String requestMethod) {
        return queryOne(AuthRes.builder().requestUri(requestUri).requestMethod(requestMethod).build());
    }

核心服务

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
  • Redissetnx版分布式锁案例
  • ResponseBodyAdvice实现统一返回 ResponseBodyAdviceController
  • 开放接口对接-短信 SmsController
  • feign调用案例TaskFeignController
  • 设计模式-责任链-任务生成案例TaskGenerateController
  • 后端枚举类型统一翻译 EnumController
  • 自定义注解实现加解密、脱敏 SensitiveController
  • 微信公众号定制消息推送WechatPubController
  • 数据库缓存一致性解决方案

RabbitMQ全链路顺序、可靠消费,100%不丢失

API入口:MessageController

  • 生产者进行可靠性消息投递
  • 消费者手动确认
  • 消息落库(状态管理、消费顺序控制)
  • 定时任务补偿
流程图

顺序可靠消息主流程

优化工具类

参考Spring StopWatch 的拓展优化,精确计算执行耗时,执行次数,方便进行优化 OptimizeUtilController

/**
 *  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
function add(op1, op2) {
    return op1 + op2
}

add(a, b)
整体思路、功能点

JavaScript规则业务思路

通过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
  • 指定时间、次数
/**
 * 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<String> rateLimiterSimpleTest() {
        return ResultGenerator.genSuccessResult();
    }
}

多线程任务进度条实现

基于CompletableFutureredis的多线程任务进度条实现,后端异步进行任务,前端轮询调用进度条进度查询

  • redis >
  • setnx 检查任务key是否存在,任务是否在进行中
  • hash 存储任务进度信息 >
  • 指定哈希键值的increment
  • CompletableFuture >
  • whenComplete 子任务完成时更新任务进度
  • exceptionally 发生异常时更新任务进度
  • AbstractProgressBarTask 抽象任务进度条组件,囊括进度条功能,子类继承实现业务逻辑即可
API调用
/**
 * 进度条任务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<TaskVo> 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<TaskVo> 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

时序数据库案例

  • 官网
  • 接口 IotDataService

数据库缓存一致性解决方案

正常流程

以上的流程没有问题,当数据变更的时候,如何能保证将缓存同步到最新呢?

先更新数据库,再更新缓存

假设数据库更新成功,缓存更新失败,在缓存过期失效之前,读取到的缓存数据都是旧的

先更新缓存,再更新数据库

假设缓存更新成功,数据库更新失败,那读取到的数据都是错误的

先删除缓存,再更新数据库

假设删除缓成功,此时A线程正在更新数据库,同时B线程也来了查询数据,发现缓存中没有,就查询数据库。此科查询到的数据任然是旧数据。

若此时做延迟删除缓存,根据业务时间灵活调整,确保修改数据线程已提交,延迟删除之后再查询就能保证数据是正确的了

若删除缓存失败,可加入消息队列,做删除重试

cacal方案

开发独立的服务,监控数据库的改变,同步对于的缓存

线程数设置理论

宽泛不切实际的结论
  1. CPU 密集型的程序 - 核心数 + 1
  2. I/O 密集型的程序 - 核心数 * 2
理论铺垫

CPU利用率: 如果指令需要不断的执行,则CPU的利用率为100%,此时CPU将不能做除执行次指令之外的任何事情

线程上下文切换的代价: 现代CPU基本都是多核心的,可以同时做核心数件事情互不打扰.如果要执行的线程大于核心数,那么就需要通过操作系统的调度了。操作系统给每个线程分配CPU时间片资源,然后不停的切换,从而实现“并行”执行的效果.每次切换会伴随着寄存器数据更新,内存页表更新等操作,就会导致CPU资源过多的浪费在上下文切换上,而不是在执行程序,得不偿失.

高效利用:多程序在运行时都会有一些 I/O操作,可能是读写文件,网络收发报文等,这些 I/O 操作在进行时时需要等待反馈的。比如网络读写时,需要等待报文发送或者接收到,在这个等待过程中,线程是等待状态,CPU没有工作。此时操作系统就会调度CPU去执行其他线程的指令,这样就完美利用了CPU这段空闲期,提高了CPU的利用率。

线程数和CPU利用率的小总结
  1. 一个极端的线程(不停执行“计算”型操作时),就可以把单个核心的利用率跑满,多核心CPU最多只能同时执行等于核心数的“极端”线程数
  2. 如果每个线程都这么“极端”,且同时执行的线程数超过核心数,会导致不必要的切换,造成负载过高,只会让执行更慢
  3. I/O 等暂停类操作时,CPU处于空闲状态,操作系统调度CPU执行其他线程,可以提高CPU利用率,同时执行更多的线程
  4. I/O 事件的频率频率越高,或者等待/暂停时间越长,CPU的空闲时间也就更长,利用率越低,操作系统可以调度CPU执行更多的线程
线程数规划的公式

引用自《Java 并发编程实战》

如果我期望目标利用率为90%(多核90),那么需要的线程数为:

核心数12 * 利用率0.9 * (1 + 50(sleep时间)/50(循环50_000_000耗时)) ≈ 22

通过线程数来计算CPU利用率

线程数22 / (核心数12 * (1 + 50(sleep时间)/50(循环50_000_000耗时))) ≈ 0.9

虽然公式很好,但在真实的程序中,一般很难获得准确的等待时间和计算时间,因为程序很复杂,不只是“计算”。一段代码中会有很多的内存读写,计算,I/O 等复合操作,精确的获取这两个指标很难,所以光靠公式计算线程数过于理想化。

真实程序中的线程数

没有固定答案,先设定预期,比如我期望的CPU利用率在多少,负载在多少,GC频率多少之类的指标后,再通过测试不断的调整到一个合理的线程数

比如一个普通的,SpringBoot 为基础的业务系统,默认Tomcat容器+HikariCP连接池+G1回收器,如果此时项目中也需要一个业务场景的多线程(或者线程池)来异步/并行执行业务流程。

此时我按照上面的公式来规划线程数的话,误差一定会很大。因为此时这台主机上,已经有很多运行中的线程了,Tomcat有自己的线程池,HikariCP也有自己的后台线程,JVM也有一些编译的线程,连G1都有自己的后台线程。这些线程也是运行在当前进程、当前主机上的,也会占用CPU的资源。

一般的流程
  1. 分析当前主机上,有没有其他进程干扰
  2. 分析当前JVM进程上,有没有其他运行中或可能运行的线程
  3. 设定目标
  4. 目标CPU利用率 - 我最高能容忍我的CPU飙到多少?
  5. 目标GC频率/暂停时间 - 多线程执行后,GC频率会增高,最大能容忍到什么频率,每次暂停时间多少?
  6. 执行效率 - 比如批处理时,我单位时间内要开多少线程才能及时处理完毕
  7. ……
  8. 梳理链路关键点,是否有卡脖子的点,因为如果线程数过多,链路上某些节点资源有限可能会导致大量的线程在等待资源(比如三方接口限流,连接池数量有限,中间件压力过大无法支撑等)
  9. 不断的增加/减少线程数来测试,按最高的要求去测试,最终获得一个“满足要求”的线程数

注意:不同场景下的线程数理念也有所不同

  1. Tomcat中的maxThreads,在Blocking I/O和No-Blocking I/O下就不一样
  2. Dubbo 默认还是单连接呢,也有I/O线程(池)和业务线程(池)的区分,I/O线程一般不是瓶颈,所以不必太多,但业务线程很容易称为瓶颈
  3. Redis 6.0以后也是多线程了,不过它只是I/O 多线程,“业务”处理还是单线程
稳妥的方案

很多的内部业务系统,并不需要啥性能,稳定好用符合需求就可以了。推荐的线程数是:CPU核心数

Java 获取CPU核心数

Runtime.getRuntime().availableProcessors()//获取逻辑核心数,如6核心12线程,那么返回的是12

Linux 获取CPU核心数

总核数 = 物理CPU个数 X 每颗物理CPU的核数

总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数

查看物理CPU个数 cat /proc/cpuinfo | grep "physical id"|sort|uniq|wc -l

查看每个物理CPU中core的个数(即核数) cat /proc/cpuinfo | grep "cpu cores" | uniq

查看逻辑CPU的个数cat /proc/cpuinfo | grep "processor" | wc -l

七大软件设计原则

OCP 开闭原则
  • 对扩展开发,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改关闭,意味着类一旦设计完成,就可以独立的工作,而不要对其进行任何的修改。
DIP 依赖倒置原则

面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。

SRP单一职责原则

一个类只应该负责一项职责

ISP接口隔离原则

不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口要好。做接口拆分时,也要尽量满足单一职责原则。将外部依赖减到最少,降低模块间的耦合.

LOD迪米特原则

也被称为最少知识原则,它提出一个模块对其他模块应该知之甚少,或者说模块之间应该彼此保持陌生,甚至意识不到对方的存在,以此最小化、简单化模块间的通信,并达到松耦合的目的。

CRP合成复用原则

优先使用合成/聚合,而不是类继承。

LSP里式替换原则

程序中的对象可以在不改变程序正确性的前提下被它的子类所替换,即子类可以替换任何基类能够出现的地方,并且经过替换后,代码还能正确工作。

treasure-slow-sql

深分页查询优化

  SELECT *
  FROM sys_user
  WHERE id > #{offset} LIMIT 0,#{pageSize};
/**
 *  优化Api
 *  @author dingwen
 *  @since 2022/8/5
 */
@Api(tags = "优化API")
@RestController
@Slf4j
@RequestMapping("optimize")
@Validated
public class OptimizeController {

    @Resource
    private SysUserService userService;


    /**
     * 适用于自增id
     * 数据总条数:4301000 </br>
     * <p>自带查询分页性能:</p>
     * <ul>
     *     <li>第 1 页 10 条关闭count查询优化耗时:4秒</li>
     *     <li>第 100,00 页 100 条关闭count查询优化耗时:5秒</li>
     *     <li>第 100,000 页 100 条关闭count查询优化耗时:5秒</li>
     *
     *     <li>第 1 页 10 条打开count查询优化耗时:4秒</li>
     *     <li>第 100,00 页 100 条打开count查询优化耗时:4秒</li>
     *     <li>第 100,000 页 100 条打开count查询优化耗时:4秒</li>
     * </ul>
     *
     * <p>优化后性能:</p>
     * <ul>
     *     <li>第 1 页 10 条耗时:30~80毫秒</li>
     *     <li>第 100,00 页 100 条耗时:30~80毫秒</li>
     *     <li>第 100,000 页 100 条耗时:30~80秒</li>
     * </ul>
     *
     * 再进行统计总记录条数时会遇到瓶颈,正确的处理方式应该是杜绝深分页,不会有用户往下翻到地100页
     * 一般选择50页即可
     *
     * @param pageDto 页面dto
     */
    @PostMapping("/deep-page")
    @ApiOperation("深分页查询优化")
    public Result<PageData<SysUser>> deepPage(@RequestBody PageDto pageDto) {
        Long current = pageDto.getCurrent();
        Long pageSize = pageDto.getPageSize();
        Page<SysUser> 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张表

kettle数据库资源库

方案思路

Java集成定时远程调用和远程服务方式调用以及使用spoon客户端进行远程调用需要启动carte。 carte.sh指定配置文件启动,单机考虑一主一从。默认用户密码(cluster),可在启动文件中配置

Java集成定时远程调用

远程调用执行

  • id: master ip
  • port: master port
  • dataRepositoryName: 数据库资源库名称
  • user: 集群名称(master中配置)
  • password: 集群密码(master中配置)
  • transName: 转换文件名称
  • jobName: 作业文件名称
  • path: 路径
  # 转换执行
 
  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

docker search hiromuhota/webspoon 

docker run -d -p 8080:8080 --name spoon hiromuhota/webspoon 

其他

Kettle自带监控页面
SpringBoot2.X和Kettle9整合
  # 手动添加如下依赖
  
  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配置指定

spring:
  security:
    user:
      name: actuator
      password: actuator

TODO

  • 自定义info、metrics、health、endpoint
  • 邮件、钉钉预警

treasure-xxl-job-admin

基于xxl-job v2.4.0封装的调度中心

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);


/**
 * 钉鸽
 *
 * @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通知

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<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        log.info("清廉系统后端服务已停止");
        // 可依据环境判断是否执行
        DingerUtils.send("清廉系统后端服务已停止");

    }
}
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<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 可依据环境判断是否执行
        DingerUtils.send("清廉系统后端服务已启动");
    }
}

设计模式treasure-gof

其他临时文档

  • business

优雅代码业务组件实现(MybatisPlus)

特色业务场景

  • 线程池案例
  • 批量save
  • 自定义MVC反序列化方式
  • 缓存业务场景自定义注解:@EasyCache
  • 国际化
  • 状态模式+简单工厂模式实现:多阶段灵活控制状态流转

设计模式六大原则:

  • 单一职责
  • 接口隔离(Servlet filter)
  • 依赖倒置
  • 迪米特(最少知道原则)
  • 里氏替换 (父类抽象方法应保持行为一致,非抽象方法不应去重写)
  • 开闭原则
  • 合成复用

状态模式: 对象不同的状态导致不同的行为

  • 主要角色:
    • 抽象状态(接口或抽象类)
    • 具体状态(抽象状态的子类或实现类)
    • 环境类 (状态持有着,依据不同的状态出发不同的行为)

状态模式案例UML类图

  • 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文档

  • 监控面板

  • 国际化

  • 监控

  • 定时任务

  • API文档

  • Aop

    • ReSubmit(防止重复提交)
    • EasyCache (缓存)
  • 通用业务组件

    • 简单工厂 + 策略模式
    • 模版方法
  • Spring 高级

    • 观察者模式(监听、事件机制)
    • 自动注入Map
  • 监控自定义 预警(钉钉、邮件)

  • Mybatis Plus

    • 多数据源
    • 逻辑删除
    • 乐观锁
    • Model
    • lambda
    • 通用枚举
    • 联合主键@MppMultiIdIMppServiceMppBaseMapper
    • 自动填充新增时间、修改时间
    • 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删除
  • XXXManagerIXXXManagerIXXXManagerImpl【Optional】

    功能动词+For使用场景

    • obtainDeptTree【获得部门树】
  • XXXServiceIXXXServiceIXXXServiceImpl 【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 上下文

  • XXXHandlerXXXCallbackXXXTriggerXXXListener

  • XXXAware 感知

  • XXXMetric指标

  • XXXPool

  • XXXChain

  • XXXFilter 过滤

  • XXXInterceptor 拦截器

  • XXXEvaluator 判断条件是否成立

  • XXXStrategy 策略

  • XXXAdapter适配器

  • XXXEvent 事件

  • XXXBuilder 构建

  • XXXTemplate 模版

  • XXXProxy 代理

  • XXXConverter 转换

  • XXXRessolver解析

  • XXXParser解析器

  • XXXUtils 工具类

  • XXXHelper 帮助类

  • XXXConstant 常量

  • XXXGenerator 生成

场景启动器使用聚合scene

Maven环境隔离

引入打包插件


            <!--maven 资源插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <delimiters>@</delimiters>
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                </configuration>
                <version>${maven-resources-plugin.version}</version>
            </plugin>

resource

 <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <!--注意会将匹配到文件排除在编译以后的结果之外,因此下面会再有一个
                filtering 的模块,再通过include的形式,将xlsx文件再如引入进来-->
                <excludes>
                    <exclude>**/*.xlsx</exclude>
                    <exclude>**/*.xml</exclude>
                    <exclude>**/*.docx</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.xlsx</include>
                    <include>**/*.xml</include>
                    <include>**/*.docx</include>
                </includes>
            </resource>
        </resources>

配置使用

disruptor log4j2高性能异步日志

整体性能有显著提升,适用C端的大量日志场景

添加依赖

        <!--高性能内存队列-->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>${disruptor.version}</version>
        </dependency>

        <!--log4j-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

排除相关logback冲突包

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

整体进行排除

    <!--全局排除 logback 使用更高效的log4j实现-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>*</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

配置 + 使用

后续计划

  • 通用业务场景拆分封装
  • Cloud系、Dubbo系
  • 监控、告警
  • 前端部分
    • Vue3
    • Uniapp
    • Flutter
  • ELK
  • 容器化

待完成任务

  • es组件
  • 代码生成组件
  • 缓存预热进一步优化
  • httpclient5 优化
  • 京东async封装
  • fc-async
  • 微服务 cloud + eureka + ribbon...
  • 自定义ribbon负载均衡
  • alibaba微服务系
  • 设计模式系
  • file场景改造优化
    • 分片上传,断点续传,秒传
    • 基于nacaos动态配置策略
  • Guava 本地缓存
  • 数据权限通用实现
  • 其他优化
  • 多线程进度条任务脱离Redis实现
  • 迷你工作流
  • caffeine cache 本地缓存拓展 + 进一步优化

联系我

文档版本

2024-01-04 09:37:47

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

Treasure是一个Java技术生态项目,涵盖了单体、微服务、DDD等架构实践,以兴趣、学习目的、技术积累为理念,逐步完善迭代。主要包含学习成长过程中一些技术点、工作中积累的一些心得,面试中一些业务场景模拟及解决方案一些常见、通用业务的解决方案、合理应用设计模式进行一些业务代码的重构优化、常用小轮子的积累、一些更优雅的编码实现、通用场景封装等内容。 展开 收起
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/dingwen-gitee/treasure.git
git@gitee.com:dingwen-gitee/treasure.git
dingwen-gitee
treasure
treasure
master

搜索帮助