diff --git a/.fleet/run.json b/.fleet/run.json new file mode 100644 index 0000000000000000000000000000000000000000..8d00d7aebea273c37efea203b465b3cdac41be63 --- /dev/null +++ b/.fleet/run.json @@ -0,0 +1,25 @@ +{ + "configurations": [ + + { + "name": "Build SpringCloud-multiple-gradle", + "type": "gradle", + "workingDir": "$PROJECT_DIR$", + "tasks": [":app:classes"], + "initScripts": { + "flmapper": "ext.mapPath = { path -> null }", + "Build SpringCloud-multiple-gradle": "System.setProperty('org.gradle.java.compile-classpath-packaging', 'true')" + } + }, + { + "name": "Application", + "type": "spring-boot", + "workingDir": "$PROJECT_DIR$", + "dependsOn": ["Build SpringCloud-multiple-gradle"], + "mainClass": "hxy.dream.app.Application", + "module": "multi-gradle.app.main", + "options": ["-XX:TieredStopAtLevel=1", "-Dspring.output.ansi.enabled=always", "-Dcom.sun.management.jmxremote", "-Dspring.jmx.enabled=true", "-Dspring.liveBeansView.mbeanDomain", "-Dspring.application.admin.enabled=true", "-Dmanagement.endpoints.jmx.exposure.include=*", "-Dfile.encoding=UTF-8", "-Dsun.stdout.encoding=UTF-8", "-Dsun.stderr.encoding=UTF-8", "-classpath", "$USER_HOME$/.m2/repository/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar:$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.javassist/javassist/3.22.0-GA/3e83394258ae2089be7219b971ec21a8288528ad/javassist-3.22.0-GA.jar"], + "activeProfiles": ["test"] + } + ] +} \ No newline at end of file diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml new file mode 100644 index 0000000000000000000000000000000000000000..eecf21681692276ce31ca718b3db72727802e357 --- /dev/null +++ b/.github/workflows/gradle-publish.yml @@ -0,0 +1,45 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle + +name: Gradle Package + +on: + release: + types: [created] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Gradle + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build -x test + + # The USERNAME and TOKEN need to correspond to the credentials environment variables used in + # the publishing section of your build.gradle + - name: Publish to GitHub Packages + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: publish + env: + USERNAME: ${{ github.actor }} + TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000000000000000000000000000000000..2efafc67a53cb2e06733d562bcec39116e3c1167 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "springboot3.0" ] + pull_request: + branches: [ "springboot3.0" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build -x test diff --git a/.gitignore b/.gitignore index 355d2ca46d3e23bd31624d1a260c5199fd7b6dc5..32526de8ca648c7f36e211987d56bd20d930e718 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,10 @@ build/ /.scannerwork/ *.hprof **/build/ -**/out/ \ No newline at end of file +**/out/ +/app/src/main/resources/application-dev.yml +class/ +bin/ +.classpath +.project +.settings/ \ No newline at end of file diff --git a/Mybatis.md b/Mybatis.md new file mode 100644 index 0000000000000000000000000000000000000000..34ca5eec1b96d97ba9a11c77ca4bb0a126a3bf84 --- /dev/null +++ b/Mybatis.md @@ -0,0 +1,38 @@ + +### 执行初始化的sql语句 + +有两种方案。一种借助与mybatis-plus 另一种就是mybatis的 + +#### 基于mybatis的sql执行方案 + +[hxy.dream.common.init.ApplicationStartupRunner](common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java) + +#### 基于mybatis-plus的sql执行方案 + +##### 基于mybatis-plus配置直接执行 + +https://www.baomidou.com/pages/226c21/#%E9%85%8D%E7%BD%AE + +```yaml +# DataSource Config +spring: + datasource: + driver-class-name: org.h2.Driver + username: root + password: test + sql: + init: + schema-locations: classpath:db/schema-h2.sql + data-locations: classpath:db/data-h2.sql +``` + +##### 基于mybatis-plus的sql执行方案 + +[hxy.dream.common.init.MysqlDdl](common/src/main/java/hxy/dream/common/init/MysqlDdl.java) + +### OpenFeign要退出历史舞台了 + +推荐 RestTemplate或者WebClient + +WebClient 声明式的API调用: +[RemoteApiConfig](common/src/main/java/hxy/dream/common/configuration/RemoteApiConfig.java) \ No newline at end of file diff --git a/README.md b/README.md index 8d8192062bc11daf9d7a9fa95e8177e55e8efa38..0e7fa008c4abac06b5db237bdd359f0cc655dd94 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,70 @@ -Eric-Dream -=== -本工程承担日常实验的作用,任何先行的尝试都在这里实践与落地。成熟的方案设计会在 [base-server](https://gitee.com/aohanhongzhi/springboot-base) 里面落地到生产。 +

Eric-Dream

+本工程承担日常实验的作用,任何先行的尝试都在这里实践与落地。成熟的方案设计会在 [base-server](https://gitee.com/aohanhongzhi/springboot-base) +里面落地到生产。 -本项目是基于Gradle构建的多模块SpringCloud工程。采用`传统线程模型`的SpringWeb框架,mybatis-plus和mysql官方驱动。非常适合入门者学习。本项目的一大亮点就是自定义枚举序列化的处理。 +本项目是基于Gradle构建的多模块SpringCloud工程。采用`传统线程模型` +的SpringWeb框架,mybatis-plus和mysql官方驱动。非常适合入门者学习。本项目的一大亮点就是[自定义枚举序列化的处理](SERIALIZE.md)。 ## 主要实现功能如下 - 功能 | 实现 | 用途 ---- |-----------------------------------------------------------------------------------------------------| --- -jackson序列化 | 自定义序列化器 | 解决参数枚举的序列化问题 -logback钉钉通知 | 自定义Appender | Error消息及时通知 -logback邮件通知 | 默认支持 | Error异常及时通知 -全局异常捕获 | 默认支持 | 捕获异常 -数据库字段加解密 | [参考CustomTypeHandler](dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java) | 给部分数据库字段加解密 -执行SQL语句 | [自动建表](common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java) | -具体框架如下表: + 功能 | 实现 | 用途 +-----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------- + IDEA开发热加载实现 | JBR + HotswapAgent | https://github.com/HotswapProjects/HotswapAgent + [jackson序列化](SERIALIZE.md) | 自定义序列化器 [BaseEnumSerializer.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fserializer%2FBaseEnumSerializer.java) | 解决参数枚举的序列化问题 + logback钉钉通知 | 自定义Appender [LogbackDingTalkAppender.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fextend%2FLogbackDingTalkAppender.java) | Error消息及时通知 + logback邮件通知 | 默认支持 | Error异常及时通知 + 全局异常捕获 | [GlobalExceptionHandler.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fextend%2FGlobalExceptionHandler.java) | 捕获异常 + 数据库字段加解密 | [参考CustomTypeHandler](dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java) | 给部分数据库字段加解密 + 执行初始化SQL语句 | [自动建表](common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java) | + [MybatisPlus的SQL脚本自动维护](common/src/main/java/hxy/dream/common/init/MysqlDdl.java) | https://baomidou.com/pages/1812u1/ | 自动建表 + SpringBoot 3.0 声明式API远程调用 | 参考 [RemoteApi](common/src/main/java/hxy/dream/common/manager/RemoteApi.java) | + 参数全局trim() | [StringTrimDeserializer](common/src/main/java/hxy/dream/common/serializer/StringTrimDeserializer.java) | 避免异常数据 + Long类型超长转String | [BaseLongSerializer.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fserializer%2FBaseLongSerializer.java) | 解决前端无法正确显示超长String + 前端null传到后端成"null"字符串 | [StringToStringConverter](common/src/main/java/hxy/dream/common/converter/StringToStringConverter.java) | 解决字符串"null"等问题 + 敏感信息从程序外配置文件读取 | [RemoteEnvironmentPostProcessor.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fconfiguration%2FRemoteEnvironmentPostProcessor.java) 极海的方案 [DataSourceConfigLoader.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fconfiguration%2FDataSourceConfigLoader.java) | 从程序外的配置文件读取数据库的账号密码信息 + 单元测试默认配置环境 | [ @ActiveProfiles("test") ](app%2Fsrc%2Ftest%2Fjava%2Fhxy%2Fdream%2FBaseTest.java) | 解决每次修改yaml文件的烦恼 | + 数据库字段加密 | [参考CustomTypeHandler](dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java) | 不支持模糊查询 + +# 基础理论项目研究 + + 基础理论 | 项目地址 +------|------------------------------------------ + IoC | https://gitee.com/eric-tutorial/easy-ioc + RPC | https://gitee.com/aohanhongzhi/my-rpc + +## jdk下载 + +https://www.graalvm.org/ + +https://www.azul.com/ +### 热加载技术 + +#### IDEA2024.2 + +**最新版本的IDEA2024.2 已经默认支持了热加载。也是需要在debug模式下。** + +#### 【废弃】第三方插件 + +这个针对的是[Jetbrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/releases) ,所以其他JDK慎重。实际测试jdk17 +21都可以正常运行。 + +https://github.com/HotswapProjects/HotswapAgent + +```shell +-XX:+AllowEnhancedClassRedefinition -XX:HotswapAgent=fatjar +``` + +> JDK22 识别不了这个参数了,也不需要这个插件,就可以热加载类了 + +![img_1.png](asset/img/update-vm-param.png) + +![img.png](asset/img/HotswapAgentLocation.png) + +注意需要在**debug**模式下启动才能生效。 + +修改代码后,Build -> Rebuild Project (Ctrl + Shift + F9) 重新编译和加载修改的类文件即可生效。 ## structure @@ -29,58 +77,99 @@ eric-dream ├── common -- 公共,配置文件,脚手架等 └── dao -- 数据持久层 ``` + 上面后缀server是服务治理模块。platform是业务应用模块。 **微服务=分布式开发+服务治理** +具体框架如下表: + + 技术 | 说明 | 官网 +----------------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------- + Spring Boot | 容器+MVC框架 | [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) + Gradle | 项目构建工具 | [https://gradle.com/](https://gradle.com/) + Spring Security | 认证和授权框架 | [https://spring.io/projects/spring-security](https://spring.io/projects/spring-security) + MyBatis | ORM框架 | [http://www.mybatis.org/mybatis-3/zh/index.html](http://www.mybatis.org/mybatis-3/zh/index.html) + MyBatisPlus | ORM框架补充 | [https://baomidou.com/](https://baomidou.com/) + MyBatisGenerator | 数据层代码生成 | [http://www.mybatis.org/generator/index.html](http://www.mybatis.org/generator/index.html) + doc-apis | 零侵入接口文档生成工具 | https://github.com/xpc1024/doc-apis + Swagger-UI | 文档生产工具 | [https://github.com/swagger-api/swagger-ui](https://github.com/swagger-api/swagger-ui) + Hibernator-Validator | 验证框架 | [http://hibernate.org/validator/](http://hibernate.org/validator/) + Elasticsearch | 搜索引擎 | [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch) + RabbitMq | 消息队列 | [https://www.rabbitmq.com/](https://www.rabbitmq.com/) + Redis | 分布式缓存 | [https://redis.io/](https://redis.io/) + redisson | 分布式锁,布隆过滤器 | https://github.com/redisson/redisson + MongoDb | NoSql数据库 | [https://www.mongodb.com/](https://www.mongodb.com/) + Docker | 应用容器引擎 | [https://www.docker.com/](https://www.docker.com/) + Hikari | SpringBoot默认数据库连接池 | [https://github.com/brettwooldridge/HikariCP](https://github.com/brettwooldridge/HikariCP) + Druid | 数据库连接池 | [https://github.com/alibaba/druid](https://github.com/alibaba/druid) + JWT | JWT登录支持 | [https://github.com/jwtk/jjwt](https://github.com/jwtk/jjwt) + LogStash | 日志收集 | [https://github.com/logstash/logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) + Lombok | 简化对象封装工具 | [https://github.com/rzwitserloot/lombok](https://github.com/rzwitserloot/lombok) + loc | 代码行数统计 | https://github.com/cgag/loc + ko-time | 轻量级的springboot项目性能分析工具,通过追踪方法调用链路以及对应的运行时长快速定位性能瓶颈 | https://gitee.com/huoyo/ko-time + okhttps | 强大轻量 且 前后端通用的 HTTP 客户端,同时支持 WebSocket 以及 Stomp 协议 | https://ok.zhxu.cn/ + +#### SpringBoot支持 + +https://spring.io/projects/spring-boot#support + ## gradle安装与配置 -https://hub.fastgit.org/GradleCN/GradleSide +https://github.com/GradleCN/GradleSide 如果IDEA自动下载gradle很慢。那么可以先提前安装好gradle,然后指定下安装目录即可。 ![](./asset/img/gradle-special-location.png) -![](./asset/img/gradle-wrapper.png) - - -技术 | 说明 | 官网 -----|----|---- -Spring Boot | 容器+MVC框架 | [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) -Gradle | 项目构建工具 | [https://gradle.com/](https://gradle.com/) -Spring Security | 认证和授权框架 | [https://spring.io/projects/spring-security](https://spring.io/projects/spring-security) -MyBatis | ORM框架 | [http://www.mybatis.org/mybatis-3/zh/index.html](http://www.mybatis.org/mybatis-3/zh/index.html) -MyBatisPlus |ORM框架补充 | [https://mybatis.plus/](https://mybatis.plus/) -MyBatisGenerator | 数据层代码生成 | [http://www.mybatis.org/generator/index.html](http://www.mybatis.org/generator/index.html) -Swagger-UI | 文档生产工具 | [https://github.com/swagger-api/swagger-ui](https://github.com/swagger-api/swagger-ui) -Hibernator-Validator | 验证框架 | [http://hibernate.org/validator/](http://hibernate.org/validator/) -Elasticsearch | 搜索引擎 | [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch) -RabbitMq | 消息队列 | [https://www.rabbitmq.com/](https://www.rabbitmq.com/) -Redis | 分布式缓存 | [https://redis.io/](https://redis.io/) -redisson | 分布式锁,布隆过滤器 | https://github.com/redisson/redisson -MongoDb | NoSql数据库 | [https://www.mongodb.com/](https://www.mongodb.com/) -Docker | 应用容器引擎 | [https://www.docker.com/](https://www.docker.com/) -Hikari | SpringBoot默认数据库连接池 | [https://github.com/brettwooldridge/HikariCP](https://github.com/brettwooldridge/HikariCP) -Druid | 数据库连接池 | [https://github.com/alibaba/druid](https://github.com/alibaba/druid) -JWT | JWT登录支持 | [https://github.com/jwtk/jjwt](https://github.com/jwtk/jjwt) -LogStash | 日志收集 | [https://github.com/logstash/logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) -Lombok | 简化对象封装工具 | [https://github.com/rzwitserloot/lombok](https://github.com/rzwitserloot/lombok) -loc |代码行数统计 | https://github.com/cgag/loc +![img.png](asset/img/gradle-wrapper.png) -#### SpringBoot支持 +腾讯的代理镜像 -https://spring.io/projects/spring-boot#support +https://mirrors.cloud.tencent.com/gradle/gradle-7.5.1-bin.zip + +https\://services.gradle.org/distributions/gradle-7.4-bin.zip + +![img_1.png](asset/img/gradle.png) ### 命令打包,跳过TEST + ```shell script ./gradlew clean bootJar -x test ``` + +```shell +./gradlew bootRun -x test +``` + +带上参数 + +``` +./gradlew bootRun -x test --args='--spring.profiles.active=test' +``` + ```shell ./gradlew dependencyInsight --dependency mybatis ``` + +### 构建 + +并行构建 + +```shell +gradle build -x test --parallel --build-cache +``` + > 需要解决多工程的依赖分析 -### 多模块构建,依赖关系解决 +### gradle多模块构建,依赖关系解决 + +```groovy + api project(':dao') +``` + +以下是 gradle6前后的使用方式: + ```groovy // implementation的依赖是不可以传递的而,entity需要被app依赖,所以需要加上 // implementation project(':entity') /* 子模块之间的依赖 */ - compile project(':entity') /* 子模块之间的依赖 */ +compile project(':entity') /* 子模块之间的依赖 */ ``` 1. [如何使用Gradle管理多模块Java项目](https://zhuanlan.zhihu.com/p/372585663) @@ -88,19 +177,49 @@ https://spring.io/projects/spring-boot#support ### 版本指定,类似dependencyManager ### docker自动化跑起来 + google出品的一个插件,可以直接将SpringBoot构建推送到Docker仓库 + ```groovy id "com.google.cloud.tools.jib" version "2.0.0" ``` ## 统一Long类型序列化 -前端JS内置的number类型是基于32位整数,Number类型的最大安全整数为9007199254740991,当Java Long型的值大小超过JS Number的最大安全整数时,超出此范围的整数值可能会被破坏,丢失精度。 +### 方案1 + +[BaseLongSerializer.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Fserializer%2FBaseLongSerializer.java) + +### 方案2 + +https://mp.weixin.qq.com/s/qhG9T0VdW4VkVy2VJJmftg + +```java +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; + +@Configuration +public class JacksonConfiguration { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + return builder -> { + // 把 Long 类型序列化为 String + builder.serializerByType(Long.class, ToStringSerializer.instance); + }; + } +} +``` + +前端JS内置的number类型是基于32位整数,Number类型的最大安全整数为9007199254740991,当Java Long型的值大小超过JS +Number的最大安全整数时,超出此范围的整数值可能会被破坏,丢失精度。 解决办法就是后端将超过精度的Long和long类型转成String给前端展示即可。 -> [JSON类库Jackson优雅序列化Java类 -](https://docs.qq.com/doc/DSFpuQkRrdk9xUlF6) +> [JSON类库Jackson优雅序列化Java类](https://docs.qq.com/doc/DSFpuQkRrdk9xUlF6) ## 统一序列化枚举 @@ -110,7 +229,9 @@ https://juejin.im/post/6844904196693557255 可能这种规范并不会被所有人认可,所以也可以用限制包的方式,自己配置指定包的枚举可以被统一处理。 #### Controller层 + ##### 入参 + ###### 表单提交 ![](./asset/img/枚举表单序列化.png) @@ -126,6 +247,7 @@ https://juejin.im/post/6844904196693557255 现在的问题在于jackson将枚举反序列化的时候需要使用code而不是ordinal。显然之前的Convert在这里貌似并没有什么作用。因为这里基本上全部靠jackson来序列化。 ##### 返回 + > 参考:[JSON类库Jackson优雅序列化Java枚举类 ](https://docs.qq.com/doc/DSFpuQkRrdk9xUlF6) @@ -134,17 +256,20 @@ https://juejin.im/post/6844904196693557255 如果发生无法正常解析的时候,那么可能是注入的bean无法使用 如果发现注入的bean无法解决json序列化问题,那么可以在`BaseEnum`加上这个注解 + ```java @JsonFormat(shape = JsonFormat.Shape.OBJECT) ``` #### ORM层 -> 参考 mybatis-plus:https://mp.baomidou.com/guide/enum.html +> +参考 [mybatis-plus官网](https://baomidou.com/pages/8390a4/#%E6%AD%A5%E9%AA%A41-%E5%A3%B0%E6%98%8E%E9%80%9A%E7%94%A8%E6%9E%9A%E4%B8%BE%E5%B1%9E%E6%80%A7) 1. [自动填充字段](https://docs.qq.com/doc/DSG5Zbk9RR1FHRVZE) #### 总结 + 通过上面方法,对数据库层和Controller层的转换操作,可以很好的处理枚举在应用中的形态,程序中可以很好的使用枚举了。 #### 自定义date的序列化器 @@ -153,13 +278,11 @@ https://blog.csdn.net/bandancer/article/details/84926383 [基于fastjson在mvc中解决enum类型序列化反序列化](https://zhuanlan.zhihu.com/p/121112597) - - ### 过滤器 [关于springboot中添加Filter的方法](https://www.jianshu.com/p/3d421fbce734) -### +### ``` WARN at com.zaxxer.hikari.pool.PoolBase.isConnectionAlive (PoolBase.java:184) - HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@63ec6a5a (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value. @@ -176,19 +299,48 @@ https://blog.csdn.net/qq_27127145/article/details/85775240 [MybatisPlus数据库加解密](https://gitee.com/aohanhongzhi/study/blob/dev/src/SpringBoot/Mybatis-plus%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AD%97%E6%AE%B5%E5%8A%A0%E8%A7%A3%E5%AF%86.md) [参考CustomTypeHandler](dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java) +https://blog.csdn.net/YW_Danny/article/details/120031966 + > https://blog.csdn.net/u012954706/article/details/105437768 +# 封装类型与基础类型 +1. 所有POJO类属性必须使用包装数据类型 +2. 所有的局部变量使用基本数据类型。 + +> 来自 《阿里巴巴Java开发规范》 # TODO -- [ ] 有的前端输入带有空格或者换行,到数据库存储可能会发生意想不到的bug,所以需要在反序列化的时候,需要将其中的非法字符去掉 -### 构建 +- [x] + 有的前端输入带有空格或者换行,到数据库存储可能会发生意想不到的bug,所以需要在反序列化的时候,需要将其中的非法字符去掉。需要设计一个方案做下全局的参数trim() -并行构建 +https://zhuanlan.zhihu.com/p/372585663 + +## 日志开启ssl + +macbook下测试端口 ```shell -gradle build -x test --parallel --build-cache +nc -vz -w 2 smtp.qq.com 465 ``` -https://zhuanlan.zhihu.com/p/372585663 +# 开发技术栈 + +## 压测工具 + +并发数,吞吐量,延时,最快最慢请求等。 + +https://github.com/hatoo/oha + +```shell +./oha-linux-amd64 --no-tui https://open.iciba.com/dsapi/?date=2023-05-03 +``` + +```shell +./oha-linux-amd64 -n 3000 https://open.iciba.com/dsapi/?date=2023-05-03 +``` + +## emoj表情 + +https://www.emojiall.com/ \ No newline at end of file diff --git a/SERIALIZE.md b/SERIALIZE.md index c96ad61f545513a99dc737d2f86667d6d1a7de9f..2262052f4a7ea5d6762fee02e414da9eb192dd41 100644 --- a/SERIALIZE.md +++ b/SERIALIZE.md @@ -1,6 +1,8 @@ # SpringBoot+Mybatisplus中枚举正反序列化的实际应用 -> 本文基于SpringBoot+Mybatisplus 框架就Java枚举的正反序列化的实际应用进行一次分析与研究,此外顺便带上DAO层关于枚举的操作,使得程序中完全使用枚举编程。由于SpringBoot内置的json处理器是jackson,所以本文的json相关处理也就是采用默认的jackson。 +> 本文基于SpringBoot+Mybatisplus +> +框架就Java枚举的正反序列化的实际应用进行一次分析与研究,此外顺便带上DAO层关于枚举的操作,使得程序中完全使用枚举编程。由于SpringBoot内置的json处理器是jackson,所以本文的json相关处理也就是采用默认的jackson。 ## 本文解决的痛点 @@ -25,25 +27,22 @@ N久之前,leo曾经问我枚举的应用,我清楚地记得菜鸟教程(htt ![](./asset/img/emum-respone.png) - 无论枚举要怎么使用,我还是按照自己的相关需求来实践了一把,由于项目中有很多枚举,使用和管理起来非常晕乎乎的。 需要把枚举与Integer转来转去,前端传输过来了一个Integer,需要手动将Integer转成枚举,存储到数据库的时候,又得将枚举转成Integer保存。 如果纯粹使用Integer传值,编码又不能知道这个数字代表啥意思,最后找来找去,注释也不全。不光是后端很是晕乎乎的。 前端由于也只接受了Integer,需要显示文字的时候,只能前后端共同定,一旦后端修改了枚举,那么前端则必须同步修改。 为了解决这个问题,我在网上找了一些解决办法,但是都不尽人意。最后折腾了jackson源码并求助于jackson的维护者解决了枚举正反序列化的问题。 - ## 基础框架 - 框架 | 官网 | 版本 - --- | --- | --- - SpringBoot| https://spring.io/projects/spring-boot | V2.2.9-GA - SpringWebMVC| https://spring.io/projects/spring-framework | 5.2.8.REALEASE - Mybatis-plus | https://mybatis.plus/ | 3.4.0 - jackson | https://github.com/FasterXML/jackson | 2.10.5 - - > 上面的框架的各个版本可能代码有点差别,但是基本思想都是一样的,所以版本不会有很大影响。此外其他计算机语言或者框架实现本文的思想都是可以的。 + 框架 | 官网 | 版本 +--------------|---------------------------------------------|---------------- + SpringBoot | https://spring.io/projects/spring-boot | V2.2.9-GA + SpringWebMVC | https://spring.io/projects/spring-framework | 5.2.8.REALEASE + Mybatis-plus | https://mybatis.plus/ | 3.4.0 + jackson | https://github.com/FasterXML/jackson | 2.10.5 +> 上面的框架的各个版本可能代码有点差别,但是基本思想都是一样的,所以版本不会有很大影响。此外其他计算机语言或者框架实现本文的思想都是可以的。 ## 模型介绍 @@ -51,8 +50,10 @@ N久之前,leo曾经问我枚举的应用,我清楚地记得菜鸟教程(htt ![](./asset/img/structure.png) -本文的重点是枚举的正反序列化,但是为了让整个枚举在工程中的应用比较完整,也会描述下枚举在DAO层的操作。jackson的正反序列化主要应用在Controller层的`参数接收`与`结果返回`。在参数接收的时候有两种形式,一种的前端通过表单提交的数据,另一种是从body提交的json数据,两种有很大的区别,在Controller的方法里面主要体现在body提交的json数据需要在对象前面加上`@RequestBody`.当然两者本质上有点区别,由于表单提交的不是json,所以无法采用json反序列化,但是本文中会顺带描述到表单提交的数据如何转换成枚举。 - +本文的重点是枚举的正反序列化,但是为了让整个枚举在工程中的应用比较完整,也会描述下枚举在DAO层的操作。jackson的正反序列化主要应用在Controller层的`参数接收` +与`结果返回` +。在参数接收的时候有两种形式,一种的前端通过表单提交的数据,另一种是从body提交的json数据,两种有很大的区别,在Controller的方法里面主要体现在body提交的json数据需要在对象前面加上`@RequestBody` +.当然两者本质上有点区别,由于表单提交的不是json,所以无法采用json反序列化,但是本文中会顺带描述到表单提交的数据如何转换成枚举。 ## show you code @@ -65,9 +66,9 @@ https://gitee.com/eric-tutorial/SpringCloud-multiple-gradle ### 定义枚举 ```java -public enum GenderEnum { - - BOY(100, "男"), GIRL(200, "女"),UNKNOWN(0, "未知"); +public enum GenderEnum { + + BOY(100, "男"), GIRL(200, "女"), UNKNOWN(0, "未知"); private final Integer code; @@ -77,41 +78,46 @@ public enum GenderEnum { this.code = code; this.description = description; } - + } ``` + ### 接受参数的对象 ```java + @Data public class UserParam { - + @NotBlank(message = "name不能为空") String name; - + @NotNull(message = "gender为1或者2") GenderEnum gender; - + @NotNull(message = "age不能为空") Integer age; - + } ``` + 注意参数Field的上面校验注解不可以是lombok的,否则会改变字节码信息,导致类的Field与值无法识别,最后枚举反序列化失败 ### Controller POST方法 + ```java @PostMapping("add/body") - public BaseResponseVO saveBody(@Valid @RequestBody UserParam userParam) { - UserModel userModel = userService.add(userParam); +public BaseResponseVO saveBody(@Valid @RequestBody UserParam userParam){ + UserModel userModel=userService.add(userParam); return BaseResponseVO.success(userModel); - } + } ``` 上面代码可以看出来框架在接受参数的时候将网络传输过来的数据进行了反序列化,在返回给前端的时候进行了正序列化成json返回的。 默认的jackson是无法直接按照`GenderEnum`中的`code`来正反序列化枚举的,因为jackson有一套自己的枚举序列化机制,从源代码中看出来, -它是按照`java.lang.Enum.class`的`name`和`ordinal`来正反序列化的。但是这个不能满足我自己定义的`code`和`description`来正反序列化的需求。 +它是按照`java.lang.Enum.class`的`name`和`ordinal`来正反序列化的。但是这个不能满足我自己定义的`code`和`description` +来正反序列化的需求。 因此我在网上搜了下,看看有木有人完成这样的需求,我想这个需求应该比较正常,网上一搜果然有很多,但是能实现的却没几个。 于是经过自己折腾,很快就有了下面的代码(最后发现都是采用默认的jackson枚举正反序列化器,并不满足需求,需要自己重写序列化器)。 @@ -141,29 +147,34 @@ public interface BaseEnum { ``` #### 正序列化器 + ```java + @Slf4j public class BaseEnumSerializer extends JsonSerializer { @Override public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException { - + log.info("\n====>开始序列化[{}]", value); gen.writeStartObject(); gen.writeNumberField("code", value.code()); gen.writeStringField("description", value.description()); gen.writeEndObject(); - + } } ``` -效果就是既返回`code`和`description`,前端既知道`code`也知道`description`.`description`可以直接显示,`code`可以用来返回给后端的操作.前端再也不用同步修改`description`了,也不需要自己判断`code`是啥意思,直接显示`description`即可.皆大欢喜. +效果就是既返回`code`和`description`,前端既知道`code`也知道`description`.`description`可以直接显示,`code` +可以用来返回给后端的操作.前端再也不用同步修改`description`了,也不需要自己判断`code`是啥意思,直接显示`description` +即可.皆大欢喜. #### 反序列化器 ```java + @Slf4j public class BaseEnumDeserializer extends JsonDeserializer { @Override @@ -179,10 +190,10 @@ public class BaseEnumDeserializer extends JsonDeserializer { JsonStreamContext parsingContext = p.getParsingContext(); String currentName = parsingContext.getCurrentName();//字段名 Object currentValue = parsingContext.getCurrentValue();//前端注入的对象(ResDTO) - Field field = ReflectionUtils.getField(currentValue.getClass(), currentName); // 通过对象和属性名获取属性的类型 - // 获取对应得枚举类 + Field field = ReflectionUtils.getField(currentValue.getClass(), currentName); // 通过对象和属性名获取属性的类型 + // 获取对应得枚举类 Class enumClass = field.getType(); - // 根据对应的值和枚举类获取相应的枚举值 + // 根据对应的值和枚举类获取相应的枚举值 BaseEnum anEnum = DefaultInputJsonToEnum.getEnum(inputParameter, enumClass); log.info("\n====>测试反序列化枚举[{}]==>[{}.{}]", inputParameter, anEnum.getClass(), anEnum); return anEnum; @@ -200,111 +211,113 @@ public class BaseEnumDeserializer extends JsonDeserializer { ```java @Bean - public Jackson2ObjectMapperBuilderCustomizer enumCustomizer() { +public Jackson2ObjectMapperBuilderCustomizer enumCustomizer(){ // 将枚举转成json返回给前端 - return jacksonObjectMapperBuilder -> { + return jacksonObjectMapperBuilder->{ // 自定义序列化器注入 - Map, JsonSerializer> serializers = new LinkedHashMap<>(); - serializers.put(BaseEnum.class, new BaseEnumSerializer()); - jacksonObjectMapperBuilder.serializersByType(serializers); + Map,JsonSerializer>serializers=new LinkedHashMap<>(); + serializers.put(BaseEnum.class,new BaseEnumSerializer()); + jacksonObjectMapperBuilder.serializersByType(serializers); // 自定义反序列化器注入,这里的注入貌似效果不行 - Map, JsonDeserializer> deserializers = new LinkedHashMap<>(); - deserializers.put(BaseEnum.class, new BaseEnumDeserializer()); - jacksonObjectMapperBuilder.deserializersByType(deserializers); + Map,JsonDeserializer>deserializers=new LinkedHashMap<>(); + deserializers.put(BaseEnum.class,new BaseEnumDeserializer()); + jacksonObjectMapperBuilder.deserializersByType(deserializers); }; - } + } ``` -经过测试,枚举序列化后返回到前端的效果如下,与期望的效果一致,这样的好处就是前端不需要管数字是啥意思,直接显示`description`即可,无论后端枚举是否修改,前端都不需要关心了。 +经过测试,枚举序列化后返回到前端的效果如下,与期望的效果一致,这样的好处就是前端不需要管数字是啥意思,直接显示`description` +即可,无论后端枚举是否修改,前端都不需要关心了。 ![](./asset/img/enums.png) -经过反复测试与人分享成果的时候,发现一个非常严重的问题,虽然前端接收参数的时候也可以反序列化成枚举,但是实际上没有按照`code`来反序列化。最后只能把jackson的源代码拉下来调试,经过调试发现,jackson反序列化的时候一直使用的是默认的枚举反序列化器,并没有使用自定义枚举反序列化器。 +经过反复测试与人分享成果的时候,发现一个非常严重的问题,虽然前端接收参数的时候也可以反序列化成枚举,但是实际上没有按照`code` +来反序列化。最后只能把jackson的源代码拉下来调试,经过调试发现,jackson反序列化的时候一直使用的是默认的枚举反序列化器,并没有使用自定义枚举反序列化器。 com.fasterxml.jackson.databind.deser.BasicDeserializerFactory#createEnumDeserializer - ```java /** - * Factory method for constructing serializers of {@link Enum} types. - */ - @Override - public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, - JavaType type, BeanDescription beanDesc) + * Factory method for constructing serializers of {@link Enum} types. + */ +@Override +public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, + JavaType type,BeanDescription beanDesc) throws JsonMappingException - { - final DeserializationConfig config = ctxt.getConfig(); - final Class enumClass = type.getRawClass(); + { +final DeserializationConfig config=ctxt.getConfig(); +final Class enumClass=type.getRawClass(); // 23-Nov-2010, tatu: Custom deserializer? - JsonDeserializer deser = _findCustomEnumDeserializer(enumClass, config, beanDesc); - - if (deser == null) { - // 12-Feb-2020, tatu: while we can't really create real deserializer for `Enum.class`, - // it is necessary to allow it in one specific case: see [databind#2605] for details - // but basically it can be used as polymorphic base. - // We could check `type.getTypeHandler()` to look for that case but seems like we - // may as well simply create placeholder (AbstractDeserializer) regardless - if (enumClass == Enum.class) { - return AbstractDeserializer.constructForNonPOJO(beanDesc); - } + JsonDeserializer deser=_findCustomEnumDeserializer(enumClass,config,beanDesc); + + if(deser==null){ + // 12-Feb-2020, tatu: while we can't really create real deserializer for `Enum.class`, + // it is necessary to allow it in one specific case: see [databind#2605] for details + // but basically it can be used as polymorphic base. + // We could check `type.getTypeHandler()` to look for that case but seems like we + // may as well simply create placeholder (AbstractDeserializer) regardless + if(enumClass==Enum.class){ + return AbstractDeserializer.constructForNonPOJO(beanDesc); + } - ValueInstantiator valueInstantiator = _constructDefaultValueInstantiator(ctxt, beanDesc); - SettableBeanProperty[] creatorProps = (valueInstantiator == null) ? null - : valueInstantiator.getFromObjectArguments(ctxt.getConfig()); - // May have @JsonCreator for static factory method: - for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) { - if (_hasCreatorAnnotation(ctxt, factory)) { - if (factory.getParameterCount() == 0) { // [databind#960] - deser = EnumDeserializer.deserializerForNoArgsCreator(config, enumClass, factory); - break; - } - Class returnType = factory.getRawReturnType(); - // usually should be class, but may be just plain Enum (for Enum.valueOf()?) - if (returnType.isAssignableFrom(enumClass)) { - deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps); - break; - } - } - } - - // Need to consider @JsonValue if one found - if (deser == null) { - deser = new EnumDeserializer(constructEnumResolver(enumClass, - config, beanDesc.findJsonValueAccessor()), - config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)); - } + ValueInstantiator valueInstantiator=_constructDefaultValueInstantiator(ctxt,beanDesc); + SettableBeanProperty[]creatorProps=(valueInstantiator==null)?null + :valueInstantiator.getFromObjectArguments(ctxt.getConfig()); + // May have @JsonCreator for static factory method: + for(AnnotatedMethod factory:beanDesc.getFactoryMethods()){ + if(_hasCreatorAnnotation(ctxt,factory)){ + if(factory.getParameterCount()==0){ // [databind#960] + deser=EnumDeserializer.deserializerForNoArgsCreator(config,enumClass,factory); + break; + } + Class returnType=factory.getRawReturnType(); + // usually should be class, but may be just plain Enum (for Enum.valueOf()?) + if(returnType.isAssignableFrom(enumClass)){ + deser=EnumDeserializer.deserializerForCreator(config,enumClass,factory,valueInstantiator,creatorProps); + break; + } + } + } + + // Need to consider @JsonValue if one found + if(deser==null){ + deser=new EnumDeserializer(constructEnumResolver(enumClass, + config,beanDesc.findJsonValueAccessor()), + config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)); + } } // and then post-process it too - if (_factoryConfig.hasDeserializerModifiers()) { - for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) { - deser = mod.modifyEnumDeserializer(config, type, beanDesc, deser); - } + if(_factoryConfig.hasDeserializerModifiers()){ + for(BeanDeserializerModifier mod:_factoryConfig.deserializerModifiers()){ + deser=mod.modifyEnumDeserializer(config,type,beanDesc,deser); + } } return deser; - } + } ``` 从上面可以看出来枚举反系列化器是怎么找到的.仔细阅读后发现,上面并没有按照接口 `BaseEnum` 来查找反序列化器,这也是为啥自定义的反序列化器没有生效的原因. 既然我发现了这个问题,我直接在github拉下来了jackson代码,然后修改成按照接口查找自定义反序列化器的方式提交了我的代码. + ```java - List interfaces = type.getInterfaces(); + List interfaces=type.getInterfaces(); - for (JavaType javaType : interfaces) { - Class rawClass = javaType.getRawClass(); - deser = _findCustomEnumDeserializer(rawClass, config, beanDesc); - if (deser != null) { - return deser; + for(JavaType javaType:interfaces){ + Class rawClass=javaType.getRawClass(); + deser=_findCustomEnumDeserializer(rawClass,config,beanDesc); + if(deser!=null){ + return deser; + } } - } ``` + pull request之后,管理者很快给我回复了。我们来回扯了几个回合之后,我们得到一个更加合理的解决办法. 这个问题,这个也是本文的重点。就是重写查找枚举反序列化器的方法,把我写的代码放在一个重写类里面即可. - https://github.com/FasterXML/jackson-databind/pull/2842 ![](./asset/img/github.com_FasterXML_jackson-databind_pull_28422.png) @@ -315,32 +328,32 @@ com.fasterxml.jackson.databind.module.SimpleDeserializers#findEnumDeserializer ```java @Override - public JsonDeserializer findEnumDeserializer(Class type, - DeserializationConfig config, BeanDescription beanDesc) +public JsonDeserializer findEnumDeserializer(Class type, + DeserializationConfig config,BeanDescription beanDesc) throws JsonMappingException - { - if (_classMappings == null) { - return null; - } - JsonDeserializer deser = _classMappings.get(new ClassKey(type)); - if (deser == null) { - // 29-Sep-2019, tatu: Not 100% sure this is workable logic but leaving - // as is (wrt [databind#2457]. Probably works ok since this covers direct - // sub-classes of `Enum`; but even if custom sub-classes aren't, unlikely - // mapping for those ever requested for deserialization - if (_hasEnumDeserializer && type.isEnum()) { - deser = _classMappings.get(new ClassKey(Enum.class)); - } + { + if(_classMappings==null){ + return null; + } + JsonDeserializer deser=_classMappings.get(new ClassKey(type)); + if(deser==null){ + // 29-Sep-2019, tatu: Not 100% sure this is workable logic but leaving + // as is (wrt [databind#2457]. Probably works ok since this covers direct + // sub-classes of `Enum`; but even if custom sub-classes aren't, unlikely + // mapping for those ever requested for deserialization + if(_hasEnumDeserializer&&type.isEnum()){ + deser=_classMappings.get(new ClassKey(Enum.class)); + } } return deser; - } + } ``` 从上面看出来这个就是查找枚举反序列化器的逻辑,重写SimpleDeserializers类即可.上面这个代码是无法按照接口找到反序列化器的,所以重写它,让它按照我期望的接口方式找到即可,最后也成功了. - 此外还从源码中分析出来 -为啥有的枚举反序列化就能正常,但是有的不能完成翻序列化。原来默认的枚举反序列化器是按照`ordinal`来反序列化的,也就是说只有当`code`与`ordinal`一致的时候就会造成一种假象, +为啥有的枚举反序列化就能正常,但是有的不能完成翻序列化。原来默认的枚举反序列化器是按照`ordinal` +来反序列化的,也就是说只有当`code`与`ordinal`一致的时候就会造成一种假象, 以为是`code`反序列化来的,其实依旧是`ordinal`反序列化来的。 从下面代码中可以看出来,枚举存储在数组中,而`ordinal`刚好是下标. @@ -349,46 +362,46 @@ com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize ```java @Override - public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException - { - JsonToken curr = p.currentToken(); - +public Object deserialize(JsonParser p,DeserializationContext ctxt)throws IOException + { + JsonToken curr=p.currentToken(); + // Usually should just get string value: - if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) { - CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) - ? _getToStringLookup(ctxt) : _lookupByName; - final String name = p.getText(); - Object result = lookup.find(name); - if (result == null) { - return _deserializeAltString(p, ctxt, lookup, name); - } - return result; + if(curr==JsonToken.VALUE_STRING||curr==JsonToken.FIELD_NAME){ + CompactStringObjectMap lookup=ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + ?_getToStringLookup(ctxt):_lookupByName; +final String name=p.getText(); + Object result=lookup.find(name); + if(result==null){ + return _deserializeAltString(p,ctxt,lookup,name); + } + return result; } // But let's consider int acceptable as well (if within ordinal range) - if (curr == JsonToken.VALUE_NUMBER_INT) { - // ... unless told not to do that - int index = p.getIntValue(); - if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { - return ctxt.handleWeirdNumberValue(_enumClass(), index, - "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow" - ); - } - if (index >= 0 && index < _enumsByIndex.length) { - return _enumsByIndex[index]; - } - if ((_enumDefaultValue != null) - && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { - return _enumDefaultValue; - } - if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { - return ctxt.handleWeirdNumberValue(_enumClass(), index, - "index value outside legal index range [0..%s]", - _enumsByIndex.length-1); - } - return null; + if(curr==JsonToken.VALUE_NUMBER_INT){ + // ... unless told not to do that + int index=p.getIntValue(); + if(ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)){ + return ctxt.handleWeirdNumberValue(_enumClass(),index, + "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow" + ); + } + if(index>=0&&index<_enumsByIndex.length){ + return _enumsByIndex[index]; + } + if((_enumDefaultValue!=null) + &&ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)){ + return _enumDefaultValue; + } + if(!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)){ + return ctxt.handleWeirdNumberValue(_enumClass(),index, + "index value outside legal index range [0..%s]", + _enumsByIndex.length-1); + } + return null; + } + return _deserializeOther(p,ctxt); } - return _deserializeOther(p, ctxt); - } ``` @@ -419,7 +432,6 @@ com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize 调试过程中最让人百思不得解的是,自定义的正反枚举序列化器,序列化器是可以按照自己定义的接口来序列化,但是反序列化不行。最后经过反复调试,发现正反序列化过程有点区别,正序列化的时候会找父类找接口,按照父类或者接口定义的序列化器来序列化。而反序列化的时候不会。体会一下,可以理解成一个正序列化的时候,准确度可以忽略,反正都是丢出去的。但是反序列化的时候必须保证精度,否则无法正确反序列化,那么对应的对象无法获取到正确的值。瞎扯一下.好比,银行存钱的时候不需要密码,取钱的时候就需要密码一样,看似一个对称的过程,但是校验机制还是有点区别的,可以细细体会这种方式的必要性。 - #### 重写SimpleDeserializers的findEnumDeserializer方法 重写了这个方法之后,把我原本写在源代码的逻辑搬出来了,很快就解决了枚举无法找到自定义反序列化器的问题。 @@ -428,18 +440,19 @@ com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize public class SimpleDeserializersWrapper extends SimpleDeserializers { static final Logger logger = LoggerFactory.getLogger(SimpleDeserializersWrapper.class); + @Override public JsonDeserializer findEnumDeserializer(Class type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc); - + if (enumDeserializer != null) { return enumDeserializer; } for (Class typeInterface : type.getInterfaces()) { enumDeserializer = this._classMappings.get(new ClassKey(typeInterface)); if (enumDeserializer != null) { - logger.info("\n====>重写枚举查找逻辑[{}]",enumDeserializer); + logger.info("\n====>重写枚举查找逻辑[{}]", enumDeserializer); return enumDeserializer; } } @@ -456,18 +469,18 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { ```java @Bean - public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { - SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper(); - deserializers.addDeserializer(BaseEnum.class, new BaseEnumDeserializer()); +public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder){ + SimpleDeserializersWrapper deserializers=new SimpleDeserializersWrapper(); + deserializers.addDeserializer(BaseEnum.class,new BaseEnumDeserializer()); - SimpleModule simpleModule = new SimpleModule(); + SimpleModule simpleModule=new SimpleModule(); simpleModule.setDeserializers(deserializers); - simpleModule.addSerializer(BaseEnum.class, new BaseEnumSerializer()); + simpleModule.addSerializer(BaseEnum.class,new BaseEnumSerializer()); - ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + ObjectMapper objectMapper=builder.createXmlMapper(false).build(); objectMapper.registerModule(simpleModule); return objectMapper; - } + } ``` ### 其他序列化应用场景 @@ -480,7 +493,8 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { #### Long类型超过精度统一序列化 -本项目中还有一个应用就是针对Long类型超过浏览器可识别的精度范围就直接序列化成String类型,这样就不用再每个返回的地方都一一String.valueof()的转化了。 +本项目中还有一个应用就是针对Long类型超过浏览器可识别的精度范围就直接序列化成String类型,这样就不用再每个返回的地方都一一String.valueof() +的转化了。 > Long类型太长了,在浏览器里面是无法正确显示的,但是实际值是没有问题的,只是看起来不对而已。 @@ -490,30 +504,26 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { ![](./asset/img/converter.png) - - ### DAO 层处理枚举存到数据库 - 具体就是在枚举的属性上面上一个注解 ```java @EnumValue//标记数据库存的值是code - private final Integer code; +private final Integer code; ``` + 此外在yaml配置文件中指定枚举所在的包。 + ```java mybatis-plus: - type-enums-package: hxy.dream.entity.enums + type-enums-package:hxy.dream.entity.enums ``` + 上面两步,就是借助mybatis-plus完成了枚举存储到数据库,与读取的时候转换的问题。这个比较简单,框架也就是做这些事情的,让开发者专注于业务,而不是实现技术的本身(不是说不要钻研技术底层原理)。 > 参考 mybatis-plus:https://mp.baomidou.com/guide/enum.html - - - - ## 总结 以上的操作完成了枚举的从前端接收,反序列化成枚举对象在程序中表达。然后再存储到数据库中。从数据库中取code转成枚举,在程序中表达,再序列化枚举后传输给前端。一个非常完整的循环,基本上满足了程序中对枚举使用的需求。 @@ -526,6 +536,107 @@ mybatis-plus: https://gitee.com/-/ide/project/aohanhongzhi/springboot-base/edit/master/-/src/main/java/hxy/base/server/entity/enums/BaseStatusCodeEnum.java +# mybatis xml兼容处理 + +mybatis的xml文件会报下面错误 + +```java +org.mybatis.spring.MyBatisSystemException:nested exception is org.apache.ibatis.exceptions.PersistenceException: + ### Error querying database.Cause:java.lang.IllegalArgumentException:invalid comparison:com.bosch.project.install.domain.enums.InstallStatusEnum and java.lang.String + ### Cause:java.lang.IllegalArgumentException:invalid comparison:com.bosch.project.install.domain.enums.InstallStatusEnum and java.lang.String + + at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96) + at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441) + at jdk.proxy2/jdk.proxy2.$Proxy140.selectList(Unknown Source) + at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224) + at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:166) + at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:77) + at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148) + at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89) + at jdk.proxy2/jdk.proxy2.$Proxy183.selectSpfInstallApplicationJoinList(Unknown Source) + at com.bosch.project.install.mapper.InstallApplicationMapperTest.test(InstallApplicationMapperTest.java:26) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74) + at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84) + at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) + at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) + at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) + at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) + at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) + at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) + at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) + at org.junit.runners.ParentRunner.run(ParentRunner.java:363) + at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) + at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) + at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) + at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) + at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) + at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54) + Caused by:org.apache.ibatis.exceptions.PersistenceException: + ### Error querying database.Cause:java.lang.IllegalArgumentException:invalid comparison:com.bosch.project.install.domain.enums.InstallStatusEnum and java.lang.String + ### Cause:java.lang.IllegalArgumentException:invalid comparison:com.bosch.project.install.domain.enums.InstallStatusEnum and java.lang.String + at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) + at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149) + at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427) + ...40more + Caused by:java.lang.IllegalArgumentException:invalid comparison:com.bosch.project.install.domain.enums.InstallStatusEnum and java.lang.String + at org.apache.ibatis.ognl.OgnlOps.compareWithConversion(OgnlOps.java:98) + at org.apache.ibatis.ognl.OgnlOps.isEqual(OgnlOps.java:153) + at org.apache.ibatis.ognl.OgnlOps.equal(OgnlOps.java:814) + at org.apache.ibatis.ognl.ASTNotEq.getValueBody(ASTNotEq.java:53) + at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212) + at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258) + at org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:61) + at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212) + at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258) + at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:560) + at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:524) + at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:46) + at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:32) + at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:34) + at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) + at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32) + at org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:55) + at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) + at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32) + at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39) + at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:305) + at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:82) + at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) + at jdk.proxy2/jdk.proxy2.$Proxy238.query(Unknown Source) + at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) + ...46more + +``` + +![img_1.png](asset/img/xml1.png) + +删除下面圈出内容即可,其他不需要变化。查询结果映射也是正常的,没有问题。 + +![img_2.png](asset/img/xml2.png) + # 问题拓展 有一位网友搜了很多的文章内容,都没有找到有效的,最后扒到了我这篇文章。但是使用的时候遇到了一个问题。下面这段代码总是无法获取到已经注入成功的BaseEnumDeserializer对象, @@ -533,20 +644,22 @@ https://gitee.com/-/ide/project/aohanhongzhi/springboot-base/edit/master/-/src/m ```java -for (Class typeInterface : type.getInterfaces()) { - enumDeserializer = this._classMappings.get(new ClassKey(typeInterface)); - if (enumDeserializer != null) { - logger.info("\n重写枚举查找逻辑[{}]", enumDeserializer); +for(Class typeInterface:type.getInterfaces()){ + enumDeserializer=this._classMappings.get(new ClassKey(typeInterface)); + if(enumDeserializer!=null){ + logger.info("\n重写枚举查找逻辑[{}]",enumDeserializer); return enumDeserializer; - } -} -return null; + } + } + return null; ``` 问题分析,调试定位出问题肯定不是序列化器注入的问题,因为已经在`this._classMappings`中存在了。那么问题只能出在HashMap的key上面了。通过调试 + ```java this._classMappings.get(key); ``` + 这一个比较发现 ![](asset/img/hashMap-get.png) @@ -557,12 +670,15 @@ this._classMappings.get(key); ![](asset/img/databind-type-Classkey.png) ![](asset/img/classmate-util-classkey.png) 很显然这位老哥import的时候包可能导入错误的了。 + ```java // 下面这个才是正确的 - import com.fasterxml.jackson.databind.type.ClassKey; - // 下面这个是错误的。 - import com.fasterxml.classmate.util.ClassKey; + +import com.fasterxml.jackson.databind.type.ClassKey; +// 下面这个是错误的。 +import com.fasterxml.classmate.util.ClassKey; ``` + ![](asset/img/hashMap-equals.png) # fastjson枚举自定义序列化器 @@ -571,4 +687,121 @@ https://blog.csdn.net/qq_26680031/article/details/83473643 # 数据库的json序列化处理,将数据库存储的json字段自动映射成对象 -https://github.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-typehandler/src/main/java/com/baomidou/mybatisplus/samples/typehandler \ No newline at end of file +https://github.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-typehandler/src/main/java/com/baomidou/mybatisplus/samples/typehandler + +# json时间格式化 + +### 编码 + +参考 [DateJsonSerializer](common/src/main/java/hxy/dream/common/serializer/DateJsonSerializer.java) + +1. 线程安全 + +```java + DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + ZoneId zoneId=ZoneId.systemDefault(); + LocalDateTime localDateTime=date.toInstant().atZone(zoneId).toLocalDateTime(); + String yearMonth=localDateTime.format(dtf); +``` + +2. 线程不安全 + +```java + public static final SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dateStirng=format.format(date) +``` + +> https://www.cnblogs.com/xwzp/p/14685452.html + + +java.sql.Date的序列化,默认是转成时间戳。默认序列化器:com.fasterxml.jackson.databind.ser.std.SqlDateSerializer + +https://blog.csdn.net/weixin_44299027/article/details/104516280 + +```java + // 修改配置即可。从com.fasterxml.jackson.databind.ser.std.SqlDateSerializer源代码看出来的。 没必要自定义一个序列化器。 + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false); +``` + +或者可以试试 + +https://blog.csdn.net/qq_21567385/article/details/107303684 + +```yaml +spring: + jackson: + # 返回时间戳 + serialization: + write-dates-as-timestamps: true +``` + +```yaml +spring: + jackson: + # 格式化返回时间 yyyy-MM-dd HH:mm:ss + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 +``` + +个别注解配置太繁琐 + +```java +@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8") +``` + +java.sql.Date与java.util.Date的两者区别: + +区别: +java.util.Date:年月日 时分秒 +java.sql.Date:年月日 + +联系: +`java.sql.Date(子类) extends java.util.Date (父类 + + + +# @JsonInclude(Include.NON_NULL)全局配置 + +配置对象Field属性,当值为null的时候,就可以序列化忽略掉。 + +![img.png](asset/img/jackson-serialize-null.png) + +https://blog.csdn.net/u012477144/article/details/99731418 + +``` +spring.jackson.default-property-inclusion=non_null #这个应该是全局的 +``` + +或者 + +```java +@Configuration +@EnableWebMvc +@Slf4j +public class WebMvcConfig extends WebMvcConfigurerAdapter { + + //@JsonInclude(Include.NON_NULL) SpringMVC层的配置(也不算全局配置,估计不影响redis啥的Jackson序列化啥的) + @Override + public void configureMessageConverters(List> converters){ + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() + .serializationInclusion(JsonInclude.Include.NON_NULL); + converters.add(new MappingJackson2HttpMessageConverter(builder.build())); + } +} +``` +# 解决 redis 序列化 java8 LocalDateTime 问题 + +https://blog.csdn.net/zhuzhoulin/article/details/106758473 + +### 配置文件 + +![img.png](asset/img/jackson-date-format.png) + + +### 序列化作用顺序 + +全局配置的大于单个注解,测试 [hxy.dream.util.JacksonTest.testSqlDate](app/src/test/java/hxy/dream/util/JacksonTest.java) + +### Java 8 date/time type `java.time.LocalDateTime` not supported by default + +Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: hxy.dream.entity.vo.BaseResponseVO["data"]->java.util.ArrayList[0]->hxy.dream.dao.model.UserModel["createTime"]) \ No newline at end of file diff --git a/SpringBoot.md b/SpringBoot.md new file mode 100644 index 0000000000000000000000000000000000000000..db19255f4d1c5cfadf1982aec3390db5e998771b --- /dev/null +++ b/SpringBoot.md @@ -0,0 +1,15 @@ +SpringBoot +=== + +[面试官:SpringBoot如何实现缓存预热?](https://mp.weixin.qq.com/s/6h6IGjBp3hYduXIcE-coYA) + +# SpringBoot AutoConfig + +SpringBoot的 自动装配看这个类就可以了 ConfigurationClassPostProcessor + +### SpringBoot 启动而执行。 + +1. 使用ApplicationListener启动监听事件实现缓存预热。 +2. 使用 @PostConstruct 注解实现缓存预热。 +3. 使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。 +4. 通过实现 InitializingBean 接口,并重写 afterPropertiesSet 方法实现缓存预热。 \ No newline at end of file diff --git a/SpringBootAutoConfig.md b/SpringBootAutoConfig.md deleted file mode 100644 index 7bb493b52f4cba56879ed5b433bac3f28290a1d8..0000000000000000000000000000000000000000 --- a/SpringBootAutoConfig.md +++ /dev/null @@ -1,3 +0,0 @@ -SpringBoot AutoConfig -=== -SpringBoot的 自动装配看这个类就可以了 ConfigurationClassPostProcessor diff --git a/TRANSACTION.md b/TRANSACTION.md index 96df8a56ac152fabb17e727834e4b48f707476b2..5ab05eccaf77ecd1c984bdfb86c2f649a4716b57 100644 --- a/TRANSACTION.md +++ b/TRANSACTION.md @@ -75,8 +75,8 @@ Spring的事务五大隔离机制与数据库也是一一对应的,就是多 [Spring事务隔离级别与数据库隔离级别不一致时,该以谁为准?](https://blog.csdn.net/weixin_44259720/article/details/112826960) -上面这个在实践的时候,第一次未成功复现,原因是因为mybatis的一级缓存,导致同一个事务的第二次查询压根没有实际执行,而是读取了第一次的缓存。 -只要禁用了 +上面这个在实践的时候,第一次未成功复现,原因是因为mybatis的一级缓存(需要禁用,才可以),导致同一个事务的第二次查询压根没有实际执行,而是读取了第一次的缓存。 + ```shell ==> Preparing: SELECT id,name,age,gender,password,create_time,update_time,deleted FROM user_model WHERE id=? AND deleted=0 @@ -115,5 +115,6 @@ Exception in thread "Thread-3" java.lang.ArithmeticException: / by zero # 拓展 +[详解Spring事务的7种传播行为](https://mp.weixin.qq.com/s/dzKmS2HtoS0Yt4dKFuwSBg) [分布式事务有这一篇就够了!](https://zhuanlan.zhihu.com/p/263555694) : 鲁友心推荐 \ No newline at end of file diff --git a/ThreadPool.md b/ThreadPool.md index 895a0eacc47914c54347791e98fe2fb756c06fb7..f4d5f04873b1561c211d5670cad5352db217b872 100644 --- a/ThreadPool.md +++ b/ThreadPool.md @@ -25,8 +25,18 @@ } ``` +直接调用 [ThreadPoolExecutorTool.java](common%2Fsrc%2Fmain%2Fjava%2Fhxy%2Fdream%2Fcommon%2Futil%2FThreadPoolExecutorTool.java) + ## SpringBoot的自带线程池 +```java + /** + * 注意这个线程池的最大线程数队列长度 + */ + @Autowired + private ThreadPoolTaskExecutor applicationTaskExecutor; + ``` + ![](asset/img/SpringBoot上下文调试.png) ![](asset/img/定位到自带线程池.png) ![](./asset/img/SpringBoot的默认线程池.png) diff --git a/app/build.gradle b/app/build.gradle index de0e01dc7155f6e0d2177cf6427012fce413d236..7ef0c88ca86ad95e130caab00b33c3dc80b6e5a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,5 +7,16 @@ bootJar.enabled = true dependencies { api project(':common') - testImplementation group: 'junit', name: 'junit', version: '4.12' + implementation 'cn.zhxu:okhttps:4.0.3' + implementation 'org.springframework.data:spring-data-redis' +// implementation 'org.springframework.cloud:spring-cloud-starter-sleuth:3.1.7' // 已经不支持了 +// implementation('com.yomahub:tlog-all-spring-boot-starter:1.5.1') { +// exclude group: 'pull-parser', module: 'pull-parser' +// } + implementation 'org.dom4j:dom4j:2.1.4' +// implementation 'pull-parser:pull-parser:2.1.10' } + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/app/src/main/java/hxy/dream/app/Application.java b/app/src/main/java/hxy/dream/app/Application.java index 5c59800f0647fd79a0a258f31665ffeaf0c1f612..046a6e9af53a80c4dbfd765c78b568f3d7736f92 100644 --- a/app/src/main/java/hxy/dream/app/Application.java +++ b/app/src/main/java/hxy/dream/app/Application.java @@ -1,31 +1,34 @@ package hxy.dream.app; - import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.cglib.core.DebuggingClassWriter; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableAsync; - /** * @author iris */ -@SpringBootApplication(scanBasePackages = {"hxy.dream"}) +@SpringBootApplication(scanBasePackages = { "hxy.dream" }) @MapperScan("hxy.dream.dao.mapper") @ServletComponentScan("hxy.dream") @EnableAsync public class Application { - private static final Logger log = LoggerFactory.getLogger(Application.class); + private static final Logger log = LoggerFactory.getLogger(Application.class); - public static void main(String[] args) { - log.info("当前CPU核心={},{}是否为守护线程={}",Runtime.getRuntime().availableProcessors(), Thread.currentThread().getName(), Thread.currentThread().isDaemon()); - ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); - log.trace("方便打断点"); - } + public static void main(String[] args) { + log.info("\n<============ ⚠️ 🚀 JAVA版本:{} CPU核心数:{} \uD83D\uDE80 ============>", + System.getProperty("java.version"), Runtime.getRuntime().availableProcessors()); + System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "class"); + log.info("当前CPU核心={},{}是否为守护线程={}", Runtime.getRuntime().availableProcessors(), + Thread.currentThread().getName(), Thread.currentThread().isDaemon()); + ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); + log.trace("方便打断点 {}", context); + } } diff --git a/app/src/main/java/hxy/dream/app/controller/AsyncController.java b/app/src/main/java/hxy/dream/app/controller/AsyncController.java index 1a0e45aafb15a7c2149c4ea8ab1738590a38857a..bdeff35ad0b8d91c252ca5ddd2de4aac451f3705 100644 --- a/app/src/main/java/hxy/dream/app/controller/AsyncController.java +++ b/app/src/main/java/hxy/dream/app/controller/AsyncController.java @@ -24,13 +24,14 @@ import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/async") public class AsyncController { + private static final Logger log = LoggerFactory.getLogger(AsyncController.class); /** * 注意这个线程池的最大线程数队列长度 */ @Autowired - ThreadPoolTaskExecutor applicationTaskExecutor; + private ThreadPoolTaskExecutor applicationTaskExecutor; /** * 从这个接口可以更好的理解异步模型的一种实现手段就是多线程! @@ -57,7 +58,7 @@ public class AsyncController { @RequestMapping("concurrency") public Object concurrency() { - log.info("请求开始执行,是否为守护线程={}", Thread.currentThread().isDaemon()); + log.info("请求开始执行,是否为守护线程={}当前类 {}", Thread.currentThread().isDaemon(), this); long start = System.currentTimeMillis(); diff --git a/app/src/main/java/hxy/dream/app/controller/ConcurrentHashMapController.java b/app/src/main/java/hxy/dream/app/controller/ConcurrentHashMapController.java index 7938619404aaef63023af2586953fc36803f6c9d..996e7fc4501f0138fe2bfd485bebe09d21dc1a88 100644 --- a/app/src/main/java/hxy/dream/app/controller/ConcurrentHashMapController.java +++ b/app/src/main/java/hxy/dream/app/controller/ConcurrentHashMapController.java @@ -60,6 +60,7 @@ public class ConcurrentHashMapController { }); + forkJoinPool.close(); //等待所有任务完成 forkJoinPool.shutdown(); forkJoinPool.awaitTermination(1, TimeUnit.HOURS); diff --git a/app/src/main/java/hxy/dream/app/controller/DeadLockController.java b/app/src/main/java/hxy/dream/app/controller/DeadLockController.java index b8edb9896811c61b9b3a629e23ee6e28aab9f4f7..6a055f336f7b5531fa81185322c927bf19691c45 100644 --- a/app/src/main/java/hxy/dream/app/controller/DeadLockController.java +++ b/app/src/main/java/hxy/dream/app/controller/DeadLockController.java @@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/deadlock") public class DeadLockController { + Object lock = new Object(); Object lock2 = new Object(); diff --git a/app/src/main/java/hxy/dream/app/controller/DownloadController.java b/app/src/main/java/hxy/dream/app/controller/DownloadController.java index 769f16e0a4b1a88f3cb42a16994804bd9fa6efda..c17a90f5eddef4a7c68d006796d96eae18d529e6 100644 --- a/app/src/main/java/hxy/dream/app/controller/DownloadController.java +++ b/app/src/main/java/hxy/dream/app/controller/DownloadController.java @@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController; public class DownloadController { @Autowired - DonwloadService donwloadService; + private DonwloadService donwloadService; @GetMapping("multi") public BaseResponseVO download() { diff --git a/app/src/main/java/hxy/dream/app/controller/ExceptionController.java b/app/src/main/java/hxy/dream/app/controller/ExceptionController.java index 32e575ba04156148d38c07d254001ced09ce2279..43f0a1860fc3545d8fe907683586d266291ba3f8 100644 --- a/app/src/main/java/hxy/dream/app/controller/ExceptionController.java +++ b/app/src/main/java/hxy/dream/app/controller/ExceptionController.java @@ -5,7 +5,8 @@ import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; /** @@ -16,6 +17,7 @@ import java.io.IOException; */ @RestController public class ExceptionController { + private static final Logger log = LoggerFactory.getLogger(ExceptionController.class); @RequestMapping("/exception") @@ -26,7 +28,11 @@ public class ExceptionController { } catch (IOException e) { log.error("{}", e.getMessage(), e); } + } + @RequestMapping("/exception-output") + public void exceptionOutput() { + throw new IllegalArgumentException("异常啊"); } } diff --git a/app/src/main/java/hxy/dream/app/controller/LogResetController.java b/app/src/main/java/hxy/dream/app/controller/LogResetController.java new file mode 100644 index 0000000000000000000000000000000000000000..b9e75c372f1aabdd72d746cbfb6ba777a2a27f7d --- /dev/null +++ b/app/src/main/java/hxy/dream/app/controller/LogResetController.java @@ -0,0 +1,31 @@ +package hxy.dream.app.controller; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import hxy.dream.entity.vo.BaseResponseVO; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author HOX4SGH + * @description + * @date 2024/10/19 + */ +@RestController +public class LogResetController { + + /** + * /log/set?name=hxy.dream.app&level=info + * + * @param name + * @param level + * @return + */ + @GetMapping("/log/set") + public BaseResponseVO setLog(String name, String level) { + Logger logger = (Logger) LoggerFactory.getLogger(name); + logger.setLevel(Level.toLevel(level)); + return BaseResponseVO.success(); + } +} diff --git a/app/src/main/java/hxy/dream/app/controller/LongController.java b/app/src/main/java/hxy/dream/app/controller/LongController.java index f78ac7ada24bb9907d383371ce683dd9689edbd9..723525f05e1b945d5f38ed9c335b9d41566f37d4 100644 --- a/app/src/main/java/hxy/dream/app/controller/LongController.java +++ b/app/src/main/java/hxy/dream/app/controller/LongController.java @@ -14,18 +14,20 @@ import java.util.HashMap; */ @RestController public class LongController { + @GetMapping(value = "/long") public BaseResponseVO testLong() { HashMap map = new HashMap(); - map.put("string", "1111111"); + map.put("String", "1111111"); Long l = 9112222222222222222L; Long l2 = Long.valueOf(9112222222222222222L); long l3 = 90071992547409912L; long l4 = 9007199254740990L; map.put("long", l); map.put("Long", l2); - map.put("超过长度的", l3); - map.put("不会失去精度的", l4); + map.put("超过长度的,精度会丢失,所以转成字符串解决丢失问题", l3); + map.put("比较小,Browser不会失去精度的", l4); return BaseResponseVO.success(map); } + } diff --git a/app/src/main/java/hxy/dream/app/controller/PathVariableController.java b/app/src/main/java/hxy/dream/app/controller/PathVariableController.java new file mode 100644 index 0000000000000000000000000000000000000000..8d144f4c6a950f16913e82ea67d2f71c06c99be9 --- /dev/null +++ b/app/src/main/java/hxy/dream/app/controller/PathVariableController.java @@ -0,0 +1,26 @@ +package hxy.dream.app.controller; + +import hxy.dream.entity.vo.BaseResponseVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PathVariableController { + + private static final Logger log = LoggerFactory.getLogger(PathVariableController.class); + + + /** + * @param variable + * @return + * @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#addMatchingMappings(java.util.Collection, java.util.List, jakarta.servlet.http.HttpServletRequest) + */ + @GetMapping("/path/variable/{variable}") + public BaseResponseVO path(@PathVariable(value = "variable") String variable) { + log.info("path: " + variable); + return BaseResponseVO.success(variable); + } +} diff --git a/app/src/main/java/hxy/dream/app/controller/SystemController.java b/app/src/main/java/hxy/dream/app/controller/SystemController.java index 0c9d6e4ae7b8e09166a6d0cfded57f1ef6c2f696..f3882d36654df1918589a78a068da780f2ca86ac 100644 --- a/app/src/main/java/hxy/dream/app/controller/SystemController.java +++ b/app/src/main/java/hxy/dream/app/controller/SystemController.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; @Slf4j @RestController public class SystemController { + @GetMapping("/") public BaseResponseVO index() { @@ -25,12 +26,18 @@ public class SystemController { // e.printStackTrace(); // } try { - TimeUnit.SECONDS.sleep(5); + TimeUnit.SECONDS.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } log.info("{}", Thread.currentThread().getName()); - return BaseResponseVO.success("SpringBoot"); + return BaseResponseVO.success("SpringBoot项目工程"); } + + @GetMapping("encoding") + public String encoding() { + return "encoding text/plain, 中文应该是乱码的。 Content-Type:text/html;charset=UTF-8就不会乱码 "; + } + } diff --git a/app/src/main/java/hxy/dream/app/controller/TranscationController.java b/app/src/main/java/hxy/dream/app/controller/TranscationController.java index 1f597bdd4c6955da55743eed2a9e3a9205c05fd3..5a2de2270eec32b550b6b520634c70ee1dfd9f7f 100644 --- a/app/src/main/java/hxy/dream/app/controller/TranscationController.java +++ b/app/src/main/java/hxy/dream/app/controller/TranscationController.java @@ -7,7 +7,8 @@ import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.annotation.Resource; +import jakarta.annotation.Resource; + import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; @@ -28,7 +29,7 @@ public class TranscationController { @Resource - TransactionService transactionService; + private TransactionService transactionService; private ThreadPoolExecutor executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors() * 2, 200, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5), new ThreadPoolExecutor.CallerRunsPolicy()); diff --git a/app/src/main/java/hxy/dream/app/controller/UserController.java b/app/src/main/java/hxy/dream/app/controller/UserController.java index cc3b1c6c4af2987bacb40b55b9cc43c4dc2bc9aa..27f99d0e793c08f48cdf58f0d933e5221edf34ef 100644 --- a/app/src/main/java/hxy/dream/app/controller/UserController.java +++ b/app/src/main/java/hxy/dream/app/controller/UserController.java @@ -6,18 +6,16 @@ import hxy.dream.app.service.UserService; import hxy.dream.common.converter.StringToEnumConverter; import hxy.dream.common.serializer.BaseEnumDeserializer; import hxy.dream.dao.model.UserModel; +import hxy.dream.entity.dto.OtherInfo; import hxy.dream.entity.vo.BaseResponseVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; + import java.util.List; /** @@ -30,12 +28,20 @@ import java.util.List; public class UserController { @Autowired - UserService userService; + private UserService userService; @GetMapping("get/{id}") public BaseResponseVO get(@PathVariable("id") String id) { log.info("\n====>当前获取的id是{}", id); UserModel userModel = userService.get(id); + if (userModel != null) { + OtherInfo otherInfo = userModel.getOtherInfo(); + if (otherInfo != null) { + String city = otherInfo.city(); + log.info("city: " + city); + } + + } return BaseResponseVO.success(userModel); } @@ -43,7 +49,7 @@ public class UserController { @GetMapping("exist/{id}") public BaseResponseVO exist(@PathVariable("id") String id) { log.info("\n====>当前获取的id是{}", id); - return userService.exist(id); + return userService.exist(id); } @@ -61,6 +67,7 @@ public class UserController { * @see BaseEnumDeserializer */ @PostMapping("add/body") + @ResponseStatus(HttpStatus.CREATED) public BaseResponseVO saveBody(@Valid @RequestBody UserParam userParam) { log.debug("\n====>当前添加的用户信息是{}", userParam); UserModel userModel = userService.add(userParam); @@ -75,7 +82,7 @@ public class UserController { * @see StringToEnumConverter */ @PostMapping("add/form") - public BaseResponseVO saveForm(@Valid UserParam userParam) { + public BaseResponseVO saveForm(@Valid UserParam userParam) { log.debug("\n====>当前添加的用户信息是{}", userParam); UserModel userModel = userService.add(userParam); return BaseResponseVO.success(userModel); @@ -88,4 +95,9 @@ public class UserController { return BaseResponseVO.success(userParam); } + @DeleteMapping("delete") + public BaseResponseVO delete(Integer id) { + return userService.delete(id); + } + } diff --git a/app/src/main/java/hxy/dream/app/controller/servlet/AsyncRunningServlet.java b/app/src/main/java/hxy/dream/app/controller/servlet/AsyncRunningServlet.java index 2f11613f16115f26de84574b92d21ca9567a14cc..cf16d34f0b0a56341d517dce8028a4f5460b9bd4 100644 --- a/app/src/main/java/hxy/dream/app/controller/servlet/AsyncRunningServlet.java +++ b/app/src/main/java/hxy/dream/app/controller/servlet/AsyncRunningServlet.java @@ -4,13 +4,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @@ -22,6 +22,7 @@ import java.io.PrintWriter; */ @WebServlet(urlPatterns = "/AsyncRunningServlet2", asyncSupported = true) public class AsyncRunningServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(AsyncRunningServlet.class); diff --git a/app/src/main/java/hxy/dream/app/controller/servlet/AsyncServlet.java b/app/src/main/java/hxy/dream/app/controller/servlet/AsyncServlet.java index 00b2951dca96434a9ad732d5d320ddaa7f088519..4e2d9166b8cea8a986459d8f90d821f333fecb56 100644 --- a/app/src/main/java/hxy/dream/app/controller/servlet/AsyncServlet.java +++ b/app/src/main/java/hxy/dream/app/controller/servlet/AsyncServlet.java @@ -6,12 +6,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @@ -23,6 +23,7 @@ import java.io.PrintWriter; */ @WebServlet(urlPatterns = "/AsyncServlet", asyncSupported = true) public class AsyncServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(AsyncServlet.class); /** diff --git a/app/src/main/java/hxy/dream/app/controller/servlet/MyTestReadListener.java b/app/src/main/java/hxy/dream/app/controller/servlet/MyTestReadListener.java index 1ae37d4d976f4c67825a141c6521f19f6f471966..7efc2bd14b5ab5fd0e6bdefb41a54d03d00af61a 100644 --- a/app/src/main/java/hxy/dream/app/controller/servlet/MyTestReadListener.java +++ b/app/src/main/java/hxy/dream/app/controller/servlet/MyTestReadListener.java @@ -4,9 +4,9 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.AsyncContext; -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; * @date 2/20/22 */ public class MyTestReadListener implements ReadListener { + private static final Logger log = LoggerFactory.getLogger(MyTestReadListener.class); diff --git a/app/src/main/java/hxy/dream/app/entity/param/UserParam.java b/app/src/main/java/hxy/dream/app/entity/param/UserParam.java index bf330b2a443c2e2c80a32ab6efa5a5727c50aa8c..09b813fbf8ec7741642e018bead223b436bfaa3a 100644 --- a/app/src/main/java/hxy/dream/app/entity/param/UserParam.java +++ b/app/src/main/java/hxy/dream/app/entity/param/UserParam.java @@ -3,8 +3,8 @@ package hxy.dream.app.entity.param; import hxy.dream.entity.enums.GenderEnum; import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; /** * @author iris diff --git a/app/src/main/java/hxy/dream/app/service/DonwloadService.java b/app/src/main/java/hxy/dream/app/service/DonwloadService.java index 931995193abfcf5031bb63f916279ac3eb90028a..018e824d0a409244b7ce46e42195edcac3c5b5ec 100644 --- a/app/src/main/java/hxy/dream/app/service/DonwloadService.java +++ b/app/src/main/java/hxy/dream/app/service/DonwloadService.java @@ -9,5 +9,7 @@ import hxy.dream.entity.vo.BaseResponseVO; * @date 2020/12/24 */ public interface DonwloadService { + BaseResponseVO multipleDonwload(); + } diff --git a/app/src/main/java/hxy/dream/app/service/TransactionService.java b/app/src/main/java/hxy/dream/app/service/TransactionService.java index a06bf6f7d36893eb7118768c06e6d37caf78a917..1cacb724af0b355408e42c03764bbbd257a42be4 100644 --- a/app/src/main/java/hxy/dream/app/service/TransactionService.java +++ b/app/src/main/java/hxy/dream/app/service/TransactionService.java @@ -1,6 +1,5 @@ package hxy.dream.app.service; - import hxy.dream.dao.mapper.UserMapper; import hxy.dream.dao.model.UserModel; import hxy.dream.entity.vo.BaseResponseVO; @@ -10,7 +9,7 @@ import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import jakarta.annotation.Resource; /** * 事物测试 @@ -21,75 +20,73 @@ import javax.annotation.Resource; @Service public class TransactionService { - @Resource - UserMapper userMapper; + @Resource + private UserMapper userMapper; - @Resource - TransactionSubService transactionSubService; + @Resource + private TransactionSubService transactionSubService; - /** - * 测试事务 - */ - @Transactional(propagation = Propagation.REQUIRED) - public BaseResponseVO propagation() { - saveParent(); - try { - transactionSubService.saveChildren(); - } catch (Exception e) { - log.error("====>\n异常直接捕获,但是还会抛出事务异常", e); - } + /** + * 测试事务 + */ + @Transactional(propagation = Propagation.REQUIRED) + public BaseResponseVO propagation() { + saveParent(); + try { + transactionSubService.saveChildren(); + } catch (Exception e) { + log.error("====>\n异常直接捕获,但是还会抛出事务异常", e); + } // int a = 1 / 0; - log.info("====>\n事务测试运行完成了"); - return BaseResponseVO.success(); - } - - // ===========service实现类 - public void saveParent() { - UserModel stu = new UserModel(); - stu.setName("parent"); - stu.setAge(19); - userMapper.insert(stu); // 数据库插入一条parent记录 - } - + log.info("====>\n事务测试运行完成了"); + return BaseResponseVO.success(); + } - @Transactional(isolation = Isolation.READ_UNCOMMITTED) - public BaseResponseVO isolation() { - int id = 1; - UserModel userModel = userMapper.selectById(id); - log.info("\n=====>第一次查询信息{}", userModel); + // ===========service实现类 + public void saveParent() { + UserModel stu = new UserModel(); + stu.setName("parent"); + stu.setAge(19); + userMapper.insert(stu); // 数据库插入一条parent记录 + } - try { - Thread.sleep(6000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // 这里读到别人未提交的数据,也就是脏数据 - userModel = userMapper.selectById(id); - log.info("\n=====>第二次查询信息{},是脏数据", userModel); - return BaseResponseVO.success(); - } + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + public BaseResponseVO isolation() { + int id = 1; + UserModel userModel = userMapper.selectById(id); + log.info("\n=====>第一次查询信息{}", userModel); - @Transactional(isolation = Isolation.READ_UNCOMMITTED) - public BaseResponseVO isolation1() { - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - UserModel userModel = new UserModel(); - userModel.setId(1); - userModel.setAge(9); - userMapper.updateById(userModel); - try { - Thread.sleep(6000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // 发生异常回滚了 - int a = 1 / 0; + try { + Thread.sleep(6000); + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + } + // 这里读到别人未提交的数据,也就是脏数据 + userModel = userMapper.selectById(id); + log.info("\n=====>第二次查询信息{},是脏数据", userModel); + return BaseResponseVO.success(); + } - return BaseResponseVO.success(); - } + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + public BaseResponseVO isolation1() { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + } + UserModel userModel = new UserModel(); + userModel.setId(1); + userModel.setAge(9); + userMapper.updateById(userModel); + try { + Thread.sleep(6000); + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + } + // 发生异常回滚了 + int a = 1 / 0; + return BaseResponseVO.success(); + } } diff --git a/app/src/main/java/hxy/dream/app/service/TransactionSubService.java b/app/src/main/java/hxy/dream/app/service/TransactionSubService.java index 5a6dc05ee4a78cf7c24bdce8597c677e156a0a27..acf53a3188688305b283f04826d3effa5658ff48 100644 --- a/app/src/main/java/hxy/dream/app/service/TransactionSubService.java +++ b/app/src/main/java/hxy/dream/app/service/TransactionSubService.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import jakarta.annotation.Resource; /** * @author eric @@ -18,7 +18,7 @@ import javax.annotation.Resource; public class TransactionSubService { @Resource - UserMapper userMapper; + private UserMapper userMapper; @Transactional(propagation = Propagation.REQUIRED) public void saveChildren() { diff --git a/app/src/main/java/hxy/dream/app/service/UserService.java b/app/src/main/java/hxy/dream/app/service/UserService.java index 6b1b62b4b6867f9315cedcdfa8d977a20a665353..21d95455e1ca0ed71a8621007851819e918edfb9 100644 --- a/app/src/main/java/hxy/dream/app/service/UserService.java +++ b/app/src/main/java/hxy/dream/app/service/UserService.java @@ -9,11 +9,15 @@ import java.util.List; @Service public interface UserService { - UserModel add(UserParam userParam); - UserModel get(String id); + UserModel add(UserParam userParam); - BaseResponseVO exist(String id); + UserModel get(String id); + + BaseResponseVO exist(String id); + + List list(); + + BaseResponseVO delete(Integer id); - List list(); } diff --git a/app/src/main/java/hxy/dream/app/service/impl/DonwloadServiceImpl.java b/app/src/main/java/hxy/dream/app/service/impl/DonwloadServiceImpl.java index 105ca90b0a5d10c90fc514c0052bdeeb3ca8d3ec..675290657309f6d00c3f273d0573d938a08a692e 100644 --- a/app/src/main/java/hxy/dream/app/service/impl/DonwloadServiceImpl.java +++ b/app/src/main/java/hxy/dream/app/service/impl/DonwloadServiceImpl.java @@ -1,9 +1,9 @@ package hxy.dream.app.service.impl; -import com.ejlchina.okhttps.OkHttps; import hxy.dream.app.service.DonwloadService; import hxy.dream.entity.vo.BaseResponseVO; import org.springframework.stereotype.Service; +import cn.zhxu.okhttps.OkHttps; import java.io.File; @@ -15,10 +15,11 @@ import java.io.File; */ @Service public class DonwloadServiceImpl implements DonwloadService { - String url = "https://typora.io/windows/typora-setup-x64.exe"; + + private String url = "https://typora.io/windows/typora-setup-x64.exe"; @Override - public BaseResponseVO multipleDonwload() { + public BaseResponseVO multipleDonwload() { System.out.println("程序开始了"); @@ -33,9 +34,9 @@ public class DonwloadServiceImpl implements DonwloadService { return null; } - void download(long totalSize, int index) { + void download(long totalSize, int index) { System.out.println("===>" + index); - String filePath = System.getProperty("user.dir") + File.separator+"typora-setup-x64-7.exe"; + String filePath = System.getProperty("user.dir") + File.separator + "typora-setup-x64-7.exe"; long size = 3 * 1024 * 1024; // 每块下载 3M long start = index * size; @@ -49,7 +50,7 @@ public class DonwloadServiceImpl implements DonwloadService { if (end < totalSize) { // 若未下载完,则继续下载下一块 download(totalSize, index + 1); } else { - System.out.println("下载完成"+filePath); + System.out.println("下载完成" + filePath); } }) .start(); diff --git a/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java b/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java index eb8271fa5bb87fb39d45aa8e2a3a0b96e41aea49..9134878ed5523cdaff8cf4988ad750f2ce4f9dcf 100644 --- a/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java +++ b/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java @@ -7,10 +7,13 @@ import hxy.dream.dao.mapper.UserMapper; import hxy.dream.dao.model.UserModel; import hxy.dream.entity.enums.GenderEnum; import hxy.dream.entity.vo.BaseResponseVO; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; + import java.util.List; /** @@ -18,10 +21,11 @@ import java.util.List; */ @Slf4j @Service +@RequiredArgsConstructor public class UserServiceImpl implements UserService { - @Resource - UserMapper userMapper; + // @Resource 可以通过上面的 @RequiredArgsConstructor注解来生成构造器。需要final标记 + private final UserMapper userMapper; @Override public UserModel add(UserParam userParam) { @@ -30,7 +34,7 @@ public class UserServiceImpl implements UserService { GenderEnum gender = userParam.getGender(); userModel.setGender(gender); userModel.setAddress(userParam.getAddress()); - userModel.setAge(userModel.getAge()); + userModel.setAge(userParam.getAge()); if (gender == GenderEnum.UNKNOWN) { log.error("性别未知"); } @@ -40,8 +44,10 @@ public class UserServiceImpl implements UserService { } @Override + @Cacheable(value = "UserService#36000", key = "'getUser'+#id", unless = "#result == null") public UserModel get(String id) { - return userMapper.selectById(id); + UserModel userModel = userMapper.selectById(id); + return userModel; } @Override @@ -49,6 +55,16 @@ public class UserServiceImpl implements UserService { return userMapper.selectList(null); } + @Override + public BaseResponseVO delete(Integer id) { + int i = userMapper.deleteById(id); + if (i > 0) { + return BaseResponseVO.success(); + } else { + return BaseResponseVO.error("删除失败"); + } + } + /** * 判断用户id存在否 * diff --git a/app/src/main/resources/META-INF/spring.factories b/app/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..ccd6393bf50a4de7954e4721d0d70f0db3ef8482 --- /dev/null +++ b/app/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.env.EnvironmentPostProcessor=hxy.dream.common.configuration.RemoteEnvironmentPostProcessor \ No newline at end of file diff --git a/app/src/main/resources/application-beta.yml b/app/src/main/resources/application-beta.yml index f768fdbebe3b50e5fa8e449f43b6a60a9c3e1911..ea8f26fd6b697d03daf3589e9248d29d445158ba 100755 --- a/app/src/main/resources/application-beta.yml +++ b/app/src/main/resources/application-beta.yml @@ -3,9 +3,8 @@ spring: datasource: url: jdbc:mysql://mysql.cupb.top:3306/eric?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8 username: eric - password: dream,1234.. + password: # 密码参考 hxy.dream.common.configuration.RemoteEnvironmentPostProcessor 获取逻辑 driver-class-name: com.mysql.cj.jdbc.Driver - platform: mysql hikari: max-lifetime: 120000 # 池中连接关闭后的最长生命周期 20分钟 # type: com.alibaba.druid.pool.DruidDataSource @@ -17,20 +16,29 @@ spring: transport: dashboard: localhost:8080 #eager: true -# redis: -# database: 0 -# host: 119.23.236.94 -# port: 6379 -# password: newpass -# jedis: -# pool: -# max-active: 8 -# cache: -# redis: -# time-to-live: 0s # 缓存过期时间30分钟 -# type: redis -# rabbitmq: -# host: 119.23.236.94 + sql: + init: + platform: mysql + data: + # redis 配置 + redis: + # 地址 121.36.136.109 localhost + host: 52.131.246.191 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 3 + # 密码 + password: newpass + # 连接超时时间 + timeout: 10s + jedis: + pool: + min-idle: 10 + max-idle: 20 + max-wait: -1ms + max-active: 200 +# ssl: true logging: level: @@ -39,16 +47,6 @@ logging: hxy.dream: debug com.fasterxml.jackson: debug - -hxy: - print: - file: /home/ubuntu/eric/print/file - pay-status: true #支付测试 - single-price: 20 - double-price: 25 - shop: cupb - http: 8080 - https: 8443 #server: # ssl: # key-store: classpath:cupbtop.jks diff --git a/app/src/main/resources/application-test.yml b/app/src/main/resources/application-test.yml new file mode 100755 index 0000000000000000000000000000000000000000..620d1596e691aa080f60e6c30430d23c7b26acd3 --- /dev/null +++ b/app/src/main/resources/application-test.yml @@ -0,0 +1,61 @@ +#线上测试配置 +spring: + datasource: + url: jdbc:p6spy:mysql://139.217.230.42:3306/test?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8 + username: # 用户名参考 hxy.dream.common.configuration.RemoteEnvironmentPostProcessor 获取逻辑 + password: # 密码参考 hxy.dream.common.configuration.RemoteEnvironmentPostProcessor 获取逻辑 + # driver-class-name: com.mysql.cj.jdbc.Driver + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + hikari: + max-lifetime: 120000 # 池中连接关闭后的最长生命周期 20分钟 + # type: com.alibaba.druid.pool.DruidDataSource + servlet: + multipart: + enabled: false + cloud: + sentinel: + transport: + dashboard: localhost:8080 + #eager: true + sql: + init: + platform: mysql + data: + # redis 配置 + redis: + # 地址 121.36.136.109 localhost + host: 52.131.246.191 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 3 + # 密码 + password: newpass + # 连接超时时间 + timeout: 10s + jedis: + pool: + min-idle: 10 + max-idle: 20 + max-wait: -1ms + max-active: 200 +# ssl: true + +logging: + level: + root: info #其他的包都是使用info正常显示日志 + hxy.dream.dao: trace # 显示mybatis的操作时候所有级别的日志 + hxy.dream: debug + com.fasterxml.jackson: debug + com.baomidou.mybatisplus: debug + org.apache.ibatis: debug + org.springframework.web.filter.CommonsRequestLoggingFilter: DEBUG +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +#server: +# ssl: +# key-store: classpath:cupbtop.jks +# key-store-password: Sigh,0404 +# key-store-type: JKS #RSA +# port: 8443 \ No newline at end of file diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml index a0d0b59dd4efb55f82ca279d23f88b4bc430e69a..505aa6c46b6c7645fe292a82f92d651190c58f00 100755 --- a/app/src/main/resources/application.yml +++ b/app/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + application: + name: dragon thymeleaf: mode: HTML #LEGACYHTML5 cache: false @@ -10,23 +12,30 @@ spring: multipart: enabled: false profiles: - active: beta #环境隔离 + active: dev #环境隔离 mail: - username: inform@islab.net.cn - password: Alerter.2019 + username: aohanhongzhi@qq.com + password: vtrxapjtpcivdbcb default-encoding: UTF-8 - host: smtp.exmail.qq.com + host: smtp.qq.com jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 + default-property-inclusion: always # 当 NON_NULL 的时候,Field为null的时候,序列化可以忽略这个Field。不传给前端。 + +email: + from: aohanhongzhi@qq.com + to: aohanhongzhi@qq.com mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: # 配置日志 -# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl local-cache-scope: statement # 每次执行sql。多个statement可能会在一个session里面。 这个是mybatis的一级缓存控制。 + return-instance-for-empty-row: true # https://mp.weixin.qq.com/s/ITwlZZfxw9D9k1iJYqZakQ + call-setters-on-nulls: true # default-enum-type-handler: org.apache.ibatis.type.EnumTypeHandler type-aliases-package: hxy.dream.dao.model # 支持通配符 * 或者 ; 分割 @@ -36,16 +45,11 @@ mybatis-plus: logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) id-type: auto - type-enums-package: hxy.dream.entity.enums +# column-format: "`%s`" # 解决数据库关键字问题 -# pagehelper分页插件 -pagehelper: - # 数据库的方言 - helperDialect: mysql - # 启用合理化,如果pageNum < 1会查询第一页,如果pageNum > pages会查询最后一页 - reasonable: true server: port: 8080 + max-http-request-header-size: 102400 # tomcat: # threads: # max: 2 @@ -62,4 +66,9 @@ server: info: app: java: -# version: @java.version@ \ No newline at end of file +# version: @java.version@ + +# https://gitee.com/huoyo/ko-time +ko-time: + pointcut: execution(public * hxy.dream..*.*(..)) + context-path: http://localhost:8080/koTime \ No newline at end of file diff --git a/app/src/main/resources/banner.txt b/app/src/main/resources/banner.txt new file mode 100644 index 0000000000000000000000000000000000000000..eaae498afc4737aa90d0ec1e22265af2985df1be --- /dev/null +++ b/app/src/main/resources/banner.txt @@ -0,0 +1,10 @@ + + _____ ______ _______ ________ _____ + / ____| ____| __ \ \ / / ____| __ \ +| (___ | |__ | |__) \ \ / /| |__ | |__) | + \___ \| __| | _ / \ \/ / | __| | _ / + ____) | |____| | \ \ \ / | |____| | \ \ +|_____/|______|_| \_\ \/ |______|_| \_\ + +🚀 SpringBoot:${spring-boot.formatted-version} 😂 当前激活环境变量 ${spring.profiles.active} 端口 ${server.port} JDK: ${java.version} + diff --git a/app/src/main/resources/logback.xml b/app/src/main/resources/logback-spring.xml similarity index 34% rename from app/src/main/resources/logback.xml rename to app/src/main/resources/logback-spring.xml index 25e27575f7445d7ee8eb4f54a53791463dc82b19..dc28e06e121ce36dc3e76a3c8ec29fe88dda180f 100644 --- a/app/src/main/resources/logback.xml +++ b/app/src/main/resources/logback-spring.xml @@ -2,15 +2,18 @@ - + - - + + + + hxy - + + @@ -22,7 +25,32 @@ - + + + + + + + ALL + ACCEPT + + ACCEPT + + ${LOG_PATH}/all.log + + + + + ${LOG_PATH}/all.%d{yyyy-MM-dd}.%i.log + + 10MB + 60 + + 200MB + + + %date %highlight(%-5level) [%thread] %yellow(at %class.%method) \(%file:%line\) - %msg%n + @@ -32,19 +60,17 @@ ACCEPT DENY - ${DEV_HOME}/debug.log + ${LOG_PATH}/debug.log - + - ${DEV_HOME}/debug.%d{yyyy-MM-dd}.%i.log - - 180 + ${LOG_PATH}/debug.%d{yyyy-MM-dd}.%i.log + + 10MB + 60 - - - 10MB - + 200MB %date %level [%thread] %logger{36} [%file : %line] %msg%n @@ -59,16 +85,18 @@ ACCEPT DENY - ${DEV_HOME}/info.log - - ${DEV_HOME}/info.%d{yyyy-MM-dd}.%i.log - 180 - - 10MB - + ${LOG_PATH}/info.log + + + ${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log + + 10MB + 60 + + 200MB - %date %level [%thread] %logger{36} [%file : %line] %msg%n + %date %level [%thread] %class.%method \(%file : %line\) %msg%n @@ -77,17 +105,19 @@ ACCEPT DENY - ${DEV_HOME}/warn.log - - ${DEV_HOME}/warn.%d{yyyy-MM-dd}.%i.log - 180 - - 10MB - + ${LOG_PATH}/warn.log + + + ${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log + + 10MB + 60 + + 200MB %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]%logger -%msg%n - + @@ -96,17 +126,19 @@ ACCEPT DENY - ${DEV_HOME}/error.log - - ${DEV_HOME}/error.%d{yyyy-MM-dd}.%i.log - 180 - - 10MB - + ${LOG_PATH}/error.log + + + ${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log + + 10MB + 60 + + 200MB %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]%logger -%msg%n - + @@ -114,58 +146,42 @@ - - + ${smtpHost} - - - 25 - - + + 465 + true + + false + ${to} - - + ${from} - ${logname}: %logger - %msg - - + ${username} - - + ${password} - - false - - + true - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n - + ❌ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n - - - 1 - + 10 - - - + + - ERROR - ACCEPT - DENY - - + + + @@ -176,6 +192,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/resources/mapper/UserMapper.xml b/app/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000000000000000000000000000000000000..1e594cbca05aae1bcb2043f77ebbec4ca1d1f090 --- /dev/null +++ b/app/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + select id, name, password, address, phone_number + from user_model + + + + update user_model + set name = #{name}, + age = #{age}, + deleted = 0 + where id = #{id} + + + + + diff --git a/app/src/main/resources/spy.properties b/app/src/main/resources/spy.properties new file mode 100644 index 0000000000000000000000000000000000000000..af0bb2a7a241c1391a7154a383579e518a64011e --- /dev/null +++ b/app/src/main/resources/spy.properties @@ -0,0 +1,24 @@ +#3.2.1以上使用 +modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory +#3.2.1以下使用或者不配置 +#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory +# 自定义日志打印 +logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger +#日志输出到控制台 +appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger +# 使用日志系统记录 sql +#appender=com.p6spy.engine.spy.appender.Slf4JLogger +# 设置 p6spy driver 代理 +deregisterdrivers=true +# 取消JDBC URL前缀 +useprefix=true +# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss +# 实际驱动可多个 +#driverlist=org.h2.Driver +# 是否开启慢SQL记录 +outagedetection=true +# 慢SQL记录标准 2 秒 +outagedetectioninterval=2 \ No newline at end of file diff --git a/app/src/main/resources/table.sql b/app/src/main/resources/table.sql index db21dbe07106a702b1aa769c93ca1380710ed054..92e5c5f9e4146fdc231c7b3f950b4f36e0749ea2 100644 --- a/app/src/main/resources/table.sql +++ b/app/src/main/resources/table.sql @@ -3,7 +3,7 @@ create table if not exists user_model id int unsigned auto_increment primary key, name varchar(20) not null, - phone char(11) null, + phone_number char(11) null, address varchar(256) null, age int null comment '年龄', gender int null comment '性别', diff --git a/app/src/test/java/hxy/dream/BaseTest.java b/app/src/test/java/hxy/dream/BaseTest.java index 1c2954cdb06cc7d68c7b4ffe0435064415056f5b..7a2c7e4f4c77f4451c0f7ae41ce064e8a41ed9fe 100644 --- a/app/src/test/java/hxy/dream/BaseTest.java +++ b/app/src/test/java/hxy/dream/BaseTest.java @@ -1,11 +1,16 @@ package hxy.dream; import hxy.dream.app.Application; -import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.ActiveProfiles; +/** + * 使用 @ActiveProfiles 注解,可以覆盖掉yaml文件的指定配置环境,方便单元测试指定环境。又不影响线上部署 + */ +@ActiveProfiles("test") @SpringBootTest(classes = Application.class) -@RunWith(SpringJUnit4ClassRunner.class) public abstract class BaseTest { + public static final Logger log = LoggerFactory.getLogger(BaseTest.class); } diff --git a/app/src/test/java/hxy/dream/app/service/impl/EnumTest.java b/app/src/test/java/hxy/dream/app/service/impl/EnumTest.java index a781f64a9f60c95ce9084f42084a7ab5a0e19c47..1c2300b7f7b052c7aa97edc83b5c10cf3edbeed7 100644 --- a/app/src/test/java/hxy/dream/app/service/impl/EnumTest.java +++ b/app/src/test/java/hxy/dream/app/service/impl/EnumTest.java @@ -5,9 +5,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StringDeserializer; import hxy.dream.BaseTest; import hxy.dream.entity.enums.GenderEnum; -import org.junit.Test; -import javax.annotation.Resource; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; /** * @author eric @@ -31,7 +31,7 @@ public class EnumTest extends BaseTest { } - @org.junit.Test + @Test public void enumJsonTest(){ try { String s = objectMapper.writeValueAsString(GenderEnum.BOY); @@ -42,10 +42,10 @@ public class EnumTest extends BaseTest { e.printStackTrace(); } } - @org.junit.Test + @Test public void enumJsonTest2(){ - GenderEnum byCode = GenderEnum.getEnumByCode(1); + GenderEnum byCode = GenderEnum.getEnumByCode(100); System.out.println(byCode); } diff --git a/app/src/test/java/hxy/dream/app/service/impl/UserServiceImplTest.java b/app/src/test/java/hxy/dream/app/service/impl/UserServiceImplTest.java index 7e5dca55d19bc64e85c46e02adb26a2e2d1f51eb..aa4361d72bb7635f3636e8d7626108faf8f495cd 100644 --- a/app/src/test/java/hxy/dream/app/service/impl/UserServiceImplTest.java +++ b/app/src/test/java/hxy/dream/app/service/impl/UserServiceImplTest.java @@ -5,14 +5,20 @@ import hxy.dream.app.entity.param.UserParam; import hxy.dream.app.service.UserService; import hxy.dream.dao.model.UserModel; import lombok.extern.slf4j.Slf4j; -import org.junit.Test; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + @Slf4j public class UserServiceImplTest extends BaseTest { + @Autowired UserService userService; + @Test + @Order(1) public void arunTest(){ UserParam userParam = new UserParam(); userParam.setName("111"); @@ -26,4 +32,11 @@ public class UserServiceImplTest extends BaseTest { UserModel userModel = userService.get("87"); log.info("\n====>{}",userModel); } + + @Test + public void listTest(){ + + List userModels = userService.list(); + log.info("\n====>{}",userModels); + } } diff --git a/app/src/test/java/hxy/dream/common/email/EmailTest.java b/app/src/test/java/hxy/dream/common/email/EmailTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a877c0b1cac9982187bc73a83b75f507d2528c7b --- /dev/null +++ b/app/src/test/java/hxy/dream/common/email/EmailTest.java @@ -0,0 +1,132 @@ +package hxy.dream.common.email; + +import hxy.dream.BaseTest; +import jakarta.mail.Flags; +import jakarta.mail.Folder; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.NoSuchProviderException; +import jakarta.mail.Session; +import jakarta.mail.Store; +import org.eclipse.angus.mail.pop3.POP3Folder; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +/** + * @author eric + * @program multi-gradle + * @description 邮件测试 + * @date 2023/1/8 + */ +public class EmailTest extends BaseTest { + + private static final Logger log = LoggerFactory.getLogger(EmailTest.class); + + + @Autowired + private JavaMailSenderImpl mailSender; + + @Test + public void emailTest() { + //简单邮件 + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setFrom("aohanhongzhi@qq.com"); + simpleMailMessage.setTo("aohanhongzhi@qq.com"); + simpleMailMessage.setSubject("Happy New Year"); + simpleMailMessage.setText("新年快乐!"); + mailSender.send(simpleMailMessage); + +// 原文链接:https://blog.csdn.net/qq_26383975/article/details/121957917 + } + + @Test + public void deleteEmail() { + try { + + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props); + session.setDebug(true); + //取得pop3协议的邮件服务器 + Store store = null; + store = session.getStore("pop3"); + + //连接pop.qq.com邮件服务器 + store.connect("pop.qq.com", "aohanhongzhi@qq.com", "vtrxapjtpcivdbcb"); + + Folder[] personalNamespaces = store.getPersonalNamespaces(); + for (Folder folder : personalNamespaces) { + + if (folder instanceof POP3Folder) { + start(folder); + } + + } +// //返回文件夹对象 + Folder defaultFolder = store.getDefaultFolder(); + Folder[] allFolder = defaultFolder.list(); + + for (Folder folder : allFolder) { + + if (folder instanceof POP3Folder) { + start(folder); + } + + } + + // POP3Folder + Folder folder = store.getFolder("INBOX"); + start(folder); + + store.close(); + } catch (NoSuchProviderException e) { + throw new RuntimeException(e); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + } + + + public void start(Folder folder) throws MessagingException { + if (folder instanceof POP3Folder) { + int k = 0; + while (k < 100) { + //设置读写 + folder.open(Folder.READ_WRITE); + String subjectTitle = "dragon"; + //获取信息 + Message message[] = folder.getMessages(); +// Message message[] = folder.search(new SubjectTerm("dragon")); + int j = 0; + for (int i = 0; i < message.length && j < 200; i++) { + String subject = message[i].getSubject(); + String from = message[i].getFrom()[0].toString(); + + if (subject.contains(subjectTitle) || subject.contains("RBLC-ADMIN") || subject.contains("GitHub") || subject.contains("物联网通信计费策略调整") || from.contains("register.csdn.net") || from.contains("tencent.com") || from.contains("github.com")) { + //设置删除标记 + message[i].setFlag(Flags.Flag.DELETED, true); + j++; + log.warn("{}删除邮件 {}", j, subject); + } else { + log.info(i + ": " + from + "\t" + subject); + } + } + log.warn("删除{}邮件", j); + if (j > 0) { + folder.close(true); + } else { + folder.close(); + break; + } + k++; + } + } + + } + +} diff --git a/app/src/test/java/hxy/dream/common/manager/RemoteApiTest.java b/app/src/test/java/hxy/dream/common/manager/RemoteApiTest.java new file mode 100644 index 0000000000000000000000000000000000000000..92ced08a21ea86e3841054c319a91a5fba14283d --- /dev/null +++ b/app/src/test/java/hxy/dream/common/manager/RemoteApiTest.java @@ -0,0 +1,23 @@ +package hxy.dream.common.manager; + + +import hxy.dream.BaseTest; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +public class RemoteApiTest extends BaseTest { + + + @Autowired + private RemoteApi remoteApi; + + @Test + public void testGet() { + String body = remoteApi.getBody(); + log.info("{}", body); + } + + +} diff --git a/app/src/test/java/hxy/dream/common/reids/RedisTemplateTest.java b/app/src/test/java/hxy/dream/common/reids/RedisTemplateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a5389e3f6ba2253d0bc8ed95442b09730da55c29 --- /dev/null +++ b/app/src/test/java/hxy/dream/common/reids/RedisTemplateTest.java @@ -0,0 +1,37 @@ +package hxy.dream.common.reids; + +import hxy.dream.BaseTest; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +/** + * @author eric + * @description + * @date 2023/6/2 + */ +public class RedisTemplateTest extends BaseTest { + + private static final Logger log = LoggerFactory.getLogger(RedisTemplateTest.class); + + + @Autowired + RedisTemplate redisTemplate; + + @Test + public void testRedisTemplate() { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String key = "key"; + valueOperations.set(key, "value"); + + Object o = valueOperations.get(key); + + log.info("结果:{}", o); + + + } + +} diff --git a/app/src/test/java/hxy/dream/common/study/EricFactoryBeanTest.java b/app/src/test/java/hxy/dream/common/study/EricFactoryBeanTest.java index e981886190515e6e8d42666688de3ef054f6f61e..f49a5ede8de730d176a87bc4f5e8802f95de5145 100644 --- a/app/src/test/java/hxy/dream/common/study/EricFactoryBeanTest.java +++ b/app/src/test/java/hxy/dream/common/study/EricFactoryBeanTest.java @@ -2,7 +2,7 @@ package hxy.dream.common.study; import hxy.dream.BaseTest; import hxy.dream.app.Application; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; diff --git a/app/src/test/java/hxy/dream/common/util/UnitTestEnvironmentTest.java b/app/src/test/java/hxy/dream/common/util/UnitTestEnvironmentTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bce08d430e22e0d9f16681e0b6c976d17d1cb2fe --- /dev/null +++ b/app/src/test/java/hxy/dream/common/util/UnitTestEnvironmentTest.java @@ -0,0 +1,19 @@ +package hxy.dream.common.util; + +import hxy.dream.BaseTest; +import org.junit.jupiter.api.Test; + +/** + * @author eric + * @description + * @date 2024/3/17 + */ +public class UnitTestEnvironmentTest extends BaseTest { + + @Test + public void testEnvironmentUtils() { + String activeProfile = EnvironmentUtils.getActiveProfile(); + log.info("{}", activeProfile); + } + +} diff --git a/app/src/test/java/hxy/dream/contoller/LongControllerTest.java b/app/src/test/java/hxy/dream/contoller/LongControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f3659885980c74671ca68036a2a2865158515596 --- /dev/null +++ b/app/src/test/java/hxy/dream/contoller/LongControllerTest.java @@ -0,0 +1,36 @@ +package hxy.dream.contoller; + + +import hxy.dream.app.Application; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ActiveProfiles("test") +@AutoConfigureMockMvc +@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class LongControllerTest { + + @Autowired + private MockMvc mockMvc; + + + @Test + public void testLongController() throws Exception { + mockMvc.perform(get("/long")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.data").exists()) + .andDo(MockMvcResultHandlers.print()); + } + +} diff --git a/app/src/test/java/hxy/dream/contoller/SystemControllerTest.java b/app/src/test/java/hxy/dream/contoller/SystemControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ef6a29cc07c9ca8c2cd1a32b1fc6967ed18a59f1 --- /dev/null +++ b/app/src/test/java/hxy/dream/contoller/SystemControllerTest.java @@ -0,0 +1,30 @@ +package hxy.dream.contoller; + +import hxy.dream.BaseTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@AutoConfigureMockMvc +public class SystemControllerTest extends BaseTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void testIndex() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.data").exists()) + .andDo(MockMvcResultHandlers.print()); + } + +} diff --git a/app/src/test/java/hxy/dream/contoller/UserControllerTest.java b/app/src/test/java/hxy/dream/contoller/UserControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..207c6e64ec52212d762bf69e6288bb3105eb4a83 --- /dev/null +++ b/app/src/test/java/hxy/dream/contoller/UserControllerTest.java @@ -0,0 +1,42 @@ +package hxy.dream.contoller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hxy.dream.BaseTest; +import hxy.dream.dao.model.UserModel; +import hxy.dream.entity.enums.GenderEnum; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@AutoConfigureMockMvc +public class UserControllerTest extends BaseTest { + + @Autowired + private MockMvc mvc; + + @Test + public void testUserAdd() throws Exception { + + UserModel stu = new UserModel(); + stu.setName("parent"); + stu.setAge(19); + stu.setGender(GenderEnum.BOY); + + ObjectMapper objectMapper = new ObjectMapper(); + String s = objectMapper.writeValueAsString(stu); + + mvc.perform(post("/user/add/body") + .content(s) // 提交body参数 + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(MockMvcResultMatchers.jsonPath("$.data").exists()); + } + +} diff --git a/app/src/test/java/hxy/dream/dao/PooledDataSourceCloseTest.java b/app/src/test/java/hxy/dream/dao/PooledDataSourceCloseTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a2f5d733126bf7776a0ff4216c1e8d989900422d --- /dev/null +++ b/app/src/test/java/hxy/dream/dao/PooledDataSourceCloseTest.java @@ -0,0 +1,32 @@ +package hxy.dream.dao; + +import org.apache.ibatis.datasource.pooled.PooledDataSource; +import org.junit.jupiter.api.Test; + + +import java.sql.Connection; + +/** + * @author HOX4SGH + * @description + * @date 2024/6/16 + */ +public class PooledDataSourceCloseTest { + + @Test + public void testPooledDataSourceClose() throws Exception { + PooledDataSource pds = new PooledDataSource(); + pds.setDriver("com.mysql.cj.jdbc.Driver"); + pds.setUrl("jdbc:mysql://.217.230.42:3306/test"); + pds.setUsername("test"); + pds.setPassword("Newp0ss,2024!"); + + while (true) { + Connection connection = pds.getConnection(); + System.out.println(connection); + Thread.sleep(1000); + connection.close(); + } + } + +} diff --git a/app/src/test/java/hxy/dream/dao/mapper/UserMapperTest.java b/app/src/test/java/hxy/dream/dao/mapper/UserMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ea84666a3cbf60013d1d41d9b38fa4f2c859ed3b --- /dev/null +++ b/app/src/test/java/hxy/dream/dao/mapper/UserMapperTest.java @@ -0,0 +1,109 @@ +package hxy.dream.dao.mapper; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import hxy.dream.BaseTest; +import hxy.dream.dao.model.UserModel; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * @author eric + * @description + * @date 2023/6/5 + */ +public class UserMapperTest extends BaseTest { + + private static final Logger log = LoggerFactory.getLogger(UserMapperTest.class); + + + @Resource + UserMapper userMapper; + + + @Test + public void testInsertOrUpdateSetNull() { + UserModel userModel = new UserModel(); + userModel.setId(2); + userModel.setName("rblc"); + userModel.setAge(231); + userModel.setAddress("上海"); + userModel.setPhoneNumber("18010472947"); + boolean update = userMapper.insertOrUpdate(userModel); + log.info("Updated user{}", update); + } + + + @Test + public void testUpdateNull() { + + UserModel userModel = new UserModel(); + userModel.setId(1); + userModel.setAge(23); + userModel.setName(null); // mybatis-plus是默认不更新 null的属性 +// https://blog.csdn.net/konnysnow/article/details/121421210 +// 当然也可以用 lambdaUpdateWrapper.set(userModel::getName, null); + int i = userMapper.updateById(userModel); + log.info("Updated {}", i); + } + + @Test + public void testUpdateSetNull() { + UserModel userModel = new UserModel(); + userModel.setId(1); + userModel.setAge(231); + + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper(); + lambdaUpdateWrapper.set(UserModel::getName, null); // 强制修改数据库为null + lambdaUpdateWrapper.eq(UserModel::getId, userModel.getId()); + + int update = userMapper.update(userModel, lambdaUpdateWrapper); + log.info("Updated user{}", update); + + } + + @Test + public void deleteWithoutLogicDelete() { + UserModel userModel = new UserModel(); + userModel.setId(1); + userModel.setAge(2); + userModel.setName("222"); + userMapper.updateWithoutLogicDelete(userModel); + } + + @Test + public void testSelectUserModel() { + UserModel userModel = new UserModel(); +// userModel.setId(1); + userModel.setPhoneNumber("18010472947"); + UserModel userModel1 = userMapper.selectUserModel(userModel); + log.info("userModel1 {}", userModel1); + } + + + /** + * 不使用加密,条件查询的时候是没有使用自定义的类型处理器 + */ + @Test + public void testSelectUserModelNoEncrypt() { + LambdaUpdateWrapper userModelLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); + userModelLambdaUpdateWrapper.eq(UserModel::getPhoneNumber, "18010472947"); + List userModels = userMapper.selectList(userModelLambdaUpdateWrapper); + log.info("userModels {}", userModels); + } + + + @Test + public void testSelectUserModelEncrypt() { + UserModel userModel = new UserModel(); +// userModel.setId(1); + userModel.setPhoneNumber("18010472947"); + List userModels = userMapper.listByNumber(userModel); + log.info("userModels {}", userModels); + } + + +} diff --git a/app/src/test/java/hxy/dream/dao/mybatis/PureMybatisTest.java b/app/src/test/java/hxy/dream/dao/mybatis/PureMybatisTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e4965bb262d5dd725bd08fdd7965f4af7a9f219e --- /dev/null +++ b/app/src/test/java/hxy/dream/dao/mybatis/PureMybatisTest.java @@ -0,0 +1,63 @@ +package hxy.dream.dao.mybatis; + +import hxy.dream.dao.mapper.UserMapper; +import hxy.dream.dao.model.UserModel; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Reader; +import java.util.Arrays; + +/** + * @author eric + * @description + * @date 2024/6/24 + */ +public class PureMybatisTest { + + private static final Logger log = LoggerFactory.getLogger(PureMybatisTest.class); + + + static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + public static void beforeAll() { + String resource = "mybatis-config.xml"; + try (Reader reader = Resources.getResourceAsReader(resource)) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void insertMultipleUsers2() { + UserModel userModel = new UserModel(); + userModel.setId(1); + userModel.setAge(111); + + UserModel userModel1 = new UserModel(); + userModel1.setId(2); + userModel1.setAge(222); + + + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper userMapper = session.getMapper(UserMapper.class); +// 同一个session,没有提交就是在同一个Transaction里面 + for (UserModel user : Arrays.asList(userModel, userModel1)) { + int i = userMapper.updateWithoutLogicDelete(user); + log.info("Updated {}", i); + } + session.commit(); // 提交事务 + } catch (Exception e) { + e.printStackTrace(); + } + // session 会自动close + } +} diff --git a/app/src/test/java/hxy/dream/jdbc/JdbcTest.java b/app/src/test/java/hxy/dream/jdbc/JdbcTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6b9e08cd1fb523782000296c5d3db0456199be58 --- /dev/null +++ b/app/src/test/java/hxy/dream/jdbc/JdbcTest.java @@ -0,0 +1,87 @@ +package hxy.dream.jdbc; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class JdbcTest { + + private static final Logger log = LoggerFactory.getLogger(JdbcTest.class); + + + @Test + public void testJdbcInsert() throws ClassNotFoundException, SQLException { + // 1. 注册驱动 + // 使用java.sql.DriverManager类的静态方法registerDriver(Driver driver) + // Driver是一个接口,参数传递:MySQL驱动程序的实现类 + // DriverManager.registerDriver(new Driver()); + // 查看驱动类源码,注册两次驱动,浪费资源 + Class.forName("com.mysql.cj.jdbc.Driver"); + // 2. 获得连接 + // uri:数据库地址 jdbc:mysql://连接主机ip:端口号//数据库名字= + String url = "jdbc:mysql://mysql.cupb.top:3306/eric"; + // static Connection getConnection(String url, String user, String password) + // 返回值是java.sql.Connection接口的实现类,在MySQL驱动程序中 + Connection conn = DriverManager.getConnection(url, "eric", "dream,1234.."); + System.out.println(conn);// com.mysql.jdbc.JDBC4Connection@10d1f30 + // 3. 获得语句执行平台,通过数据库连接对象,获取到SQL语句的执行者对象 + //conn对象,调用方法 Statement createStatement() 获取Statement对象,将SQL语句发送到数据库 + //返回的是Statement接口的实现类对象,在MySQL驱动程序中 + Statement stat = conn.createStatement(); + System.out.println(stat);//com.mysql.jdbc.StatementImpl@137bc9 + // 4. 执行sql语句 + //通过执行者对象调用方法执行SQL语句,获取结果 + //int executeUpdate(String sql) 执行数据库中的SQL语句,仅限于insert,update,delete + //返回值int,操作成功数据库的行数 + int row = stat.executeUpdate("INSERT INTO user_model(name,age,address) VALUES('汽车用品',50,'疯狂涨价')"); + log.info("affect row {}", row); + // 5. 释放资源 + stat.close(); + conn.close(); +// 原文链接:https://blog.csdn.net/qq_35537301/article/details/81154182 + } + + + @Test + public void testJdbcSelect() throws ClassNotFoundException, SQLException { + Class.forName("com.mysql.cj.jdbc.Driver"); + // 2. 获得连接 + // uri:数据库地址 jdbc:mysql://连接主机ip:端口号//数据库名字 + String url = "jdbc:mysql://mysql.cupb.top:3306/eric"; + // static Connection getConnection(String url, String user, String password) + // 返回值是java.sql.Connection接口的实现类,在MySQL驱动程序中 + Connection conn = DriverManager.getConnection(url, "eric", "dream,1234.."); + + String id = "1"; + String name = "汽车用品"; + + // 拼写SQL语句 + String sql = "select * from user_model where id = ? or name = ? "; + // 3.获取执行SQL语句 + //Connection接口 + PreparedStatement pst = conn.prepareStatement(sql); + //调用pst对象的setXXX方法设置问号占位符的参数 下面参数的设置具体是由 mysql驱动实现的。(面向接口编程,只管申明,具体执行由接口实现) + pst.setObject(1, id); + pst.setObject(2, name); + System.out.println(sql); + // 4.调用执行者对象方法,执行SQL语句获取结果集 + ResultSet rs = pst.executeQuery(); + // 5.处理结果集 + while (rs.next()) { + log.info(rs.getString("name") + "\t" + rs.getString("address")); + } + // 6.关闭资源 + rs.close(); + pst.close(); + conn.close(); +// 原文链接:https://blog.csdn.net/qq_35537301/article/details/81154182 + } + +} diff --git a/app/src/test/java/hxy/dream/thread/VirtureThreadTest.java b/app/src/test/java/hxy/dream/thread/VirtureThreadTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ee585f5262e48944b0a6228535a39e8b0bdd0eab --- /dev/null +++ b/app/src/test/java/hxy/dream/thread/VirtureThreadTest.java @@ -0,0 +1,36 @@ +package hxy.dream.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.stream.IntStream; + +/** + * @author eric + * @description + * @date 2023/9/22 + */ +public class VirtureThreadTest { + + private static final Logger log = LoggerFactory.getLogger(VirtureThreadTest.class); + + + public static void main(String[] args) { + + var value = "虚拟线程"; + log.info(value); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + IntStream.range(0, 10_000).forEach(i -> { + executor.submit(() -> { + log.info("Thread " + i + ""); + Thread.sleep(Duration.ofSeconds(1)); + return i; + }); + }); + } // executor.close() is called implicitly, and waits + } + +} diff --git a/app/src/test/java/hxy/dream/util/IntegerTest.java b/app/src/test/java/hxy/dream/util/IntegerTest.java index 7a22b2610fc668c310ddbf86345acfa6fc3d4ed8..bf88218d713b56cc4a50496019f32a2f8d5755c6 100644 --- a/app/src/test/java/hxy/dream/util/IntegerTest.java +++ b/app/src/test/java/hxy/dream/util/IntegerTest.java @@ -1,8 +1,7 @@ package hxy.dream.util; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * @author eric @@ -16,13 +15,13 @@ public class IntegerTest { public void tests(){ int i = 1290; Integer j =1290; - Assert.assertTrue(j==i); +// Assert.assertTrue(j==i); } @Test public void test2(){ Integer i = 1290; Integer j =1290; - Assert.assertTrue(j==i); +// Assert.assertTrue(j==i); } } diff --git a/app/src/test/java/hxy/dream/util/JacksonTest.java b/app/src/test/java/hxy/dream/util/JacksonTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a2c7446e47fe097af46e143941402653866fb164 --- /dev/null +++ b/app/src/test/java/hxy/dream/util/JacksonTest.java @@ -0,0 +1,69 @@ +package hxy.dream.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import hxy.dream.common.serializer.SqlDateJsonSerializer; +import hxy.dream.entity.dto.UserDTO; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Date; + +/** + * @author eric + * @description + * @date 2023/5/26 + */ +public class JacksonTest { + + private static final Logger log = LoggerFactory.getLogger(JacksonTest.class); + + @Test + public void testDateSerialization() throws Exception { + + Date date = new Date(122, 5, 23); + + String string = date.toString(); + log.info("{}", string); + + + ObjectMapper objectMapper = new ObjectMapper(); + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Date.class, new SqlDateJsonSerializer()); + objectMapper.registerModule(simpleModule); + String s = objectMapper.writeValueAsString(date); + log.info("{}", s); + + } + + @Test + public void testSqlDate() throws Exception { + + UserDTO userDTO = new UserDTO(); + userDTO.setId(1); + userDTO.setName("test"); + + // 看Date的序列化 + userDTO.setRegisterDate(new Date(110, 1, 1)); + + // 自定义序列化器 + ObjectMapper objectMapper = new ObjectMapper(); + if (false) { + // 这里相当于全局配置 + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Date.class, new SqlDateJsonSerializer()); + objectMapper.registerModule(simpleModule); + } + + // 修改配置即可。从com.fasterxml.jackson.databind.ser.std.SqlDateSerializer源代码看出来的 + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false); + + String s = objectMapper.writeValueAsString(userDTO); + log.info("{}", s); + + } + + +} diff --git a/app/src/test/resources/api/http-client.env.json b/app/src/test/resources/api/http-client.env.json new file mode 100644 index 0000000000000000000000000000000000000000..f81a90cb4ccdfd211ef97a1a39d81dfa44cdf1e9 --- /dev/null +++ b/app/src/test/resources/api/http-client.env.json @@ -0,0 +1,5 @@ +{ + "dev": { + "host": "http://localhost:8080" + } +} \ No newline at end of file diff --git a/app/src/test/resources/api/local.http b/app/src/test/resources/api/local.http index 291731a43df63df01e46ac774b864abc5da32299..7d6b6a72e5340fe5ff367f4cd638dc6273d1884f 100644 --- a/app/src/test/resources/api/local.http +++ b/app/src/test/resources/api/local.http @@ -1,7 +1,10 @@ -GET http://localhost:8080/user/get/12 +GET {{host}}/ + +### +GET {{host}}/user/get/1 ### 添加 -POST http://localhost:8080/user/add/body +POST {{host}}/user/add/body Content-Type: application/json Accept: application/json @@ -13,7 +16,7 @@ Accept: application/json } ### 添加 -POST http://localhost:8080/user/add/body +POST {{host}}/user/add/body Content-Type: application/json Accept: application/json @@ -26,28 +29,33 @@ Accept: application/json "age": 23 } -### +### 删除 +DELETE {{host}}/user/delete?id=2 + ### 添加 -POST http://localhost:8080/user/add2?gender=0&name=张三&age=22 +POST {{host}}/user/add2?gender=0&name=张三&age=22 Content-Type: application/x-www-form-urlencoded Accept: application/json ### 列表 -GET http://localhost:8080/user/list +GET {{host}}/user/list ### 事务测试传播机制 -GET http://localhost:8080/transaction/propagation +GET {{host}}/transaction/propagation ### 事务隔离级别测试 -GET http://localhost:8080/transaction/isolation +GET {{host}}/transaction/isolation ### 事务测试 -GET http://localhost:8080/ +GET {{host}}/ ### 返回json测试 -GET http://localhost:8080/exception +GET {{host}}/exception + +### +GET {{host}}/exception-output ### servlet @@ -64,7 +72,18 @@ GET localhost:8080/AsyncServlet ### ThreadLocal 线程安全测试 -GET localhost:8080/threadlocal/wrong?userId=122 +GET {{host}}/threadlocal/wrong?userId=122 ### ConcurentHashMap GET localhost:8080/concurrentHashMap/wrong + +### Long精度测试 +GET {{host}}/long + + +### 异步测试 + +GET {{host}}/async/test + +### +GET {{host}}/async/concurrency \ No newline at end of file diff --git a/app/src/test/resources/mybatis-config.xml b/app/src/test/resources/mybatis-config.xml new file mode 100644 index 0000000000000000000000000000000000000000..1d9cf7b9da0c0ff9fb612f83abe488e7ad453eb0 --- /dev/null +++ b/app/src/test/resources/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/asset/database.json b/asset/database.json new file mode 100644 index 0000000000000000000000000000000000000000..e636293dea155dc7c6511a0f78e3acd148dad55e --- /dev/null +++ b/asset/database.json @@ -0,0 +1,7 @@ +{ + "spring.datasource.password":"Newp0ss,2024!", + "spring.datasource.url":"jdbc:mysql://139.217.230.42:3306/test?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8", + "spring.datasource.p6spyurl":"jdbc:p6spy:mysql://139.217.230.42:3306/test?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8", + "spring.datasource.username":"test", + "spring.r2dbc.url":"r2dbc:mysql://139.217.230.42:3306/test?serverTimezone=GMT&characterEncoding=UTF-8" +} diff --git a/asset/gradle-proxy-image/init.gradle b/asset/gradle-proxy-image/init.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ba8b3b81884116e9a3449c107aeffb15a28505d3 --- /dev/null +++ b/asset/gradle-proxy-image/init.gradle @@ -0,0 +1,39 @@ +def urlMappings = [ + "https://repo.maven.apache.org/maven2" : "https://mirrors.tencent.com/nexus/repository/maven-public/", + "https://dl.google.com/dl/android/maven2": "https://mirrors.tencent.com/nexus/repository/maven-public/", + "https://plugins.gradle.org/m2" : "https://mirrors.tencent.com/nexus/repository/gradle-plugins/" +] + +def enableMirror = { + all { repo -> + if (repo instanceof MavenArtifactRepository) { + def originalUrl = repo.url.toString().replaceAll("/\$", "") + def mirrorUrl = urlMappings[originalUrl] + if (mirrorUrl) { + logger.lifecycle("Repository[${repo.url}] is mirrored to $mirrorUrl") + repo.setUrl(mirrorUrl) + } else { + logger.lifecycle("[Groovy]Repository[${repo.url}] not config proxy.") + } + } + } +} + +gradle.allprojects { + buildscript { + repositories.configure(enableMirror) + } + repositories.configure(enableMirror) +} + +gradle.beforeSettings { settings -> + settings.pluginManagement.repositories.configure(enableMirror) + // 6.8 及更高版本执行 DependencyResolutionManagement 配置 + if (gradle.gradleVersion >= "6.8") { + def drm = settings.dependencyResolutionManagement + drm.repositories.configure(enableMirror) + println("Gradle ${gradle.gradleVersion} DependencyResolutionManagement Configured $settings") + } else { + println("Gradle ${gradle.gradleVersion} DependencyResolutionManagement Ignored $settings") + } +} diff --git a/asset/gradle-proxy-image/init.gradle.kts b/asset/gradle-proxy-image/init.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..db25f2ebf2101ffb928d3a8aaf582bef7c4120e8 --- /dev/null +++ b/asset/gradle-proxy-image/init.gradle.kts @@ -0,0 +1,41 @@ +fun RepositoryHandler.enableMirror() { + all { + if (this is MavenArtifactRepository) { + val originalUrl = this.url.toString().removeSuffix("/") + urlMappings[originalUrl]?.let { + logger.lifecycle("Repository[$url] is mirrored to $it") + this.setUrl(it) + } ?: run { + logger.lifecycle("[Kotlin]Repository[$originalUrl] has no mirror configured") + } + } + } +} + +val urlMappings = mapOf( + "https://repo.maven.apache.org/maven2" to "https://mirrors.tencent.com/nexus/repository/maven-public/", + "https://dl.google.com/dl/android/maven2" to "https://mirrors.tencent.com/nexus/repository/maven-public/", + "https://plugins.gradle.org/m2" to "https://mirrors.tencent.com/nexus/repository/gradle-plugins/" +) + +gradle.allprojects { + buildscript { + repositories.enableMirror() + } + repositories.enableMirror() +} + +gradle.beforeSettings { + pluginManagement.repositories.enableMirror() + // 6.8 及更高版本执行 DependencyResolutionManagement 配置 + if (gradle.gradleVersion >= "6.8") { + val getDrm = settings.javaClass.getDeclaredMethod("getDependencyResolutionManagement") + val drm = getDrm.invoke(settings) + val getRepos = drm.javaClass.getDeclaredMethod("getRepositories") + val repos = getRepos.invoke(drm) as RepositoryHandler + repos.enableMirror() + println("Gradle ${gradle.gradleVersion} DependencyResolutionManagement Configured $settings") + } else { + println("Gradle ${gradle.gradleVersion} DependencyResolutionManagement Ignored $settings") + } +} \ No newline at end of file diff --git a/asset/img/HotswapAgentLocation.png b/asset/img/HotswapAgentLocation.png new file mode 100644 index 0000000000000000000000000000000000000000..60abe6e4f7111eaeaeaf211b5e7f183c9d26186e Binary files /dev/null and b/asset/img/HotswapAgentLocation.png differ diff --git a/asset/img/gradle-wrapper.png b/asset/img/gradle-wrapper.png index 8f2c0fe7c7b9114e55dca5ab048cc7b1770e983f..e8e51131efd03b7e28d7d61553a8a85f37f91141 100644 Binary files a/asset/img/gradle-wrapper.png and b/asset/img/gradle-wrapper.png differ diff --git a/asset/img/gradle.png b/asset/img/gradle.png new file mode 100644 index 0000000000000000000000000000000000000000..532579205765b5adfb7b7f1f125a7c905eff646a Binary files /dev/null and b/asset/img/gradle.png differ diff --git a/asset/img/jackson-date-format.png b/asset/img/jackson-date-format.png new file mode 100644 index 0000000000000000000000000000000000000000..3b9406546d0d66c6445206a7baf716a988f81581 Binary files /dev/null and b/asset/img/jackson-date-format.png differ diff --git a/asset/img/jackson-serialize-null.png b/asset/img/jackson-serialize-null.png new file mode 100644 index 0000000000000000000000000000000000000000..9f585605ac6e327321eb52fdd590b3039557695b Binary files /dev/null and b/asset/img/jackson-serialize-null.png differ diff --git a/asset/img/update-vm-param.png b/asset/img/update-vm-param.png new file mode 100644 index 0000000000000000000000000000000000000000..64f95cf68b8680de418055ee56608a0bd87c2f07 Binary files /dev/null and b/asset/img/update-vm-param.png differ diff --git a/asset/img/xml1.png b/asset/img/xml1.png new file mode 100644 index 0000000000000000000000000000000000000000..e57be969a84595d422a156e075d2696552b69943 Binary files /dev/null and b/asset/img/xml1.png differ diff --git a/asset/img/xml2.png b/asset/img/xml2.png new file mode 100644 index 0000000000000000000000000000000000000000..9027a9cf3660c7026a17349369889b4ef719dccf Binary files /dev/null and b/asset/img/xml2.png differ diff --git a/asset/java/HikariCPEndpoint.java b/asset/java/HikariCPEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..e8460aabe52649066234f61aedc31394e4a2c026 --- /dev/null +++ b/asset/java/HikariCPEndpoint.java @@ -0,0 +1,38 @@ +package com.bosch.config; + +import com.zaxxer.hikari.HikariConfigMXBean; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; + +import java.util.HashMap; +import java.util.Map; + + +@Endpoint(id="hikari") +public class HikariCPEndpoint { + + HikariDataSource dataSource; + + public HikariCPEndpoint(HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + @ReadOperation + public Map info() { + + Map info = new HashMap<>(); + //连接池配置 + HikariConfigMXBean hikariConfigMXBean = dataSource.getHikariConfigMXBean(); + info.put("total",hikariConfigMXBean.getMaximumPoolSize()); + + //连接池运行状态 + HikariPoolMXBean hikariPoolMXBean = dataSource.getHikariPoolMXBean(); + info.put("active",hikariPoolMXBean.getActiveConnections()); + info.put("idle",hikariPoolMXBean.getIdleConnections()); + info.put("thread",hikariPoolMXBean.getThreadsAwaitingConnection()); + return info; + } + +} \ No newline at end of file diff --git a/asset/java/WebMvcConfiguration.java b/asset/java/WebMvcConfiguration.java index 2fcbdfd4f33af337bf6e8aed3e6ffc87924e01e1..2cac41365f43b485e5e6f6be153f42f0be778213 100644 --- a/asset/java/WebMvcConfiguration.java +++ b/asset/java/WebMvcConfiguration.java @@ -11,6 +11,9 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter import java.util.List; +/** + * 这个只是配置了 SpringMVC那一层,此外还有对外的MQ消息啥的,其他使用jackson序列化redis啥的 + */ @EnableWebMvc @Configuration public class WebMvcConfiguration extends WebMvcConfigurerAdapter { diff --git a/app/src/main/resources/logback-dev.xml b/asset/logback-dev.xml similarity index 97% rename from app/src/main/resources/logback-dev.xml rename to asset/logback-dev.xml index adee6386980d17bdbde69854b8677ffd90626838..0572dd77e6392f7ddfbd3e504e0598c47e43a5e9 100755 --- a/app/src/main/resources/logback-dev.xml +++ b/asset/logback-dev.xml @@ -15,8 +15,8 @@ - - + + diff --git a/build.gradle b/build.gradle index fdc460226a6fcd253a2f6ca60995ed7e59b68b10..a8bf745a366e35ff8cdd6fbabc9096f3746d3cc4 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ */ buildscript { ext { - springBootVersion = '2.3.12.RELEASE' + springBootVersion = '3.3.5' // https://spring.io/projects/spring-boot#learn } repositories { mavenLocal() @@ -34,8 +34,19 @@ buildscript { plugins { id 'java-library' + // https://github.com/spring-gradle-plugins/dependency-management-plugin + id 'io.spring.dependency-management' version '1.1.6' id 'idea' id "com.google.cloud.tools.jib" version "2.0.0" +// https://github.com/graalvm/native-build-tools +// id 'org.graalvm.buildtools.native' version '0.9.28' +} + + +java { +// sourceCompatibility = JavaVersion.VERSION_21 + /* 指定jdk版本 */ +// targetCompatibility = JavaVersion.VERSION_21 } description = "Spring Boot multi gradle project" @@ -43,31 +54,35 @@ description = "Spring Boot multi gradle project" defaultTasks 'build' allprojects { - group "org.springframework.boot" + group = 'hxy.dream' + version = '1.0-SNAPSHOT' - repositories { - mavenLocal() - maven { name "Alibaba central"; url "https://maven.aliyun.com/repository/central" } - maven { name "Alibaba"; url "https://maven.aliyun.com/repository/public" } - maven { name "Alibaba google"; url "https://maven.aliyun.com/repository/google" } - maven { name "Alibaba gradle-plugin"; url "https://maven.aliyun.com/repository/gradle-plugin" } - maven { name "Alibaba spring"; url "https://maven.aliyun.com/repository/spring" } - maven { name "Alibaba spring-plugin"; url "https://maven.aliyun.com/repository/spring-plugin" } - maven { name "Alibaba grails-core"; url "https://maven.aliyun.com/repository/grails-core" } - maven { name "Alibaba apache-snapshots"; url "https://maven.aliyun.com/repository/apache-snapshots" } - maven { - url "https://mirrors.huaweicloud.com/repository/maven/" - name = "华为开源镜像库" - } - maven { url "https://repo.spring.io/libs-release" } - mavenCentral() - if (version.contains('-')) { - maven { url "https://repo.spring.io/milestone" } - } - if (version.endsWith('-SNAPSHOT')) { - maven { url "https://repo.spring.io/snapshot" } - } - } +// repositories { +// mavenLocal() +// maven { name "Alibaba central"; url "https://maven.aliyun.com/repository/central" } +// maven { name "Alibaba"; url "https://maven.aliyun.com/repository/public" } +// maven { name "Alibaba google"; url "https://maven.aliyun.com/repository/google" } +// maven { name "Alibaba gradle-plugin"; url "https://maven.aliyun.com/repository/gradle-plugin" } +// maven { name "Alibaba spring"; url "https://maven.aliyun.com/repository/spring" } +// maven { name "Alibaba spring-plugin"; url "https://maven.aliyun.com/repository/spring-plugin" } +// maven { name "Alibaba grails-core"; url "https://maven.aliyun.com/repository/grails-core" } +// maven { name "Alibaba apache-snapshots"; url "https://maven.aliyun.com/repository/apache-snapshots" } +// maven { +// url "https://mirrors.huaweicloud.com/repository/maven/" +// name = "华为开源镜像库" +// } +// maven { url "https://repo.spring.io/libs-release" } +// mavenCentral() +// if (version.contains('-')) { +// maven { url "https://repo.spring.io/milestone" } +// } +// if (version.endsWith('-SNAPSHOT')) { +// maven { url "https://repo.spring.io/snapshot" } +// } +// +// // 临时解决mybatis没有适配 SpringBoot3.0问题 +// maven { name "OSS Snapshot repository"; url "https://oss.sonatype.org/content/repositories/snapshots/" } +// } configurations.all { resolutionStrategy.cacheChangingModulesFor 0, "minutes" @@ -75,7 +90,8 @@ allprojects { test { // 要用junit可以换成:useJUnitPlatform() TestNG更好更丰富 - useTestNG() +// useTestNG() + useJUnitPlatform() } } @@ -90,17 +106,13 @@ subprojects { apply plugin: 'io.spring.dependency-management' /* 依赖管理,用来传递spring的依赖 */ // apply plugin: 'id "com.google.cloud.tools.jib" version "2.0.0"' - group = 'hxy.dream' - version = '1.0-SNAPSHOT' - /* 指定jdk版本 */ - sourceCompatibility = 11 - targetCompatibility = 11 /* java编译的时候缺省状态下会因为中文字符而失败 */ [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' repositories { mavenLocal() +// maven { name "Alibaba central"; url "https://maven.aliyun.com/repository/central" } maven { name "Alibaba"; url "https://maven.aliyun.com/repository/public" } maven { name "Alibaba google"; url "https://maven.aliyun.com/repository/google" } @@ -119,13 +131,39 @@ subprojects { /* 添加通用依赖 */ dependencies { + +// 暂不支持SpringBoot3 https://gitee.com/huoyo/ko-time/issues/I880X2 +// implementation 'cn.langpy:ko-time:3.0.1' + implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'jakarta.servlet:jakarta.servlet-api:6.1.0' + implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'com.squareup.okio:okio:3.9.1' // 解决mock启动错误 + + implementation 'org.springframework.data:spring-data-redis' + +// https://github.com/brettwooldridge/HikariCP + implementation 'com.zaxxer:HikariCP:6.0.0' +// https://github.com/p6spy/p6spy + implementation 'p6spy:p6spy:3.9.1' + implementation 'commons-io:commons-io:2.17.0' + + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' // Java 8 Datatypes + implementation 'com.fasterxml.jackson:jackson-bom' // Java 8 Datatypes compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok' + testImplementation 'io.projectreactor:reactor-test' + + +// 有待进一步完善 https://github.com/xpc1024/doc-apis +// implementation 'com.doc-apis:doc-apis-starter:1.0.0' // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-sentinel // compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-alibaba-sentinel', version: '0.9.0.RELEASE' @@ -133,10 +171,13 @@ subprojects { } } - -group 'hxy.dream' -version '1.0-SNAPSHOT' - +// +//graalvmNative { +// binaries.all { +// resources.autodetect() +// } +// toolchainDetection = false +//} //下面是google构建docker镜像插件的配置 jib { @@ -156,4 +197,4 @@ jib { mainClass = 'com.xxx.RunApplication' ports = ['8080'] } -} +} \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index 0ec3f28adb607034e656e9e853ce472ddd9456fd..ddee9307bc29cdb72adbd604eb57a297d261568e 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -15,17 +15,21 @@ repositories { dependencies { api project(':dao') /* 子模块之间的依赖 */ - api 'com.ejlchina:okhttps-jackson:3.4.0' + implementation 'cn.zhxu:okhttps:4.0.3' + implementation 'cn.zhxu:okhttps-jackson:4.0.3' api 'org.springframework.boot:spring-boot-starter-mail' - - implementation group: 'org.javassist', name: 'javassist', version: '3.27.0-GA' - implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.6' - implementation 'org.redisson:redisson:3.15.0' + implementation 'org.springframework.data:spring-data-redis' + implementation 'redis.clients:jedis' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.0' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.0' // Java 8 Datatypes + implementation 'com.fasterxml.jackson:jackson-bom:2.18.0' // Java 8 Datatypes + implementation group: 'org.javassist', name: 'javassist', version: '3.30.2-GA' + implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.22.1' + implementation 'org.redisson:redisson:3.37.0' // https://cnblogs.com/keeya/p/13846807.html // https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter // compile 'org.redisson:redisson-spring-boot-starter:3.15.0' - testImplementation group: 'junit', name: 'junit', version: '4.12' } diff --git a/common/src/main/java/hxy/dream/common/configuration/AsyncConfig.java b/common/src/main/java/hxy/dream/common/configuration/AsyncConfig.java index 795241ecbbd16e343f76524ad31e94398c7ddae7..6a43f72d439a210a85c4348d3287b421fabd5561 100644 --- a/common/src/main/java/hxy/dream/common/configuration/AsyncConfig.java +++ b/common/src/main/java/hxy/dream/common/configuration/AsyncConfig.java @@ -9,7 +9,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.DispatcherServlet; -import javax.servlet.MultipartConfigElement; +import jakarta.servlet.MultipartConfigElement; /** * @author eric @@ -46,14 +46,5 @@ public class AsyncConfig { // } // return registration; // } - @Bean - public FilterRegistrationBean someFilterRegistration() { - FilterRegistrationBean registration = new FilterRegistrationBean(); - registration.setFilter(new TokenFilter()); - registration.addUrlPatterns("/*"); - registration.setAsyncSupported(true); - registration.setName("repeatableFilter"); - registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); - return registration; - } + } diff --git a/common/src/main/java/hxy/dream/common/configuration/BaseWebMvcConfig.java b/common/src/main/java/hxy/dream/common/configuration/BaseWebMvcConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c47cfd99470e87b084ca30c2af3c847aa4e6b685 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/configuration/BaseWebMvcConfig.java @@ -0,0 +1,48 @@ +package hxy.dream.common.configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +@Configuration +public class BaseWebMvcConfig extends WebMvcConfigurationSupport { + + @Autowired + ObjectMapper objectMapper; + + @Override + protected void extendMessageConverters(List> converters) { + for (HttpMessageConverter converter : converters) { + // 解决controller返回普通文本中文乱码问题 + if (converter instanceof StringHttpMessageConverter) { +// ContentType: text/html,Chaset=UTF-8 + ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8); + } + + if (converter instanceof MappingJackson2HttpMessageConverter) { + MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converter; + // 把自己定义的序列化措施,放进去。 + mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper); + // 解决controller返回json对象中文乱码问题 + mappingJackson2HttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8); + } + } + } + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + //setUseSuffixPatternMatch 后缀模式匹配 + configurer.setUseSuffixPatternMatch(true); + + //setUseTrailingSlashMatch 自动后缀路径模式匹配。尾斜杠默认不被支持,如果没有显式指定将返回404。 + configurer.setUseTrailingSlashMatch(true); + } +} diff --git a/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java b/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java index cbd6e448fc9b75dacfa1a984a59ce9264a74ba1f..f936a276b666f248f490d0b03d8b8d3798ba205b 100644 --- a/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java +++ b/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java @@ -10,28 +10,47 @@ package hxy.dream.common.configuration; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.DataChangeRecorderInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import hxy.dream.common.serializer.BaseEnumDeserializer; import hxy.dream.common.serializer.BaseEnumSerializer; import hxy.dream.common.serializer.BaseLongSerializer; import hxy.dream.common.serializer.DateJsonDeserializer; import hxy.dream.common.serializer.DateJsonSerializer; import hxy.dream.common.serializer.SimpleDeserializersWrapper; +import hxy.dream.common.serializer.StringTrimDeserializer; import hxy.dream.entity.enums.BaseEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Date; +import java.util.TimeZone; @Slf4j @Configuration public class BeanConfig { + @Bean public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { SimpleModule simpleModule = new SimpleModule(); @@ -44,15 +63,39 @@ public class BeanConfig { simpleModule.addSerializer(BaseEnum.class, new BaseEnumSerializer()); simpleModule.addSerializer(Date.class, new DateJsonSerializer()); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + // 超过浏览器处理精度的Long类型转成String给前端 simpleModule.addSerializer(Long.class, new BaseLongSerializer()); simpleModule.addSerializer(Long.TYPE, new BaseLongSerializer()); + simpleModule.addDeserializer(String.class, new StringTrimDeserializer(String.class)); + + builder.timeZone(TimeZone.getDefault()); ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.registerModule(simpleModule); + // 设置日期格式化格式 +// objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + + + // 解决: Java 8 date/time type `java.time.LocalDateTime` not supported by default + // 支持 jdk 1.8 日期 ---- start --- + +// objectMapper.registerModule(new Jdk8Module()) +// .registerModule(new JavaTimeModule()) +// .registerModule(new ParameterNamesModule()); +// objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); +// objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + // --end -- +// objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + + // 配置忽略未知属性 - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); +// objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return objectMapper; } @@ -63,6 +106,14 @@ public class BeanConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + +// 数据变动记录插件 + DataChangeRecorderInnerInterceptor dataChangeRecorderInnerInterceptor = new DataChangeRecorderInnerInterceptor(); + // 配置安全阈值,例如限制批量更新或插入的记录数不超过 1000 条 + dataChangeRecorderInnerInterceptor.setBatchUpdateLimit(1000); + interceptor.addInnerInterceptor(dataChangeRecorderInnerInterceptor); + + // mybatisplus与pagehelper的功能冲突了,所以后面会带上两个limit PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInnerInterceptor.setMaxLimit(100L); @@ -72,6 +123,8 @@ public class BeanConfig { interceptor.addInnerInterceptor(paginationInnerInterceptor); interceptor.addInnerInterceptor(blockAttackInnerInterceptor); +// 非法SQL拦截 + interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor()); return interceptor; } diff --git a/common/src/main/java/hxy/dream/common/configuration/CustomLogContextListener.java b/common/src/main/java/hxy/dream/common/configuration/CustomLogContextListener.java new file mode 100644 index 0000000000000000000000000000000000000000..d22ae27ebe09526100edea8c13a9f5296f0e762e --- /dev/null +++ b/common/src/main/java/hxy/dream/common/configuration/CustomLogContextListener.java @@ -0,0 +1,125 @@ +package hxy.dream.common.configuration; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.LoggerContextListener; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.spi.ContextAwareBase; +import ch.qos.logback.core.spi.LifeCycle; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; + +/** + * @version 1.0 + * @class: CustomLogContextListener + * @description: 定义logback 日志监听器,指定日志文件存放目录 主要是解决远程脚本部署的时候发现,日志./logs会新建在用户目录下,而不是应用部署的目录下 + */ +public class CustomLogContextListener extends ContextAwareBase implements LoggerContextListener, LifeCycle { + + /** + * 存储日志路径标识 + */ + private static final String LOG_PAHT_KEY = "LOG_PATH"; + + @Override + public boolean isResetResistant() { + return false; + } + + @Override + public void onStart(LoggerContext loggerContext) { + + } + + @Override + public void onReset(LoggerContext loggerContext) { + + } + + @Override + public void onStop(LoggerContext loggerContext) { + + } + + @Override + public void onLevelChange(Logger logger, Level level) { + + } + + /** + * // classpath:file:/home/eric/Project/RBLC/spf_rblc_backend/target/rblc-spf.jar!/BOOT-INF/classes!/ + * // classpath:nested:/home/eric/Project/Java/air-share/build/libs/air-share-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/ + */ + @Override + public void start() { + String classpath = null; + + if (true) { + // SpringBoot 推荐 + try { + classpath = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX).getPath(); + System.out.println("springboot get classpath:" + classpath); + } catch (FileNotFoundException e) { + System.err.println("File not found" + e.getMessage()); + } + } else { + // 非Spring推荐 + ProtectionDomain protectionDomain = CustomLogContextListener.class.getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + URL location = codeSource.getLocation(); + System.out.println("java :" + location); + System.out.println("java classpath:" + location.getPath()); + } + + String logPath = "./logs"; + + // 判断是否jar 包启动 + if (classpath != null && (classpath.contains("file:") || classpath.contains("nested:")) && classpath.contains(".jar") && classpath.contains("BOOT-INF")) { + String jarFilePath = classpath.substring(0, classpath.indexOf(".jar")); + System.out.println("jarFilePath:" + jarFilePath); + File file = new File(jarFilePath); + String currentPath = file.getParent(); +// String currentPath = new File(jarFilePath).getParentFile().getParentFile().getParent(); + // 如果是jar包启动的,那么获取当前jar包程序的路径,作为日志存放的位置 + if (classpath.startsWith("file:")) { + currentPath = currentPath.replace("file:", ""); + } else if (classpath.startsWith("nested:")) { + currentPath = currentPath.replace("nested:", ""); + } else { + int i = currentPath.indexOf(File.separator); + currentPath = currentPath.substring(i); + } + logPath = currentPath + File.separator + "logs"; + } + + // 判断文件夹是否存在,不存在需要新建 + File file = new File(logPath); + if (!file.exists()) { + boolean mkdirs = file.mkdirs(); + if (mkdirs) { + System.out.println("日志存储路径创建成功 " + logPath); + } + } + + System.out.println("日志存储路径 " + logPath); + System.setProperty(LOG_PAHT_KEY, logPath); + Context context = getContext(); + context.putProperty(LOG_PAHT_KEY, logPath); + } + + @Override + public void stop() { + + } + + @Override + public boolean isStarted() { + return false; + } +} diff --git a/common/src/main/java/hxy/dream/common/configuration/DataSourceConfigLoader.java b/common/src/main/java/hxy/dream/common/configuration/DataSourceConfigLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..7ff51015ca83656e2581cad7660233ca5a33f777 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/configuration/DataSourceConfigLoader.java @@ -0,0 +1,87 @@ +package hxy.dream.common.configuration; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +/** + * B站Up主 【极海channel】 的方案 https://b23.tv/OTa283B + * 这个不需要 spring.factories 去配置生效 + */ +@Component +public class DataSourceConfigLoader implements BeanPostProcessor, EnvironmentAware { + + private static final Logger log = LoggerFactory.getLogger(DataSourceConfigLoader.class); + + private ConfigurableEnvironment environment; + + @Override + public void setEnvironment(Environment environment) { + this.environment = (ConfigurableEnvironment) environment; + } + + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof MybatisPlusAutoConfiguration) { //在mybatis初始化前搞定配置信息 + String currentProfile = environment.getProperty("spring.profiles.active"); + if (currentProfile.equalsIgnoreCase("dev")) { + log.warn("本地调试,直接忽略读取本地配置文件信息"); + } else { + + Map systemProperties = environment.getSystemProperties(); + // 读取配置文件,从配置文件中加载这个变量。 + String database = System.getProperty("user.home") + "/.config/eric-config/database.json"; + try { + File file = new File(database); + if (file.exists()) { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonObject = objectMapper.readTree(file); + if (!jsonObject.isEmpty()) { + String databaseUrl = jsonObject.get("spring.datasource.url").textValue(); + String databaseUsername = jsonObject.get("spring.datasource.username").textValue(); + String password = jsonObject.get("spring.datasource.password").textValue(); + if (databaseUrl.contains("jdbc:p6spy:mysql")) { + systemProperties.put("spring.datasource.driver-class-name", "com.p6spy.engine.spy.P6SpyDriver"); + } else { + systemProperties.put("spring.datasource.driver-class-name", "com.mysql.cj.jdbc.Driver"); + } + systemProperties.put("spring.datasource.url", databaseUrl); + systemProperties.put("spring.datasource.username", databaseUsername); + systemProperties.put("spring.datasource.password", password); + + String r2dbcDatabaseUrl = jsonObject.get("spring.r2dbc.url").textValue(); + systemProperties.put("spring.r2dbc.url", r2dbcDatabaseUrl); + systemProperties.put("spring.r2dbc.username", databaseUsername); + systemProperties.put("spring.r2dbc.password", password); + } else { + log.warn("数据库配置文件没有信息 {}", database); + } + } else { + log.error("没有数据库配置文件,那就配置yaml文件吧 {}", database); + } + + } catch (JsonProcessingException e) { + log.error("{}", e.getMessage(), e); + } catch (IOException e) { + log.error("{}", e.getMessage(), e); + } + } + } + return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + } +} diff --git a/common/src/main/java/hxy/dream/common/configuration/FilterConfiguration.java b/common/src/main/java/hxy/dream/common/configuration/FilterConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..099ceb1bcb47a983cadaa18c118f0f62451d2444 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/configuration/FilterConfiguration.java @@ -0,0 +1,55 @@ +package hxy.dream.common.configuration; + +import hxy.dream.common.filter.RequestTrimFilter; +import hxy.dream.common.filter.RepeatableFilter; +import hxy.dream.common.filter.TokenFilter; +import jakarta.servlet.DispatcherType; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FilterConfiguration { + + @Bean + public FilterRegistrationBean someFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new TokenFilter()); + registration.addUrlPatterns("/*"); + registration.setAsyncSupported(true); + registration.setName("tokenFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + + /** + * 添加去除参数头尾空格过滤器 + * + * @return + */ + @Bean + public FilterRegistrationBean trimFilter() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new RequestTrimFilter()); + registration.addUrlPatterns("/*"); + registration.setName("requestTrimFilter"); + registration.setOrder(Integer.MAX_VALUE - 1); + return registration; + } + + /** + * 让request可重复读取 + * + * @return + */ + @Bean + public FilterRegistrationBean streamFilter() { + FilterRegistrationBean streamBean = new FilterRegistrationBean(); + streamBean.setDispatcherTypes(DispatcherType.REQUEST); + streamBean.setFilter(new RepeatableFilter()); + streamBean.setName("repeatableFilter"); + streamBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return streamBean; + } +} diff --git a/common/src/main/java/hxy/dream/common/configuration/MybatisAuditHandler.java b/common/src/main/java/hxy/dream/common/configuration/MybatisAuditHandler.java index 11cabe678d836f59531a205d6e1cb8d9028803f0..8b63d185828a0a44171de4ffd09a53ec92cf5135 100644 --- a/common/src/main/java/hxy/dream/common/configuration/MybatisAuditHandler.java +++ b/common/src/main/java/hxy/dream/common/configuration/MybatisAuditHandler.java @@ -17,25 +17,29 @@ public class MybatisAuditHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { - if (LOG.isDebugEnabled()){ - LOG.debug("\n====>mybatis自动填充创建时间字段"); + LocalDateTime now = LocalDateTime.now(); + + if (LOG.isDebugEnabled()) { + LOG.debug("\n====>mybatis自动填充创建时间字段 ,当前时间 {}", now); } // 声明自动填充字段的逻辑。 // String userId = AuthHolder.getCurrentUserId(); String userId = ""; // this.strictInsertFill(metaObject,"creator",String.class, userId); - this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now()); + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, now); } @Override public void updateFill(MetaObject metaObject) { - if (LOG.isDebugEnabled()){ - LOG.debug("\n====>mybatis自动填充更新时间字段"); + LocalDateTime now = LocalDateTime.now(); + + if (LOG.isDebugEnabled()) { + LOG.debug("\n====>mybatis自动填充更新时间字段 ,当前时间 {}", now); } // 声明自动填充字段的逻辑。 // String userId = AuthHolder.getCurrentUserId(); String userId = ""; // this.strictUpdateFill(metaObject,"updater",String.class,userId); - this.strictUpdateFill(metaObject,"updateTime", LocalDateTime.class,LocalDateTime.now()); + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, now); } } \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/configuration/RemoteApiConfig.java b/common/src/main/java/hxy/dream/common/configuration/RemoteApiConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..9c2c58f908cd4817cd13989e2424bb3f0124eaee --- /dev/null +++ b/common/src/main/java/hxy/dream/common/configuration/RemoteApiConfig.java @@ -0,0 +1,22 @@ +package hxy.dream.common.configuration; + + +import hxy.dream.common.manager.RemoteApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.support.WebClientAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +@Configuration(proxyBeanMethods = false) +public class RemoteApiConfig { + + @Bean + RemoteApi remoteApiService() { + WebClient client = WebClient.builder().baseUrl("https://httpbin.org/").build(); + HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builderFor(WebClientAdapter.create(client)).build(); + // 后期会自动扫描注入,不需要手动指定注入了。但是上面host配置肯定还是需要的。 + return httpServiceProxyFactory.createClient(RemoteApi.class); + } + +} diff --git a/common/src/main/java/hxy/dream/common/configuration/RemoteEnvironmentPostProcessor.java b/common/src/main/java/hxy/dream/common/configuration/RemoteEnvironmentPostProcessor.java index 24947bce93ff3b9b6669589cfe088de9b7fadefc..be9e3ed20489cec0e8407570a9bc2336e9356cd6 100644 --- a/common/src/main/java/hxy/dream/common/configuration/RemoteEnvironmentPostProcessor.java +++ b/common/src/main/java/hxy/dream/common/configuration/RemoteEnvironmentPostProcessor.java @@ -1,40 +1,83 @@ package hxy.dream.common.configuration; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; -import org.springframework.context.annotation.Profile; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertiesPropertySource; -import org.springframework.stereotype.Component; +import java.io.File; +import java.io.IOException; +import java.util.Map; import java.util.Properties; /** - * @author Raina - * @description 远程存储环境变量修改本地 + * @description bean初始化之前,Spring环境变量修改 https://blog.csdn.net/weixin_43827985/article/details/114368232 + * 1. 可以读取Azue KeyValue + * 2. 可以读取本机的配置文件 + *

+ * 这个类生效靠:app/src/main/resources/META-INF/spring.factories * @date 2022/2/25 */ -@Component -@Profile(value = {"prod", "beta"}) +//@Profile(value = {"prod", "beta"}) // 貌似这个没啥用,其他环境变量的时候也跑了程序 public class RemoteEnvironmentPostProcessor implements EnvironmentPostProcessor { + private static final Logger log = LoggerFactory.getLogger(RemoteEnvironmentPostProcessor.class); + @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { String currentProfile = environment.getProperty("spring.profiles.active"); - if (currentProfile == null || currentProfile.equalsIgnoreCase("dev")) { - log.debug("本地调试,直接忽略"); - return; + if (currentProfile == null) { + // 这里需要带特定环境变量的配置文件,即没有 dev,beta,prod都是可以的 + Map systemProperties = environment.getSystemProperties(); + systemProperties.put("key", "value"); } else { - String url = environment.getProperty("azure.keyvault.url"); - final Properties properties = new Properties(); - if (url == null) { - // 从远程vault这种管理工具或者redis里面获取配置信息,然后再更新到本地的配置中 - properties.put("password", "vault获取的值"); - PropertiesPropertySource propertySource = new PropertiesPropertySource("prod", properties); + if (currentProfile.equalsIgnoreCase("dev")) { + log.warn("本地调试,直接忽略读取本地配置文件信息"); + } else { + final Properties properties = new Properties(); + + // 读取配置文件,从配置文件中加载这个变量。 【腾讯文档】系统配置信息 https://docs.qq.com/doc/DSFdLamxuUXNWRVZJ + String database = System.getProperty("user.home") + "/.config/eric-config/database.json"; + try { + File file = new File(database); + if (file.exists()) { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonObject = objectMapper.readTree(file); + if (!jsonObject.isEmpty()) { + String databaseUsername = jsonObject.get("spring.datasource.username").textValue(); + String databaseUrl = jsonObject.get("spring.datasource.url").textValue(); + String password = jsonObject.get("spring.datasource.password").textValue(); + properties.put("spring.datasource.url", databaseUrl); + properties.put("spring.datasource.username", databaseUsername); + properties.put("spring.datasource.password", password); + } else { + log.error("数据库配置文件没有信息 {}", database); + } + } else { + log.error("没有数据库配置文件,那就配置yaml文件吧 {}", database); + } + } catch (JsonProcessingException e) { + log.error("{}", e.getMessage(), e); + } catch (IOException e) { + log.error("{}", e.getMessage(), e); + } + + String url = environment.getProperty("azure.keyvault.url"); + if (url == null) { + // 从远程vault这种管理工具或者redis里面获取配置信息,然后再更新到本地的配置中 https://learn.microsoft.com/zh-cn/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-key-vault + properties.put("password", "vault获取的值"); + } + + PropertiesPropertySource propertySource = new PropertiesPropertySource(currentProfile, properties); +// PropertiesPropertySource propertySource = new PropertiesPropertySource("prod", properties); // 只修改指定环境变量的配置信息 environment.getPropertySources().addFirst(propertySource); + } } } diff --git a/common/src/main/java/hxy/dream/common/configuration/RequestLoggingConfig.java b/common/src/main/java/hxy/dream/common/configuration/RequestLoggingConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..d5d56c51ead4d62bf90d800eb1b993e84950aaa7 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/configuration/RequestLoggingConfig.java @@ -0,0 +1,24 @@ +package hxy.dream.common.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.CommonsRequestLoggingFilter; + +@Configuration +public class RequestLoggingConfig { + /** + * 类似gin的一样的请求log,不是详细的 + * + * @return + */ + @Bean + public CommonsRequestLoggingFilter logFilter() { + CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); + filter.setIncludeQueryString(true); + filter.setIncludePayload(true); + filter.setIncludeHeaders(true); + filter.setIncludeClientInfo(true); + filter.setAfterMessagePrefix("REQUEST DATA-"); + return filter; + } +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/configuration/WebMvcConfig.java b/common/src/main/java/hxy/dream/common/configuration/WebMvcConfig.java index 618da7ecbd8fe2c18e39bd6ed67aee95956381d1..41bfb45e44c35c97bedc94f93b0a3d2f455a3b23 100644 --- a/common/src/main/java/hxy/dream/common/configuration/WebMvcConfig.java +++ b/common/src/main/java/hxy/dream/common/configuration/WebMvcConfig.java @@ -3,6 +3,7 @@ package hxy.dream.common.configuration; import hxy.dream.common.converter.IntegerCodeToEnumConverterFactory; import hxy.dream.common.converter.StringCodeToEnumConverterFactory; import hxy.dream.common.converter.StringToDateConverterFactory; +import hxy.dream.common.converter.StringToStringConverter; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -23,6 +24,7 @@ public class WebMvcConfig implements WebMvcConfigurer { */ @Override public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new StringToStringConverter()); registry.addConverterFactory(new IntegerCodeToEnumConverterFactory()); registry.addConverterFactory(new StringCodeToEnumConverterFactory()); registry.addConverterFactory(new StringToDateConverterFactory()); diff --git a/common/src/main/java/hxy/dream/common/converter/StringToDateConverterFactory.java b/common/src/main/java/hxy/dream/common/converter/StringToDateConverterFactory.java index e33cd4c83b61bdedf68b534dbf613a9ccdb49ee6..485b8af0a6b85e882adcfbe5240c6f1f61b6db83 100644 --- a/common/src/main/java/hxy/dream/common/converter/StringToDateConverterFactory.java +++ b/common/src/main/java/hxy/dream/common/converter/StringToDateConverterFactory.java @@ -9,8 +9,6 @@ public class StringToDateConverterFactory implements ConverterFactory Converter getConverter(Class targetType) { - - return new StringToDateConverter(); } } diff --git a/common/src/main/java/hxy/dream/common/converter/StringToStringConverter.java b/common/src/main/java/hxy/dream/common/converter/StringToStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..c5617782fb8a3c9dd0a450bbd26ccbebcb9220d9 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/converter/StringToStringConverter.java @@ -0,0 +1,27 @@ +package hxy.dream.common.converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.convert.converter.Converter; + +/** + * 这个转换器,可以去掉前端参数里的"null"值。 + * 【腾讯文档】SpringMVC学习 + * https://docs.qq.com/doc/DeXNEanBlc09MWG9C + *

+ * GET {{host}}/file/list?pageNum=1&pageSize=20&searchValue=null + * 等于下面这个 + * GET {{host}}/file/list?pageNum=1&pageSize=20&searchValue= + */ +public class StringToStringConverter implements Converter { + private static final Logger log = LoggerFactory.getLogger(StringToStringConverter.class); + + @Override + public String convert(String source) { + log.info("convert source: {}", source); + if (source != null && "null".equals(source.toLowerCase())) { + return ""; + } + return source; + } +} diff --git a/common/src/main/java/hxy/dream/common/extend/GlobalExceptionHandler.java b/common/src/main/java/hxy/dream/common/extend/GlobalExceptionHandler.java index d4c90adf349e1a0ea86b144b95998ce10c0fce23..057c28b8996bd15e2bf7d3d75e40840fcff26af1 100644 --- a/common/src/main/java/hxy/dream/common/extend/GlobalExceptionHandler.java +++ b/common/src/main/java/hxy/dream/common/extend/GlobalExceptionHandler.java @@ -24,17 +24,18 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.validation.ConstraintViolationException; -import javax.validation.ValidationException; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ValidationException; + import java.io.IOException; import java.util.Map; /** - * Description:全局异常处理,采用@RestControllerAdvice + @ExceptionHandler解决 - *
自定义异常处理类 + * Description:全局异常处理,采用@RestControllerAdvice + @ExceptionHandler解决
+ * 自定义异常处理类 * * @author eric */ @@ -53,7 +54,8 @@ public class GlobalExceptionHandler implements InitializingBean { return new DefaultErrorAttributes() { @Override public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { - return super.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults().excluding(ErrorAttributeOptions.Include.EXCEPTION)); + return super.getErrorAttributes(webRequest, + ErrorAttributeOptions.defaults().excluding(ErrorAttributeOptions.Include.EXCEPTION)); } }; } @@ -64,7 +66,6 @@ public class GlobalExceptionHandler implements InitializingBean { res.sendError(ex.getHttpStatus().value(), ex.getMessage()); } - /** * 参数校验异常捕获 包括各种自定义的参数异常 * @@ -75,7 +76,8 @@ public class GlobalExceptionHandler implements InitializingBean { */ @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ConstraintViolationException.class) - public BaseResponseVO constraintViolationExceptionHandler(HttpServletRequest request, ConstraintViolationException e) { + public BaseResponseVO constraintViolationExceptionHandler(HttpServletRequest request, + ConstraintViolationException e) { log.warn("\n====>{} Exception Message: {}", request.getRequestURI(), e.getMessage(), e); return BaseResponseVO.error("参数错误", e.getMessage()); } @@ -98,16 +100,14 @@ public class GlobalExceptionHandler implements InitializingBean { } /** - * 方法参数校验 - * https://blog.csdn.net/chengliqu4475/article/details/100834090 + * 方法参数校验 https://blog.csdn.net/chengliqu4475/article/details/100834090 */ @ExceptionHandler(MethodArgumentNotValidException.class) public BaseResponseVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - log.warn(e.getMessage(), e); + log.warn("{}", e.getMessage(), e); return BaseResponseVO.error("参数检验出错啦!", e.getBindingResult().getFieldError().getDefaultMessage()); } - /** * 处理400参数错误 * @@ -129,8 +129,10 @@ public class GlobalExceptionHandler implements InitializingBean { */ @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) @ExceptionHandler(HttpRequestMethodNotSupportedException.class) - public BaseResponseVO handleHttpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException e) { - log.warn("\n====>[{}]不支持当前[{}]请求方法,应该是[{},{}]", request.getRequestURI(), e.getMethod(), e.getSupportedHttpMethods(), e.getSupportedMethods(), e); + public BaseResponseVO handleHttpRequestMethodNotSupportedException(HttpServletRequest request, + HttpRequestMethodNotSupportedException e) { + log.warn("\n====>[{}]不支持当前[{}]请求方法,应该是[{},{}]", request.getRequestURI(), e.getMethod(), + e.getSupportedHttpMethods(), e.getSupportedMethods(), e); return BaseResponseVO.badrequest("请求方法不支持", e.getMessage()); } @@ -149,9 +151,7 @@ public class GlobalExceptionHandler implements InitializingBean { } /** - * 这个应该放在最下面比较好,最后加载 - * 处理未定义的其他异常信息 - * 参数为空等 + * 这个应该放在最下面比较好,最后加载 处理未定义的其他异常信息 参数为空等 * * @param request * @param exception @@ -179,9 +179,11 @@ public class GlobalExceptionHandler implements InitializingBean { log.info("\n====>全局异常注入正常"); } + /** + * bean注入初始化 + */ @PostConstruct public void init() { log.info("\n====>@PostConstruct 全局异常注入正常"); } } - diff --git a/common/src/main/java/hxy/dream/common/extend/InteractiveRecord.java b/common/src/main/java/hxy/dream/common/extend/InteractiveRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..355dc5961cb198949afe0474fc86232ec73e703e --- /dev/null +++ b/common/src/main/java/hxy/dream/common/extend/InteractiveRecord.java @@ -0,0 +1,50 @@ +package hxy.dream.common.extend; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; +import reactor.netty.http.server.HttpServerRequest; + +import java.lang.reflect.Type; + +/** + * 非AOP方式记录参数,严重依赖Servlet + * + * 有待再次调试 https://www.bilibili.com/video/BV1ZW4y1Q7yG/ + * + */ +//@Component +public class InteractiveRecord extends RequestBodyAdviceAdapter implements ResponseBodyAdvice { + private static final Logger log = LoggerFactory.getLogger(InteractiveRecord.class); + + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + return true; + } + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + log.info("请求参数{},{}", body,request.getURI()); + return body; + } + + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + log.info("返回参数{}", body); + return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType); + } +} diff --git a/common/src/main/java/hxy/dream/common/extend/LogbackDingTalkAppender.java b/common/src/main/java/hxy/dream/common/extend/LogbackDingTalkAppender.java index b608e0bc659c4921a534fbe8dc79473d3c554aab..c6667b938c2ebca305da9c59d6db53922a94215d 100644 --- a/common/src/main/java/hxy/dream/common/extend/LogbackDingTalkAppender.java +++ b/common/src/main/java/hxy/dream/common/extend/LogbackDingTalkAppender.java @@ -1,68 +1,68 @@ -package hxy.dream.common.extend; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.UnsynchronizedAppenderBase; -import com.ejlchina.okhttps.HTTP; -import com.ejlchina.okhttps.OkHttps; -import com.ejlchina.okhttps.jackson.JacksonMsgConvertor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author eric - * @program inspector-server - * @description 错误消息发送到钉钉上 - * @date 2021/10/28 - */ -@Component -public class LogbackDingTalkAppender extends UnsynchronizedAppenderBase { - - private static final Logger log = LoggerFactory.getLogger(LogbackDingTalkAppender.class); - private HTTP http = HTTP.builder() - .baseUrl("http://easyprint.vip:9090/") - .addMsgConvertor(new JacksonMsgConvertor()) - .build(); - - @Value("spring.profiles.active") - String activeProfile; - - @Override - protected void append(ILoggingEvent eventObject) { - Level level = eventObject.getLevel(); - - if (activeProfile == null || activeProfile.contains("dev") || activeProfile.contains("test")) { - if (level.toInt() == Level.ERROR_INT) { - log.debug("\n====>当前是本地测试环境,错误信息不通知钉钉"); - } - return; - } - switch (level.toInt()) { - case Level.ERROR_INT: - // 发送到钉钉 - ConcurrentHashMap postBody = new ConcurrentHashMap<>(); - long timeStamp = eventObject.getTimeStamp(); - - // 专属业务日志名字,不同业务响应等级划分 - String loggerName = eventObject.getLoggerName(); - String msg = String.format("时间:%s,级别:%s,原因%s", timeStamp, eventObject.getLevel(), eventObject.getFormattedMessage()); - postBody.put("title", loggerName); - postBody.put("msg", msg); - String s = http.async("api/dingtalk/v1/notice").setBodyPara(postBody).bodyType(OkHttps.JSON).post().getResult().getBody().toString(); - log.debug("钉钉返回信息{}", s); - break; - case Level.WARN_INT: - case Level.INFO_INT: - // -// log.info("发送日志信息到钉钉" + eventObject); - break; - default: - // 默认处理 - } - - } -} +//package hxy.dream.common.extend; +// +//import ch.qos.logback.classic.Level; +//import ch.qos.logback.classic.spi.ILoggingEvent; +//import ch.qos.logback.core.UnsynchronizedAppenderBase; +//import com.ejlchina.okhttps.HTTP; +//import com.ejlchina.okhttps.OkHttps; +//import com.ejlchina.okhttps.jackson.JacksonMsgConvertor; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Component; +// +//import java.util.concurrent.ConcurrentHashMap; +// +///** +// * @author eric +// * @program inspector-server +// * @description 错误消息发送到钉钉上 +// * @date 2021/10/28 +// */ +//@Component +//public class LogbackDingTalkAppender extends UnsynchronizedAppenderBase { +// +// private static final Logger log = LoggerFactory.getLogger(LogbackDingTalkAppender.class); +// private HTTP http = HTTP.builder() +// .baseUrl("http://easyprint.vip:9090/") +// .addMsgConvertor(new JacksonMsgConvertor()) +// .build(); +// +// @Value("spring.profiles.active") +// String activeProfile; +// +// @Override +// protected void append(ILoggingEvent eventObject) { +// Level level = eventObject.getLevel(); +// +// if (activeProfile == null || activeProfile.contains("dev") || activeProfile.contains("test")) { +// if (level.toInt() == Level.ERROR_INT) { +// log.debug("\n====>当前是本地测试环境,错误信息不通知钉钉"); +// } +// return; +// } +// switch (level.toInt()) { +// case Level.ERROR_INT: +// // 发送到钉钉 +// ConcurrentHashMap postBody = new ConcurrentHashMap<>(); +// long timeStamp = eventObject.getTimeStamp(); +// +// // 专属业务日志名字,不同业务响应等级划分 +// String loggerName = eventObject.getLoggerName(); +// String msg = String.format("时间:%s,级别:%s,原因%s", timeStamp, eventObject.getLevel(), eventObject.getFormattedMessage()); +// postBody.put("title", loggerName); +// postBody.put("msg", msg); +// String s = http.async("api/dingtalk/v1/notice").setBodyPara(postBody).bodyType(OkHttps.JSON).post().getResult().getBody().toString(); +// log.debug("钉钉返回信息{}", s); +// break; +// case Level.WARN_INT: +// case Level.INFO_INT: +// // +//// log.info("发送日志信息到钉钉" + eventObject); +// break; +// default: +// // 默认处理 +// } +// +// } +//} diff --git a/common/src/main/java/hxy/dream/common/extend/ParameterRecord.java b/common/src/main/java/hxy/dream/common/extend/ParameterRecord.java index aade4aed882308f75721ad4ef30071898b5a4f78..ac3f23cc814eedbe1a454cde54d34bc99aadaa10 100644 --- a/common/src/main/java/hxy/dream/common/extend/ParameterRecord.java +++ b/common/src/main/java/hxy/dream/common/extend/ParameterRecord.java @@ -3,6 +3,7 @@ package hxy.dream.common.extend; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.fasterxml.jackson.databind.ObjectMapper; import hxy.dream.common.util.IPAddress; +import hxy.dream.common.util.SpringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; @@ -21,8 +22,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -31,7 +32,6 @@ import java.io.InputStream; import java.lang.reflect.Method; import java.util.Map; - /** * 请求信息,参数记录,时间 * @@ -42,224 +42,235 @@ import java.util.Map; @Order(1) public class ParameterRecord { - private static final Logger log = LoggerFactory.getLogger("parameter"); - - private final int timeLength = 150; - private final int dataLength = 250; - private final int paramLength = 1000; - - String[] ignoreUrl = new String[]{"actuator"}; - - ObjectMapper objectMapper = new ObjectMapper(); - - @Around("execution(* hxy.dream.*.controller.*.*(..))") - public Object processTx(ProceedingJoinPoint jp) throws Throwable { - String ip = null, url = null, requestURI = null, targetMethod = null, params = null, resultJson = null, requestMethod = null, ipAddr = null; - long timeConsuming = 0; - long start = System.currentTimeMillis(); - - try { - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (requestAttributes == null) { - return null; - } - HttpServletRequest request = requestAttributes.getRequest(); - ipAddr = IPAddress.getIpAddr(request); - requestMethod = request.getMethod(); - requestURI = request.getRequestURI(); - final StringBuffer requestURL = request.getRequestURL(); - - final Signature signature1 = jp.getSignature(); - if (signature1 != null && signature1 instanceof MethodSignature) { - MethodSignature signature = (MethodSignature) signature1; - Method method = signature.getMethod(); - Class targetClass = method.getDeclaringClass(); - //拼接请求的接口名 end - url = getUrl(targetClass, method); - - String name = signature.getName(); - - //接口对应的方法名 - targetMethod = targetClass.getSimpleName() + "." + method.getName() + "()"; - } - - boolean contains = false; - for (String a : ignoreUrl) { - contains = url.contains(a); - if (contains) { - break; - } - } - - - final Map parameterMap = request.getParameterMap(); - - if (parameterMap.size() == 0) { - BufferedReader reader = null; - try { - reader = request.getReader(); - String str, wholeStr = ""; - while ((str = reader.readLine()) != null) { - wholeStr += str; - } - if (StringUtils.isNotEmpty(wholeStr)) { - params = wholeStr; - } - } catch (IOException e1) { - log.error("{}", e1.getMessage(), e1); - } - } else { - params = objectMapper.writeValueAsString(parameterMap); + private static final Logger log = LoggerFactory.getLogger("parameter"); + + private final int timeLength = 150; + private final int dataLength = 250; + private final int paramLength = 1000; + + String[] ignoreUrl = new String[] { "actuator" }; + + ObjectMapper objectMapper = SpringUtils.getBean(ObjectMapper.class); + + @Around("execution(* hxy.dream.*.controller.*.*(..))") + public Object processTx(ProceedingJoinPoint jp) throws Throwable { + String ip = null, url = null, requestURI = null, targetMethod = null, params = null, resultJson = null, + requestMethod = null, ipAddr = null; + long timeConsuming = 0; + long start = System.currentTimeMillis(); + + try { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + if (requestAttributes == null) { + return null; + } + HttpServletRequest request = requestAttributes.getRequest(); + ipAddr = IPAddress.getIpAddr(request); + requestMethod = request.getMethod(); + requestURI = request.getRequestURI(); + final StringBuffer requestURL = request.getRequestURL(); + log.debug(requestURI); + + final Signature signature1 = jp.getSignature(); + if (signature1 != null && signature1 instanceof MethodSignature) { + MethodSignature signature = (MethodSignature) signature1; + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + // 拼接请求的接口名 end + url = getUrl(targetClass, method); + + String name = signature.getName(); + + // 接口对应的方法名 + targetMethod = targetClass.getSimpleName() + "." + method.getName() + "()"; + } + + boolean contains = false; + for (String a : ignoreUrl) { + if (url != null) { + contains = url.contains(a); + if (contains) { + break; + } + } else { + break; + } + } + + final Map parameterMap = request.getParameterMap(); + + if (parameterMap.size() == 0) { + BufferedReader reader = null; + try { + reader = request.getReader(); + String str, wholeStr = ""; + while ((str = reader.readLine()) != null) { + wholeStr += str; + } + if (StringUtils.isNotEmpty(wholeStr)) { + params = wholeStr; + } + } catch (IOException e1) { + log.error("{}", e1.getMessage(), e1); + } + } else { + params = objectMapper.writeValueAsString(parameterMap); // 参数记录的目的就是为了数据恢复,所以这里无论多大的参数都要记录下来! // if (params.length() > paramLength) { // params = params.substring(0, paramLength) + "....eg...."; // } - } - - ip = request.getRemoteHost(); - - if (jp != null) { - boolean paramsExist = true; - Object[] args = jp.getArgs(); - for (Object object : args) { - if (object == null) { - paramsExist = false; - } - } - SourceLocation sourceLocation = jp.getSourceLocation(); - - // 这里会抛出Controller的业务异常 - Object result = jp.proceed(); - if (result != null) { - resultJson = objectMapper.writeValueAsString(result); - - timeConsuming = System.currentTimeMillis() - start; - - if (resultJson.length() > dataLength) { - resultJson = resultJson.substring(0, dataLength) + "....略...."; - } - if (url != null && !contains) { - - if (timeConsuming > timeLength) { - log.warn("\n\n====> visitor host ip :{}\n====> url : {} uri : {} \n====>method:{}\n====>parameter:{}\n====>response:{}\n====>耗时较长:{}ms", ipAddr + " 代理:" + ip, requestMethod + " " + url, requestURI, targetMethod, params, resultJson, timeConsuming); - } else { - log.info("\n\n====> visitor host ip :{}\n====> url : {} uri : {} \n====>method:{}\n====>parameter:{}\n====>response:{}\n====>耗时:{}ms", ipAddr + " 代理:" + ip, requestMethod + " " + url, requestURI, targetMethod, params, resultJson, timeConsuming); - } - } - return result; - } - - } - - } catch (Throwable throwable) { + } + + ip = request.getRemoteHost(); + + if (jp != null) { + boolean paramsExist = true; + Object[] args = jp.getArgs(); + for (Object object : args) { + if (object == null) { + paramsExist = false; + } + } + SourceLocation sourceLocation = jp.getSourceLocation(); + + // 这里会抛出Controller的业务异常 + Object result = jp.proceed(); + if (result != null) { + resultJson = objectMapper.writeValueAsString(result); + + timeConsuming = System.currentTimeMillis() - start; + + if (resultJson.length() > dataLength) { + resultJson = resultJson.substring(0, dataLength) + "....略...."; + } + if (url != null && !contains) { + + if (timeConsuming > timeLength) { + log.warn("\n\n====> visitor host ip :{}\n====> url : {} uri : {} \n====>method:{}\n====>parameter:{}\n====>response:{}\n====>耗时较长:{}ms", + ipAddr + " 代理:" + ip, requestMethod + " " + url, requestURI, targetMethod, params, + resultJson, timeConsuming); + } else { + log.info("\n\n====> visitor host ip :{}\n====> url : {} uri : {} \n====>method:{}\n====>parameter:{}\n====>response:{}\n====>耗时:{}ms", + ipAddr + " 代理:" + ip, requestMethod + " " + url, requestURI, targetMethod, params, + resultJson, timeConsuming); + } + } + return result; + } + + } + + } catch (Throwable throwable) { // 这里会捕获Controller里面的业务异常直接返回 - timeConsuming = System.currentTimeMillis() - start; - log.info("\n\n====> visitor host ip : {}\n====> url : {} uri : {} \n====> method : {}\n====> parameter : {}\n====> exception : {}\n====> 耗时 : {}ms", ipAddr + " 代理:" + ip, requestMethod + " " + url, requestURI, targetMethod, params, throwable.getMessage(), timeConsuming); - // 异常继续抛出,交由全局异常捕获再处理 - throw throwable; - } - - return null; - } - - private String getUrl(Class targetClass, Method method) { - //拼接请求的接口名 start - StringBuilder requestMapping = new StringBuilder(); - - RequestMapping mapping = targetClass.getAnnotation(RequestMapping.class); - if (mapping != null) { - String[] value = mapping.value(); - if (value.length > 0) { - requestMapping.append(value[0]); - } - } - - mapping = method.getAnnotation(RequestMapping.class); - if (mapping != null) { - String[] value = mapping.value(); - if (value.length > 0) { - requestMapping.append(value[0]); - } else { - String[] path = mapping.path(); - if (path.length > 0) { - requestMapping.append(path[0]); - } - } - } else { - PostMapping postMapping = method.getAnnotation(PostMapping.class); - if (postMapping != null) { - String[] value = postMapping.value(); - if (value.length > 0) { - requestMapping.append(value[0]); - } else { - String[] path = postMapping.path(); - if (path.length > 0) { - requestMapping.append(path[0]); - } - } - } else { - GetMapping getMapping = method.getAnnotation(GetMapping.class); - if (getMapping != null) { - String[] value = getMapping.value(); - if (value.length > 0) { - requestMapping.append(value[0]); - } else { - String[] path = getMapping.path(); - if (path.length > 0) { - requestMapping.append(path[0]); - } - } - } else { - DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class); - if (deleteMapping != null) { - String[] value = deleteMapping.value(); - if (value.length > 0) { - requestMapping.append(value[0]); - } else { - String[] path = deleteMapping.path(); - if (path.length > 0) { - requestMapping.append(path[0]); - } - } - } else { - PutMapping putMapping = method.getAnnotation(PutMapping.class); - if (putMapping != null) { - String[] value = putMapping.value(); - if (value.length > 0) { - requestMapping.append(value[0]); - } else { - String[] path = putMapping.path(); - if (path.length > 0) { - requestMapping.append(path[0]); - } - } - } - } - } - } - } - - return requestMapping.toString(); - - } - - - /** - * Description: 复制输入流
- */ - public static InputStream cloneInputStream(ServletInputStream inputStream) { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len; - try { - while ((len = inputStream.read(buffer)) > -1) { - byteArrayOutputStream.write(buffer, 0, len); - } - byteArrayOutputStream.flush(); - } catch (IOException e) { - log.error("复制输入流错误{}", e.getMessage(), e); - } - return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); - } + timeConsuming = System.currentTimeMillis() - start; + log.info("\n\n====> visitor host ip : {}\n====> url : {} uri : {} \n====> method : {}\n====> parameter : {}\n====> exception : {}\n====> 耗时 : {}ms", + ipAddr + " 代理:" + ip, requestMethod + " " + url, requestURI, targetMethod, params, + throwable.getMessage(), timeConsuming); + // 异常继续抛出,交由全局异常捕获再处理 + throw throwable; + } + + return null; + } + + private String getUrl(Class targetClass, Method method) { + // 拼接请求的接口名 start + StringBuilder requestMapping = new StringBuilder(); + + RequestMapping mapping = targetClass.getAnnotation(RequestMapping.class); + if (mapping != null) { + String[] value = mapping.value(); + if (value.length > 0) { + requestMapping.append(value[0]); + } + } + + mapping = method.getAnnotation(RequestMapping.class); + if (mapping != null) { + String[] value = mapping.value(); + if (value.length > 0) { + requestMapping.append(value[0]); + } else { + String[] path = mapping.path(); + if (path.length > 0) { + requestMapping.append(path[0]); + } + } + } else { + PostMapping postMapping = method.getAnnotation(PostMapping.class); + if (postMapping != null) { + String[] value = postMapping.value(); + if (value.length > 0) { + requestMapping.append(value[0]); + } else { + String[] path = postMapping.path(); + if (path.length > 0) { + requestMapping.append(path[0]); + } + } + } else { + GetMapping getMapping = method.getAnnotation(GetMapping.class); + if (getMapping != null) { + String[] value = getMapping.value(); + if (value.length > 0) { + requestMapping.append(value[0]); + } else { + String[] path = getMapping.path(); + if (path.length > 0) { + requestMapping.append(path[0]); + } + } + } else { + DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class); + if (deleteMapping != null) { + String[] value = deleteMapping.value(); + if (value.length > 0) { + requestMapping.append(value[0]); + } else { + String[] path = deleteMapping.path(); + if (path.length > 0) { + requestMapping.append(path[0]); + } + } + } else { + PutMapping putMapping = method.getAnnotation(PutMapping.class); + if (putMapping != null) { + String[] value = putMapping.value(); + if (value.length > 0) { + requestMapping.append(value[0]); + } else { + String[] path = putMapping.path(); + if (path.length > 0) { + requestMapping.append(path[0]); + } + } + } + } + } + } + } + + return requestMapping.toString(); + + } + + /** + * Description: 复制输入流
+ */ + public static InputStream cloneInputStream(ServletInputStream inputStream) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + try { + while ((len = inputStream.read(buffer)) > -1) { + byteArrayOutputStream.write(buffer, 0, len); + } + byteArrayOutputStream.flush(); + } catch (IOException e) { + log.error("复制输入流错误{}", e.getMessage(), e); + } + return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + } } diff --git a/common/src/main/java/hxy/dream/common/filter/RepeatableFilter.java b/common/src/main/java/hxy/dream/common/filter/RepeatableFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..ee20d8ddacc911baff2543b997f95d2e47e1c2f5 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/filter/RepeatableFilter.java @@ -0,0 +1,57 @@ +package hxy.dream.common.filter; + +import hxy.dream.common.filter.wrapper.RepeatedlyReadRequestWrapper; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * 过滤器封装下request再传到后续处理 + */ +public class RepeatableFilter implements Filter { + + /** + * 不需要重复读取的路由 + */ + private final Set IGNORE_PATHS = Collections.unmodifiableSet(new HashSet<>( + Arrays.asList("/api/file/upload"))); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + ServletRequest requestWrapper = null; + + if (request instanceof HttpServletRequest) { + HttpServletRequest servletRequest = (HttpServletRequest) request; + String requestURI = servletRequest.getRequestURI(); + if (IGNORE_PATHS.contains(requestURI)) { + // 文件就不用重复读取了 + chain.doFilter(servletRequest, response); + } else { + requestWrapper = new RepeatedlyReadRequestWrapper(servletRequest); + //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。 + // 在chain.doFiler方法中传递新的request对象 + chain.doFilter(requestWrapper, response); + } + } + + } + + @Override + public void destroy() { + + } +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/filter/RequestTrimFilter.java b/common/src/main/java/hxy/dream/common/filter/RequestTrimFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..656998df277118afa96702dcfd5cb78df3e561fb --- /dev/null +++ b/common/src/main/java/hxy/dream/common/filter/RequestTrimFilter.java @@ -0,0 +1,23 @@ +package hxy.dream.common.filter; + +import hxy.dream.common.filter.wrapper.RequestTrimHttpServletRequestWrapper; +import jakarta.servlet.Filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * 请求参数过滤器 + */ +public class RequestTrimFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + filterChain.doFilter(new RequestTrimHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse); + } + +} diff --git a/common/src/main/java/hxy/dream/common/filter/RequestWrapperFilter.java b/common/src/main/java/hxy/dream/common/filter/RequestWrapperFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..5984485a453486c23fab6dfd54d42b3a70aac79a --- /dev/null +++ b/common/src/main/java/hxy/dream/common/filter/RequestWrapperFilter.java @@ -0,0 +1,25 @@ +package hxy.dream.common.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import java.io.IOException; + +@Component +public class RequestWrapperFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); + // 可以在这里处理请求数据 + byte[] body = requestWrapper.getContentAsByteArray(); + // 处理body,例如记录日志 + //。。。 + filterChain.doFilter(requestWrapper, response); + } +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/filter/ResponseWrapperFilter.java b/common/src/main/java/hxy/dream/common/filter/ResponseWrapperFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..7d36acc146a9879cf058bf388d9ad5e9aa0801b3 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/filter/ResponseWrapperFilter.java @@ -0,0 +1,29 @@ +package hxy.dream.common.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import java.io.IOException; + +@Component +public class ResponseWrapperFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + filterChain.doFilter(request, responseWrapper); + + // 可以在这里处理响应数据 + byte[] body = responseWrapper.getContentAsByteArray(); + // 处理body,例如添加签名 + responseWrapper.setHeader("X-Signature", "some-signature"); + + // 必须调用此方法以将响应数据发送到客户端 + responseWrapper.copyBodyToResponse(); + } +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/filter/TokenFilter.java b/common/src/main/java/hxy/dream/common/filter/TokenFilter.java index fd0f2f06f9e6bf03aacc3059fbcfc8bd2f7d520c..2f6f465867fa9e867fccbb1086fe6141a803a640 100644 --- a/common/src/main/java/hxy/dream/common/filter/TokenFilter.java +++ b/common/src/main/java/hxy/dream/common/filter/TokenFilter.java @@ -4,14 +4,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.annotation.WebFilter; -import javax.servlet.annotation.WebInitParam; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.annotation.WebInitParam; + import java.io.IOException; /** diff --git a/common/src/main/java/hxy/dream/common/filter/wrapper/RepeatedlyReadRequestWrapper.java b/common/src/main/java/hxy/dream/common/filter/wrapper/RepeatedlyReadRequestWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3d2d6f440b1eef068ed72e35b78d4c2a1cad74fa --- /dev/null +++ b/common/src/main/java/hxy/dream/common/filter/wrapper/RepeatedlyReadRequestWrapper.java @@ -0,0 +1,108 @@ +package hxy.dream.common.filter.wrapper; + + +import lombok.extern.slf4j.Slf4j; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + + +/** + * 可重复读取body的请求 + * + * @author eric + */ +@Slf4j +public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper { + + /** + * 将流缓存到了 body变量中了。 + */ + private final String body; + + public RepeatedlyReadRequestWrapper(HttpServletRequest request) { + super(request); + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + InputStream inputStream = null; + try { + inputStream = request.getInputStream(); + if (inputStream != null) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + char[] charBuffer = new char[128]; + int bytesRead = -1; + while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } else { + stringBuilder.append(""); + } + } catch (IOException ex) { + log.error("{}", ex.getMessage(), ex); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + log.error("{}", e.getMessage(), e); + } + } + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + log.error("{}", e.getMessage(), e); + } + } + } + body = stringBuilder.toString(); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 这里在 body变量里重复读取即可 + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); + + ServletInputStream servletInputStream = new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return byteArrayInputStream.read(); + } + }; + return servletInputStream; + + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } + + public String getBody() { + return this.body; + } + + +} diff --git a/common/src/main/java/hxy/dream/common/filter/wrapper/RequestTrimHttpServletRequestWrapper.java b/common/src/main/java/hxy/dream/common/filter/wrapper/RequestTrimHttpServletRequestWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..f1e6d7fadb2283220477cb2a4c66b62be51e4df9 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/filter/wrapper/RequestTrimHttpServletRequestWrapper.java @@ -0,0 +1,130 @@ +package hxy.dream.common.filter.wrapper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import hxy.dream.common.util.SpringUtils; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * URL query param。但是测试 表单提交参数没有获取到 + * 请求参数trim处理,非json参数 + */ +public class RequestTrimHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private static final String CHARSET = "utf-8"; + + private Map params = new HashMap<>(); + + + public RequestTrimHttpServletRequestWrapper(HttpServletRequest request) { + //将request交给父类,调用对应方法的时候,将其输出 + super(request); + //持有request中的参数 + this.params.putAll(request.getParameterMap()); + this.modifyParameterValues(); + } + +// /** 这里对json的处理是多余的,jackson在全局反序列化的时候已经处理了 +// * post类型的请求参数必须通过流才能获取到值 +// */ +// @Override +// public ServletInputStream getInputStream() throws IOException { +// //非json类型,直接返回 +// if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) { +// return super.getInputStream(); +// } +// //为空,直接返回 +// String json = IOUtils.toString(super.getInputStream(), CHARSET); +// if (!StringUtils.hasLength(json)) { +// return super.getInputStream(); +// } +// +// // FIXME 这里对json的 trim是多余的。 +// ObjectMapper objectMapper = SpringUtils.getBean(ObjectMapper.class); +// JsonNode jsonObject = objectMapper.readTree(json); +// byte[] bytes = objectMapper.writeValueAsString(jsonObject).getBytes(CHARSET); +// +// ByteArrayInputStream bis = new ByteArrayInputStream(bytes); +// return new MyServletInputStream(bis); +// } + + /** + * 将parameter的值去除空格后重写回去 + */ + public void modifyParameterValues() { + Set set = params.keySet(); + Iterator it = set.iterator(); + while (it.hasNext()) { + String key = it.next(); + String[] values = params.get(key); + if (values != null && values.length > 0) { + for (int i = 0; i < values.length; i++) { + values[i] = values[i].trim(); + } + } + params.put(key, values); + } + } + + /** + * 从当前类中map获取参数 + */ + @Override + public String getParameter(String name) { + String[] values = params.get(name); + if (values == null || values.length == 0) { + return null; + } + return values[0]; + } + + @Override + public String[] getParameterValues(String name) { + return params.get(name); + } + + @Deprecated + class MyServletInputStream extends ServletInputStream { + private ByteArrayInputStream bis; + + public MyServletInputStream(ByteArrayInputStream bis) { + this.bis = bis; + } + + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener listener) { + + } + + @Override + public int read() { + return bis.read(); + } + } + +} diff --git a/common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java b/common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java index 499b0f601de6d9361da3125bc8fb99ad71dca70b..05f37e02d3824df9c979cc24cdb817938fdf1c94 100644 --- a/common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java +++ b/common/src/main/java/hxy/dream/common/init/ApplicationStartupRunner.java @@ -3,10 +3,12 @@ package hxy.dream.common.init; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.jdbc.ScriptRunner; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; +import javax.sql.DataSource; import java.nio.charset.Charset; import java.sql.Connection; import java.sql.DriverManager; @@ -27,16 +29,29 @@ public class ApplicationStartupRunner implements CommandLineRunner { @Value("${spring.datasource.password}") private String password; + /** + * javax.sql.DataSource 是一个接口,所以不用关心这个具体是谁实现的。 + * 可以是 mysql,p6spy,hikari,mybatis等 + */ + @Autowired + DataSource dataSource; + @Override public void run(String... args) throws Exception { log.info("ApplicationStartupRunner run method Started !!"); - // 执行sql脚本语句 + Connection connection = null; + boolean jdbc = false; try { - Class.forName(driver); - connection = DriverManager.getConnection(url, userName, password); - // 设置不自动提交 - connection.setAutoCommit(false); + if (jdbc) { + Class.forName(driver); + connection = DriverManager.getConnection(url, userName, password); + // 设置不自动提交 + connection.setAutoCommit(false); + } else { + connection = dataSource.getConnection(); + } + ScriptRunner runner = new ScriptRunner(connection); // 设置不自动提交 runner.setAutoCommit(false); @@ -55,15 +70,17 @@ public class ApplicationStartupRunner implements CommandLineRunner { runner.setLogWriter(null); // 如果又多个sql文件,可以写多个runner.runScript(xxx), Resources.setCharset(Charset.forName("UTF8")); - + // 执行sql脚本语句 ScriptRunner scriptRunner = new ScriptRunner(connection); scriptRunner.runScript(Resources.getResourceAsReader("table.sql")); connection.commit(); } catch (Exception e) { - log.error("{}", e); + log.error("执行sql脚本失败。数据库: {} 用户 {}", url, userName, e); } finally { - if (connection != null) { - connection.close();// 连接关闭 + if (jdbc) { + if (connection != null) { + connection.close();// 连接关闭 + } } } } diff --git a/common/src/main/java/hxy/dream/common/init/MysqlDdl.java b/common/src/main/java/hxy/dream/common/init/MysqlDdl.java new file mode 100644 index 0000000000000000000000000000000000000000..0201fd7add9aad1406f32ccc8c344df37e2e61df --- /dev/null +++ b/common/src/main/java/hxy/dream/common/init/MysqlDdl.java @@ -0,0 +1,25 @@ +package hxy.dream.common.init; + +import com.baomidou.mybatisplus.extension.ddl.SimpleDdl; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +/** + * https://baomidou.com/pages/1812u1/ + * 数据库 DDL 表结构执行 SQL 自动维护功能 + */ +@Component +public class MysqlDdl extends SimpleDdl { + + /** + * 执行 SQL 脚本方式 + */ + @Override + public List getSqlFiles() { + return Arrays.asList( + "table.sql" + ); + } +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/manager/RemoteApi.java b/common/src/main/java/hxy/dream/common/manager/RemoteApi.java new file mode 100644 index 0000000000000000000000000000000000000000..ee522af0c4310bfd36542d6581e0fab883e3b05d --- /dev/null +++ b/common/src/main/java/hxy/dream/common/manager/RemoteApi.java @@ -0,0 +1,23 @@ +package hxy.dream.common.manager; + +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; + +/** + * 通过RPC/Http调用远程的服务请求 + * + * @author iris + * @see hxy.dream.common.configuration.RemoteApiConfig + *

+ * 还可以看看 Retrofit + */ +@HttpExchange +public interface RemoteApi { + + /** + * @return 声明式HTTP客户端 + */ + @GetExchange("/get") + String getBody(); + +} diff --git a/common/src/main/java/hxy/dream/common/manager/RemoteApiService.java b/common/src/main/java/hxy/dream/common/manager/RemoteApiService.java deleted file mode 100644 index 23ef20e3b987d29c4e17ca2ae0ec694df1e784ed..0000000000000000000000000000000000000000 --- a/common/src/main/java/hxy/dream/common/manager/RemoteApiService.java +++ /dev/null @@ -1,14 +0,0 @@ -package hxy.dream.common.manager; - -import org.springframework.stereotype.Component; - -/** - * 通过RPC/Http调用远程的服务请求 - * @author iris - */ -@Component -public class RemoteApiService { - - - -} diff --git a/common/src/main/java/hxy/dream/common/redis/DistributedLockHandler.java b/common/src/main/java/hxy/dream/common/redis/DistributedLockHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..44849a18e52023c3beb0d3fe9d0e391a4caa8303 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/redis/DistributedLockHandler.java @@ -0,0 +1,58 @@ +package hxy.dream.common.redis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.concurrent.TimeUnit; + +/** + * 不是很严谨的分布式锁 + */ +@Component +public class DistributedLockHandler { + + private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class); + + + public final static long LOCK_EXPIRE = 30 * 1000L; + public final static long LOCK_TRY_INTERVAL = 30L; + public final static long LOCK_TRY_TIMEOUT = 20 * 1000L; + + @Autowired + private RedisTemplate redisTemplate; + + public boolean getLock(String key, String value) { + try { + if (StringUtils.hasLength(key) && StringUtils.hasLength(value)) { + + long startTime = System.currentTimeMillis(); + do { + if (!redisTemplate.hasKey(key)) { + redisTemplate.opsForValue().set(key, value, LOCK_EXPIRE, TimeUnit.MILLISECONDS); + return true; + } + if (System.currentTimeMillis() - startTime > LOCK_TRY_TIMEOUT) { + // 获取锁超时,直接放弃 + return false; + } + Thread.sleep(LOCK_TRY_INTERVAL); + } while (redisTemplate.hasKey(key)); + } + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + return false; + } + return false; + } + + public void releaseLock(String key) { + if (StringUtils.hasLength(key)) { + redisTemplate.delete(key); + } + } + +} diff --git a/common/src/main/java/hxy/dream/common/redis/RedisConfig.java b/common/src/main/java/hxy/dream/common/redis/RedisConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..ddd3df77e79e7584421560e958c94462e815e47c --- /dev/null +++ b/common/src/main/java/hxy/dream/common/redis/RedisConfig.java @@ -0,0 +1,132 @@ +package hxy.dream.common.redis; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; + +/** + * redis配置 + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + @Autowired + private RedisConnectionFactory redisConnectionFactory; + + /** + * 这个仅仅针对Redis 序列化问题解决! 不能纳入到全局,否则会造成返回前端带上了类名。 + * + * @return + */ + @Bean + @Primary + public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() { + ObjectMapper om = new ObjectMapper(); + // 解决查询缓存转换异常的问题 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 ---- start --- + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + // --end -- + GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); + return genericJackson2JsonRedisSerializer; + } + + @Bean + @Primary + public RedisTemplate redisTemplate() { + + RedisTemplate redisTemplate = new RedisTemplate(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + // 这里 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + GenericJackson2JsonRedisSerializer serializer = genericJackson2JsonRedisSerializer(); + redisTemplate.setValueSerializer(serializer); + // 这里 + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(serializer); + redisTemplate.setEnableTransactionSupport(true); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + + @Bean + @Override + public CacheManager cacheManager() { + + // 设置序列化 + RedisSerializer stringSerializer = new StringRedisSerializer(); + + //解决查询缓存转换异常的问题 + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new JavaTimeModule()); + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(om, Object.class); + + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer)) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) + .disableCachingNullValues() + // 缓存过期时间 Duration.ZERO -> eternal + .entryTtl(Duration.ZERO); +// .entryTtl(Duration.ofMinutes(30)); + + boolean ok = false; + if (ok) { + // TODO 这一部分不会报错,但是redis的缓存失效时间就没有实现了。使用的是默认失效时间,应该就是不失效 + RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(config) + .transactionAware(); + @SuppressWarnings("serial") + Set cacheNames = new HashSet() { + { + add("codeNameCache"); + } + }; + builder.initialCacheNames(cacheNames); + return builder.build(); + } else { + // FIXME 使用这个可以自定义管理缓存时间 + RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); + return new RedisConfigCacheManager(cacheWriter, config); + } + } +} diff --git a/common/src/main/java/hxy/dream/common/redis/RedisConfigCacheManager.java b/common/src/main/java/hxy/dream/common/redis/RedisConfigCacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..10e5778ca13e4fb437e3789a56e044cecc6f7161 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/redis/RedisConfigCacheManager.java @@ -0,0 +1,91 @@ +package hxy.dream.common.redis; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.cache.CacheKeyPrefix; +import org.springframework.data.redis.cache.RedisCache; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; + +import java.time.Duration; + +/** + * redis 配置类 + */ +@Slf4j +public class RedisConfigCacheManager extends RedisCacheManager { + + + public RedisConfigCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { + super(cacheWriter, defaultCacheConfiguration); + } + + + private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = cacheName -> cacheName + ":"; + + @Override + protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) { + +// GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = SpringUtils.getBean(GenericJackson2JsonRedisSerializer.class); + + RedisSerializationContext.SerializationPair DEFAULT_PAIR = RedisSerializationContext.SerializationPair + .fromSerializer(genericJackson2JsonRedisSerializer()); + + if (name != null && name.length() > 0) { + final int lastIndexOf = name.lastIndexOf('#'); + if (lastIndexOf > -1) { + final String ttl = name.substring(lastIndexOf + 1); + if (isNumeric(ttl)) { + final Duration duration = Duration.ofSeconds(Long.parseLong(ttl)); + cacheConfig = cacheConfig.entryTtl(duration); + //修改缓存key和value值的序列化方式 + cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX) + .serializeValuesWith(DEFAULT_PAIR); + final String cacheName = name.substring(0, lastIndexOf); + return super.createRedisCache(cacheName, cacheConfig); + } else { + log.error("过期时间配置错误!!!#号后面不是数字 => {}", ttl); + } + } + } + //修改缓存key和value值的序列化方式 + cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX) + .serializeValuesWith(DEFAULT_PAIR); + return super.createRedisCache(name, cacheConfig); + } + + public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() { + ObjectMapper om = new ObjectMapper(); + // 解决查询缓存转换异常的问题 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 ---- start --- + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + // --end -- + GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); + return genericJackson2JsonRedisSerializer; + } + + public static boolean isNumeric(String str) { + for (int i = str.length(); --i >= 0; ) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java b/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java index f5d6e5d71acbe117f2be1379fcf9cfb755445d5e..709c15ecdd9bf56277d674ca88b184c693cc40c7 100644 --- a/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java @@ -24,6 +24,7 @@ import java.lang.reflect.Field; */ @Slf4j public class BaseEnumDeserializer extends JsonDeserializer { + @Override public BaseEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { @@ -34,7 +35,6 @@ public class BaseEnumDeserializer extends JsonDeserializer { return null; } - JsonStreamContext parsingContext = jp.getParsingContext(); //前端注入的对象(ResDTO) @@ -84,7 +84,11 @@ public class BaseEnumDeserializer extends JsonDeserializer { Class enumClass = field.getType(); BaseEnum anEnum = DefaultInputJsonToEnum.getEnum(inputParameter, enumClass); - log.debug("\n====>测试反序列化枚举[{}]==>[{}.{}]", inputParameter, anEnum.getClass(), anEnum); + if (anEnum != null) { + if (log.isDebugEnabled()) { + log.debug("\n====>测试反序列化枚举[{}]==>[{}.{}]", inputParameter, anEnum.getClass(), anEnum); + } + } return anEnum; } else { throw new BaseException("自定义枚举反序列化错误:json的这个字段[" + parsingContext.getParent() + "]没有值。枚举反序列化失败,注意该属性不可以使用lombok的注解,如@NonNull等"); @@ -94,6 +98,5 @@ public class BaseEnumDeserializer extends JsonDeserializer { throw new RuntimeException(e); } - } } diff --git a/common/src/main/java/hxy/dream/common/serializer/BaseEnumSerializer.java b/common/src/main/java/hxy/dream/common/serializer/BaseEnumSerializer.java index 724b22f1df1106922af23e34a3227cf12c578c76..050e3ced3d870c42dac3213069b916ffc952560b 100644 --- a/common/src/main/java/hxy/dream/common/serializer/BaseEnumSerializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/BaseEnumSerializer.java @@ -14,7 +14,9 @@ public class BaseEnumSerializer extends JsonSerializer { @Override public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException { - log.info("\n====>开始序列化[{}]", value); + if (log.isTraceEnabled()) { + log.trace("\n====>开始序列化[{}]", value); + } if (value != null) { gen.writeStartObject(); Integer code = value.code(); diff --git a/common/src/main/java/hxy/dream/common/serializer/BaseLongSerializer.java b/common/src/main/java/hxy/dream/common/serializer/BaseLongSerializer.java index eed12372e91bd3a209070d9f7dc53314d0f7f971..0040049dd3ed0300374d96dc7cd2341d237728b9 100644 --- a/common/src/main/java/hxy/dream/common/serializer/BaseLongSerializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/BaseLongSerializer.java @@ -21,8 +21,9 @@ public class BaseLongSerializer extends JsonSerializer { */ @Override public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - if (value > 9007199254740990L) { + if (value != null && (value > 9007199254740990L || value.toString().length() > 16)) { gen.writeNumber("\"" + value + "\""); +// gen.writeString(value.toString()); } else { gen.writeNumber(value); } diff --git a/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java b/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java index 9eb170a27bb20ef8896dd690892e9e2c247701d0..dea3ad319194aa9d2e1594bf8ab9e314d8fb5f52 100644 --- a/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java @@ -9,14 +9,22 @@ import java.text.SimpleDateFormat; import java.util.Date; public class DateJsonDeserializer extends JsonDeserializer { + public static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + public static final SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); @Override public Date deserialize(com.fasterxml.jackson.core.JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, com.fasterxml.jackson.core.JsonProcessingException { try { - if (jsonParser != null && StringUtils.hasText(jsonParser.getText())) { - return format.parse(jsonParser.getText()); + String text = jsonParser.getText(); + if (jsonParser != null && StringUtils.hasText(text)) { + if (text.contains("T")) { + // element ui + return format2.parse(text); + } else { + return format.parse(text); + } } else { return null; } diff --git a/common/src/main/java/hxy/dream/common/serializer/DateJsonSerializer.java b/common/src/main/java/hxy/dream/common/serializer/DateJsonSerializer.java index 95547dfcc9386574fe4e4ab8987278bbb0a3083b..496f28d024f94276e412ff114982a2ae8c338ffe 100644 --- a/common/src/main/java/hxy/dream/common/serializer/DateJsonSerializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/DateJsonSerializer.java @@ -5,15 +5,25 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.text.SimpleDateFormat; +/** + * 或者直接yaml文件配置也是可以的 spring.jackson.date-format + */ public class DateJsonSerializer extends JsonSerializer { - public static final SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - @Override - public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeString(format.format(date)); - } - + + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + ZoneId zoneId = ZoneId.systemDefault(); + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + LocalDateTime localDateTime = date.toInstant().atZone(zoneId).toLocalDateTime(); + String yearMonth = localDateTime.format(dtf); + jsonGenerator.writeString(yearMonth); + } + } \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java b/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java index 217961df692de116cbf280f075e1dcf8ff842ba6..2e19af980bf46b3eee3a38c4f0d0f7d48e6891d6 100644 --- a/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java +++ b/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java @@ -18,29 +18,32 @@ public class DefaultInputJsonToEnum { if (debugEnabled) { logger.debug("\n====>反序列化,当前输入的值[{}]对应的枚举类是[{}]", inputParameter, enumClass); } - try { + if (inputParameter != null && inputParameter.length() > 0) { + inputParameter = inputParameter.trim(); + try { // values是默认的方法,必定存在 - Method valuesMethod = enumClass.getDeclaredMethod("values"); + Method valuesMethod = enumClass.getDeclaredMethod("values"); // 通过反射获取该枚举类的所有枚举值 - BaseEnum[] values = (BaseEnum[]) valuesMethod.invoke(null); - BaseEnum baseEnum = null; - - for (BaseEnum value : values) { - //因为inputParameter都是string类型的,code转成字符串才能比较 - if (inputParameter.equals(String.valueOf(value.code())) || inputParameter.equals(value.description())) { - baseEnum = value; - break; + BaseEnum[] values = (BaseEnum[]) valuesMethod.invoke(null); + BaseEnum baseEnum = null; + + for (BaseEnum value : values) { + //因为inputParameter都是string类型的,code转成字符串才能比较 + if (inputParameter.equals(String.valueOf(value.code())) || inputParameter.equals(value.description())|| inputParameter.equals(value.name())) { + baseEnum = value; + break; + } } + //如果都拿不到,那就直接抛出异常了 + if (baseEnum == null) { + throw new RuntimeException(String.format("枚举反序列化错误,输入参数[%s]找不到对应的枚举值", inputParameter)); + } + return baseEnum; + } catch (Exception e) { + throw new RuntimeException(e); } - - //如果都拿不到,那就直接抛出异常了 - if (baseEnum == null) { - throw new RuntimeException(String.format("枚举反序列化错误,输入参数[%s]找不到对应的枚举值", inputParameter)); - } - return baseEnum; - } catch (Exception e) { - throw new RuntimeException(e); } + return null; } private BaseEnum getEnumByName(String inputParameter, Class enumClass) { diff --git a/common/src/main/java/hxy/dream/common/serializer/ReflectionUtils.java b/common/src/main/java/hxy/dream/common/serializer/ReflectionUtils.java index 64fb22ffed2fe52ae1b331cc71ae8fd3e481dbb4..bc850933336bac6b6935cf2fd824e94ffd826587 100644 --- a/common/src/main/java/hxy/dream/common/serializer/ReflectionUtils.java +++ b/common/src/main/java/hxy/dream/common/serializer/ReflectionUtils.java @@ -9,11 +9,16 @@ import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.aop.framework.Advised; import org.springframework.core.BridgeMethodResolver; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.StandardReflectionParameterNameDiscoverer; import java.lang.annotation.Annotation; -import java.lang.reflect.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -25,7 +30,7 @@ import java.util.List; */ public class ReflectionUtils { - public static ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + public static ParameterNameDiscoverer parameterNameDiscoverer = new StandardReflectionParameterNameDiscoverer(); // javassist.ClassPool dubbo中包含此包 private static ClassPool classPool = ClassPool.getDefault(); diff --git a/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java b/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java index 1bfbac66aa19134ffa1c2d8601e5cb392f3a5a59..137d9a85be4697ea8ca9214a0de3393927697b1b 100644 --- a/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java +++ b/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory; public class SimpleDeserializersWrapper extends SimpleDeserializers { - static final Logger logger = LoggerFactory.getLogger(SimpleDeserializersWrapper.class); + private static final Logger logger = LoggerFactory.getLogger(SimpleDeserializersWrapper.class); @Override public JsonDeserializer findEnumDeserializer(Class type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { diff --git a/common/src/main/java/hxy/dream/common/serializer/SqlDateJsonSerializer.java b/common/src/main/java/hxy/dream/common/serializer/SqlDateJsonSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..97de29e8a54148c27fb5cbdeb1e60f1c8d7b365e --- /dev/null +++ b/common/src/main/java/hxy/dream/common/serializer/SqlDateJsonSerializer.java @@ -0,0 +1,22 @@ +package hxy.dream.common.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.sql.Date; + +/** + * 建议直接配置 objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false); + * true 就是时间戳,false就是 正常日期 + */ +@Deprecated +public class SqlDateJsonSerializer extends JsonSerializer { + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString("时间格式化" + date.toString()); + } + +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/serializer/StringTrimDeserializer.java b/common/src/main/java/hxy/dream/common/serializer/StringTrimDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..4595257d365df216074f513a477855e6540722e5 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/serializer/StringTrimDeserializer.java @@ -0,0 +1,36 @@ +package hxy.dream.common.serializer; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; + +import java.io.IOException; + +/** + * jackson全局去掉 前后空格,特别是接受前端参数 + */ +public class StringTrimDeserializer extends StdScalarDeserializer { + + /** + * 子类可以重写父类中的protected方法,并把它的可见性改为public,但是子类不能削弱父类中定义的方法的可访问性。 + * 例如:如果一个方法在父类中被定义为public,在子类中也必须为public。子类方法的可访问性必须大于等于父类方法的可访问性。 + * + * @param vc + */ + public StringTrimDeserializer(Class vc) { + super(vc); + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + + String valueAsString = p.getValueAsString(); + + if (valueAsString != null) { + valueAsString = valueAsString.trim(); + } + + return valueAsString; + } +} diff --git a/common/src/main/java/hxy/dream/common/study/EricFactoryBean.java b/common/src/main/java/hxy/dream/common/study/EricFactoryBean.java index cc7259b5557176fdf75f8d2f99d3cab3ce7e1331..a91cb79c91e1a6593757fa6d5f2ddfdee0b81802 100644 --- a/common/src/main/java/hxy/dream/common/study/EricFactoryBean.java +++ b/common/src/main/java/hxy/dream/common/study/EricFactoryBean.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Component; */ @Component public class EricFactoryBean implements FactoryBean { + @Override public EricService getObject() throws Exception { return new EricService(); diff --git a/common/src/main/java/hxy/dream/common/util/EnvironmentUtils.java b/common/src/main/java/hxy/dream/common/util/EnvironmentUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..fb5388956d22e90a1705685a87544936f8513efe --- /dev/null +++ b/common/src/main/java/hxy/dream/common/util/EnvironmentUtils.java @@ -0,0 +1,59 @@ +package hxy.dream.common.util; + +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +/** + * @author eric + */ +@Component +public class EnvironmentUtils implements EnvironmentAware { + + + public static final String PROD = "prod"; + public static final String DEV = "dev"; + public static final String BETA = "beta"; + public static final String UAT = "uat"; + + + private static Environment environment; + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + /** + * 或者直接@Value("${spring.profiles.active}")即可 + * + * @return 当前选择的环境变量 + */ + public static String getActiveProfile() { + if (environment != null) { + if (environment.getActiveProfiles().length > 0) { + return environment.getActiveProfiles()[0]; + } else { + return environment.getDefaultProfiles()[0]; + } + } else { + return null; + } + } + + public static boolean isProdOrBeta() { + String activeProfile = getActiveProfile(); + if (PROD.equals(activeProfile) || BETA.equals(activeProfile)) { + return true; + } + return false; + } + + public static boolean isProd() { + String activeProfile = getActiveProfile(); + if (PROD.equals(activeProfile)) { + return true; + } + return false; + } +} diff --git a/common/src/main/java/hxy/dream/common/util/IPAddress.java b/common/src/main/java/hxy/dream/common/util/IPAddress.java index 51671f38e25d1876a23b01d3bcde860c686f69e3..3adad754d01f0cacd2384c8c8559082825723c74 100644 --- a/common/src/main/java/hxy/dream/common/util/IPAddress.java +++ b/common/src/main/java/hxy/dream/common/util/IPAddress.java @@ -3,7 +3,7 @@ package hxy.dream.common.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; @@ -11,6 +11,7 @@ import java.net.UnknownHostException; * 经过实际验证确实可以取到nginx代理后的真实客户端地址 */ public class IPAddress { + private static final Logger log = LoggerFactory.getLogger(IPAddress.class); /** diff --git a/common/src/main/java/hxy/dream/common/util/SpringUtils.java b/common/src/main/java/hxy/dream/common/util/SpringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..04977aff9ed2f336653270551cd341629fa30130 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/util/SpringUtils.java @@ -0,0 +1,129 @@ +package hxy.dream.common.util; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { + /** + * Spring应用上下文环境 + */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws BeansException + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws BeansException + */ + public static T getBean(Class clz) throws BeansException { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws NoSuchBeanDefinitionException + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws NoSuchBeanDefinitionException + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws NoSuchBeanDefinitionException + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() { + final String[] activeProfiles = getActiveProfiles(); + return activeProfiles != null && activeProfiles.length > 0 ? activeProfiles[0] : null; + } +} diff --git a/common/src/main/java/hxy/dream/common/util/ThreadPoolExecutorTool.java b/common/src/main/java/hxy/dream/common/util/ThreadPoolExecutorTool.java new file mode 100644 index 0000000000000000000000000000000000000000..4998c15524e1135e9f5872024f4164256759661f --- /dev/null +++ b/common/src/main/java/hxy/dream/common/util/ThreadPoolExecutorTool.java @@ -0,0 +1,22 @@ +package hxy.dream.common.util; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @author eric + * @description + * @date 2021/12/23 + */ +public class ThreadPoolExecutorTool { + /** + * 阿里巴巴建议自己手动新建线程池,一定要指定最大线程数。 + * 这里的线程池maximumPoolSize依据CPU核心数来确定核心线程数有多少个。一般是核心数+1 + */ + public static ExecutorService POOL = new ThreadPoolExecutor(8, 16, + 15L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2048), Executors. + defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); +} diff --git a/common/src/main/java/hxy/dream/common/util/ValueUtil.java b/common/src/main/java/hxy/dream/common/util/ValueUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..5e40c3758cffd86eabe07a2c93877a804c01cd67 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/util/ValueUtil.java @@ -0,0 +1,24 @@ +package hxy.dream.common.util; + + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class ValueUtil { + + private static String value; + + /** + * @return 为Spring容器外的方法提供SpringBoot的配置文件信息 + */ + public static String getValue() { + return value; + } + + @Value("${server.port}") + public void setValue(String value) { + ValueUtil.value = value; + } + +} diff --git a/dao/build.gradle b/dao/build.gradle index ad91eb5e3390c167715abae18f2249a6eaaa87cf..573f134a79e645ce8fa05e22a193e96177f16d31 100644 --- a/dao/build.gradle +++ b/dao/build.gradle @@ -12,7 +12,6 @@ dependencies { api project(':entity') /* 子模块之间的依赖 */ // mysql的驱动版本应该是受啥控制了,可以忽略版本号,后期可以调查下,通过依赖分析目前没有找到原因 - runtimeOnly 'mysql:mysql-connector-java' - testImplementation group: 'junit', name: 'junit', version: '4.12' + runtimeOnly 'mysql:mysql-connector-java:8.0.33' } diff --git a/dao/src/main/java/hxy/dream/dao/UserDao.java b/dao/src/main/java/hxy/dream/dao/UserDao.java index 97864562cb75f85c78cdd6de8fc3e4aa0062dc44..c76ac37933217b67a3628b458ff0093a1946cd0a 100644 --- a/dao/src/main/java/hxy/dream/dao/UserDao.java +++ b/dao/src/main/java/hxy/dream/dao/UserDao.java @@ -3,22 +3,24 @@ package hxy.dream.dao; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import hxy.dream.dao.mapper.UserMapper; import hxy.dream.dao.model.UserModel; +import jakarta.annotation.Resource; import org.springframework.stereotype.Component; -import javax.annotation.Resource; import java.util.List; /** * @author iris + * 没有必要添加DAO层 */ +@Deprecated @Component public class UserDao { @Resource - UserMapper userMapper; + private UserMapper userMapper; public List selectByName(String name) { - QueryWrapper objectQueryWrapper = new QueryWrapper(); + QueryWrapper objectQueryWrapper = new QueryWrapper<>(); objectQueryWrapper.eq("name", name); return userMapper.selectList(objectQueryWrapper); } diff --git a/dao/src/main/java/hxy/dream/dao/configuration/mybatis/BasicInfoDTOTypeHandler.java b/dao/src/main/java/hxy/dream/dao/configuration/mybatis/BasicInfoDTOTypeHandler.java index 0421f96df8d4b180c4dfd7263f01dd23f17cf5a4..5cae7e6b759d990ec915a14e6de4e06dd2cef971 100644 --- a/dao/src/main/java/hxy/dream/dao/configuration/mybatis/BasicInfoDTOTypeHandler.java +++ b/dao/src/main/java/hxy/dream/dao/configuration/mybatis/BasicInfoDTOTypeHandler.java @@ -24,7 +24,7 @@ public class BasicInfoDTOTypeHandler extends JacksonTypeHandler { } @Override - protected Object parse(String json) { + public Object parse(String json) { try { return getObjectMapper().readValue(json, new TypeReference>() { }); diff --git a/dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java b/dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java index 05f6e1086ca615f7dbea34ffeb18eb5e9d29147e..6693435ce84c8bdbb67f71d275ffbf0b94eb2aa1 100644 --- a/dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java +++ b/dao/src/main/java/hxy/dream/dao/configuration/mybatis/CustomTypeHandler.java @@ -1,17 +1,9 @@ package hxy.dream.dao.configuration.mybatis; -/** - * @ClassName AESTypeHandler - * @date 2020/4/9 14:27. - * @Description - */ - import com.baomidou.mybatisplus.core.toolkit.StringUtils; - -import hxy.dream.dao.util.KeyCenterUtils; +import hxy.dream.dao.util.AesCbcEncryption; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; -import org.springframework.stereotype.Service; import java.sql.CallableStatement; import java.sql.PreparedStatement; @@ -19,39 +11,56 @@ import java.sql.ResultSet; import java.sql.SQLException; /** - * 数据库字段加解密处理 + * !!! mp的条件里是不会用这个自定义类型处理器的,原来是啥就是啥!!! + *

+ * 数据库字段加解密处理。 + * 加密后就有一个问题,没办法对数据进行检索了。比如like查询。但是mybatis-plus提供了自己企业版的加密方式可以做到。https://baomidou.com/pages/1864e1/#%E5%AD%97%E6%AE%B5%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86 * * @param */ -@Service public class CustomTypeHandler extends BaseTypeHandler { - - public CustomTypeHandler() { - } + private final String AES_PREFIX = "{aes}"; @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { - ps.setString(i, KeyCenterUtils.encrypt((String) parameter)); + String encrypt = (String) parameter; + if (parameter != null) { + encrypt = AesCbcEncryption.encrypt((String) parameter); + encrypt = AES_PREFIX + encrypt; + } + ps.setString(i, encrypt); } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { String columnValue = rs.getString(columnName); - //有一些可能是空字符 - return StringUtils.isBlank(columnValue) ? (T) columnValue : (T) KeyCenterUtils.decrypt(columnValue); + if (columnValue != null && columnValue.startsWith(AES_PREFIX)) { + columnValue = columnValue.replace(AES_PREFIX, ""); + //有一些可能是空字符 + return StringUtils.isBlank(columnValue) ? (T) columnValue : (T) AesCbcEncryption.decrypt(columnValue); + } + return (T) columnValue; } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String columnValue = rs.getString(columnIndex); - return StringUtils.isBlank(columnValue) ? (T) columnValue : (T) KeyCenterUtils.decrypt(columnValue); + if (columnValue != null && columnValue.startsWith(AES_PREFIX)) { + columnValue = columnValue.replace(AES_PREFIX, ""); + return StringUtils.isBlank(columnValue) ? (T) columnValue : (T) AesCbcEncryption.decrypt(columnValue); + } + return (T) columnValue; } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String columnValue = cs.getString(columnIndex); - return StringUtils.isBlank(columnValue) ? (T) columnValue : (T) KeyCenterUtils.decrypt(columnValue); + if (columnValue != null && columnValue.startsWith(AES_PREFIX)) { + columnValue = columnValue.replace(AES_PREFIX, ""); + return StringUtils.isBlank(columnValue) ? (T) columnValue : (T) AesCbcEncryption.decrypt(columnValue); + } + return (T) columnValue; } } diff --git a/dao/src/main/java/hxy/dream/dao/mapper/UserMapper.java b/dao/src/main/java/hxy/dream/dao/mapper/UserMapper.java index 61985dcc3384ce499bfe004cf5179576fbc2a21e..52b3b35c4c91730cd9dcb80f1c927a19907dde78 100644 --- a/dao/src/main/java/hxy/dream/dao/mapper/UserMapper.java +++ b/dao/src/main/java/hxy/dream/dao/mapper/UserMapper.java @@ -2,9 +2,39 @@ package hxy.dream.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import hxy.dream.dao.model.UserModel; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; + +import java.util.List; /** * @author iris */ public interface UserMapper extends BaseMapper { + + /** + * 忽略逻辑删除,只能写SQL语句 + * + * @param userModel + * @return + */ + int updateWithoutLogicDelete(UserModel userModel); + + UserModel selectUserModel(UserModel userModel); + + + /** + * 没办法去做模糊查询了 + * + * @param userModel + * @return + */ + @Results(id = "resultMap", value = { + @Result(column = "address", property = "address", typeHandler = hxy.dream.dao.configuration.mybatis.CustomTypeHandler.class), + @Result(column = "phone_number", property = "phoneNumber", typeHandler = hxy.dream.dao.configuration.mybatis.CustomTypeHandler.class) + }) + @Select("select * from user_model where phone_number = #{phoneNumber} or phone_number= #{phoneNumber, typeHandler=hxy.dream.dao.configuration.mybatis.CustomTypeHandler}") + List listByNumber(UserModel userModel); + } diff --git a/dao/src/main/java/hxy/dream/dao/model/UserModel.java b/dao/src/main/java/hxy/dream/dao/model/UserModel.java index 64d704f031558ea0ef16c9e7703a5f6c8f448c56..a7390c40ea03754d712799ce8b81ac92d5d1807a 100644 --- a/dao/src/main/java/hxy/dream/dao/model/UserModel.java +++ b/dao/src/main/java/hxy/dream/dao/model/UserModel.java @@ -26,15 +26,24 @@ public class UserModel extends BaseModel { * 参考yaml文件配置id-type: auto */ @TableId(type = IdType.AUTO) - Integer id; - String name; - Integer age; - GenderEnum gender; - String password; + private Integer id; + private String name; + private Integer age; + private GenderEnum gender; + private String password; + /** + * !! mp的条件里是不会用这个自定义类型处理器的,原来是啥就是啥!!! + */ //有了这个数据库BaseMapper插入的时候才能加密 @TableField(typeHandler = CustomTypeHandler.class) - String address; + private String address; + + /** + * !!! mp的条件里是不会用这个自定义类型处理器的,原来是啥就是啥!!! + */ + @TableField(typeHandler = CustomTypeHandler.class) + private String phoneNumber; /** * 注意!! 必须开启映射注解 @@ -46,7 +55,7 @@ public class UserModel extends BaseModel { * 注意!!选择对应的 JSON 处理器也必须存在对应依赖包 */ @TableField(typeHandler = BasicInfoDTOTypeHandler.class) - List basicInfos; + private List basicInfos; @TableField(typeHandler = FastjsonTypeHandler.class) private OtherInfo otherInfo; diff --git a/dao/src/main/java/hxy/dream/dao/util/AesCbcEncryption.java b/dao/src/main/java/hxy/dream/dao/util/AesCbcEncryption.java new file mode 100644 index 0000000000000000000000000000000000000000..a240060537f0afb6a38bd364f28f3c31926f1797 --- /dev/null +++ b/dao/src/main/java/hxy/dream/dao/util/AesCbcEncryption.java @@ -0,0 +1,133 @@ +package hxy.dream.dao.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + + +public class AesCbcEncryption { + + private static final Logger log = LoggerFactory.getLogger(AesCbcEncryption.class); + + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + private static final int KEY_SIZE = 128; + // 使用与Golang相同的key和iv + static String key = "rblctyuiopasdfgr"; + static String iv = "rblctyuiopasdfgh"; + +// public static void main(String[] args) { +// try { +// // 原始明文 +// String plaintext = "oooooo灰灰"; +// +// +// System.out.println("明文:" + plaintext); +// System.out.println("key:" + key); +// System.out.println("iv:" + iv); +// +// // 加密 +// byte[] encrypted = cbcEncrypt(plaintext.getBytes(StandardCharsets.UTF_8), key.getBytes(StandardCharsets.UTF_8), iv.getBytes(StandardCharsets.UTF_8)); +// String encryptedString = bytesToHex(encrypted); +// System.out.println("加密后的密文:" + encryptedString); +// +// // Base64编码 +// String encryptedBase64 = base64Encode(encrypted); +// System.out.println("Base64编码后的密文:" + encryptedBase64); +// +// // 解密 +// byte[] encrypted1 = hexToBytes(encryptedString); +// byte[] decrypted = cbcDecrypt(encrypted1, key.getBytes(StandardCharsets.UTF_8), iv.getBytes(StandardCharsets.UTF_8)); +// String decryptedText = new String(decrypted, StandardCharsets.UTF_8); +// System.out.println("解密后的明文:" + decryptedText); +// +// // 封装后的测试 +// String encrypt = encrypt(plaintext); +// System.out.println("加密后的base64文本:" + encrypt); +// String decrypt = decrypt(encrypt); +// System.out.println("解密后的文本:" + decrypt); +// +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + + public static String encrypt(String rawString) { + byte[] encrypted = null; + try { + encrypted = cbcEncrypt(rawString.getBytes(StandardCharsets.UTF_8), key.getBytes(StandardCharsets.UTF_8), iv.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("{}", e.getMessage(), e); + } + String encryptedString = bytesToHex(encrypted); + System.out.println("加密后的密文:" + encryptedString); + + // Base64编码 + String encryptedBase64 = base64Encode(encrypted); + return encryptedBase64; + } + + public static String decrypt(String encryptedBase64) { + byte[] encrypted1 = base64Decode(encryptedBase64); + byte[] decrypted = null; + try { + decrypted = cbcDecrypt(encrypted1, key.getBytes(StandardCharsets.UTF_8), iv.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("解密错误 {}", e.getMessage(), e); + } + if (decrypted != null) { + // 不能为空 + String decryptedText = new String(decrypted, StandardCharsets.UTF_8); + return decryptedText; + } else { + log.error("解密错误 {}", encryptedBase64); + } + return encryptedBase64; + } + + private static byte[] cbcEncrypt(byte[] text, byte[] key, byte[] iv) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + SecretKeySpec secretKey = new SecretKeySpec(key, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + return cipher.doFinal(text); + } + + private static byte[] cbcDecrypt(byte[] encrypted, byte[] key, byte[] iv) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + SecretKeySpec secretKey = new SecretKeySpec(key, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); + return cipher.doFinal(encrypted); + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X", b & 0xFF)); + } + return sb.toString(); + } + + public static String base64Encode(byte[] data) { + return Base64.getEncoder().encodeToString(data); + } + + public static byte[] base64Decode(String rawString) { + return Base64.getDecoder().decode(rawString.getBytes()); + } + + private static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] bytes = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return bytes; + } +} diff --git a/dao/src/main/java/hxy/dream/dao/util/KeyCenterUtils.java b/dao/src/main/java/hxy/dream/dao/util/KeyCenterUtils.java index 17fe126a49e9f0426e4c610d8ef9f48387766709..b2c3906b8062f2a4301a97bb33aead525e907a96 100644 --- a/dao/src/main/java/hxy/dream/dao/util/KeyCenterUtils.java +++ b/dao/src/main/java/hxy/dream/dao/util/KeyCenterUtils.java @@ -9,6 +9,7 @@ public class KeyCenterUtils { */ public static String encrypt(String src) { try { + // 最好替换成 RSA 或者 AES 。 Base64只能算是编码,不算加密。 String result = Base64.getEncoder().encodeToString(src.getBytes("UTF-8")); return result; } catch (Exception e) { diff --git a/dao/src/main/java/hxy/dream/dao/util/RSAEncryption.java b/dao/src/main/java/hxy/dream/dao/util/RSAEncryption.java index 9160560b14542ab0febe7e24861b2b02b5b73c4c..7600b9732ab73c105a142df5e5c173df115545cb 100644 --- a/dao/src/main/java/hxy/dream/dao/util/RSAEncryption.java +++ b/dao/src/main/java/hxy/dream/dao/util/RSAEncryption.java @@ -188,16 +188,16 @@ public class RSAEncryption { * * @param args */ - public static void main(String[] args) { - - String input = "Hello World!"; -// second(input); - - String s = infoEncrypt(input); - System.out.println("加密后密码" + s); - String s1 = infoDecrypt(s); - System.out.println("解密后的密码" + s1); - } +// public static void main(String[] args) { +// +// String input = "Hello World!"; +//// second(input); +// +// String s = infoEncrypt(input); +// System.out.println("加密后密码" + s); +// String s1 = infoDecrypt(s); +// System.out.println("解密后的密码" + s1); +// } static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLUKF8+tqeLt+MESnrFxrUDkt56MocLoh/On2I\n" + "+jm/0W+BcnAlPC3Q6xH5lQjNklqcN+XldKmGqkDQS/qT9/ZcNWNv1oI7skAP7TqPupRdMIzvd0rg\n" + diff --git a/entity/build.gradle b/entity/build.gradle index 60cc64a76971f6c0b3e0c62f67d9bcaa4ded3d48..10084bd00ae67e485a24a08d2b360c4ac3eec09a 100644 --- a/entity/build.gradle +++ b/entity/build.gradle @@ -8,7 +8,9 @@ bootJar.enabled = false dependencies { // implementation的依赖是不可以传递的而,entity需要被app依赖,所以需要加上api - api 'com.baomidou:mybatis-plus-boot-starter:3.5.1' + api 'com.baomidou:mybatis-plus-spring-boot3-starter:3.5.9' + api("com.baomidou:mybatis-plus-jsqlparser:3.5.9") api 'org.springframework.boot:spring-boot-starter-validation' + api 'jakarta.annotation:jakarta.annotation-api:3.0.0' } \ No newline at end of file diff --git a/entity/src/main/java/hxy/dream/entity/dto/OtherInfo.java b/entity/src/main/java/hxy/dream/entity/dto/OtherInfo.java index 9965ec1af3bb6cb6487a3fd01d20294b4b7e109a..df521dbd5012ba505283463576be2eff887d8d0a 100644 --- a/entity/src/main/java/hxy/dream/entity/dto/OtherInfo.java +++ b/entity/src/main/java/hxy/dream/entity/dto/OtherInfo.java @@ -3,16 +3,26 @@ package hxy.dream.entity.dto; import lombok.Data; /** - * 其他信息 + * 对于 Java 中的记录(Record),你无法直接使用构造函数或公共方法来为其字段赋值,因为记录是不可变的,其字段是 final 的,并且由编译器自动生成 getter 方法。要创建并设置记录的实例,可以使用记录的构造函数。 + * 貌似没有setter方法 + * + * @param sex + * @param city */ -@Data -public class OtherInfo { - /** - * 性别 - */ - private String sex; - /** - * 居住城市 - */ - private String city; -} \ No newline at end of file +public record OtherInfo(String sex, String city) { + +} +///** +// * 其他信息 +// */ +//@Data +//public class OtherInfo { +// /** +// * 性别 +// */ +// private String sex; +// /** +// * 居住城市 +// */ +// private String city; +//} \ No newline at end of file diff --git a/entity/src/main/java/hxy/dream/entity/dto/UserDTO.java b/entity/src/main/java/hxy/dream/entity/dto/UserDTO.java index 00b72fd8f79fa2a1f49d348a8915546900382071..45d6183560503e7c3360e091c6deaf3fb02ca54c 100644 --- a/entity/src/main/java/hxy/dream/entity/dto/UserDTO.java +++ b/entity/src/main/java/hxy/dream/entity/dto/UserDTO.java @@ -1,15 +1,22 @@ package hxy.dream.entity.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; +import java.sql.Date; + @Data public class UserDTO implements DTO { - String id; + Integer id; // GenderEnum gender; + public String name; + +// @JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8") // 这里就是单个注解的配置 + public Date registerDate; + @Override public String dto() { - - return id; + return ""; } } diff --git a/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java b/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java index cb60938bf0b3b67fb63a35e9ab403fb739b57047..7ca5ab99c4eca9ad9903f5b3e0f1c1bbaa318978 100644 --- a/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java +++ b/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java @@ -5,6 +5,7 @@ package hxy.dream.entity.enums; */ // 如果发现注入的bean无法解决json序列化问题,那么可以加上这个注解 //@JsonFormat(shape = JsonFormat.Shape.OBJECT) +// 有必要的话,这个也是可以加一个泛型的,表示支持 Integer或者其他的。 public interface BaseEnum { /** * Code integer. @@ -12,6 +13,7 @@ public interface BaseEnum { * @return the integer */ Integer code(); +// T code(); /** * Description string. @@ -20,4 +22,6 @@ public interface BaseEnum { */ String description(); + String name(); + } \ No newline at end of file diff --git a/entity/src/main/java/hxy/dream/entity/enums/GenderEnum.java b/entity/src/main/java/hxy/dream/entity/enums/GenderEnum.java index d0df6c3956504487e20e7d25748c271fb139e5ac..be6d78b6f6b9a0823d98ca08ba382643e5a8cef5 100644 --- a/entity/src/main/java/hxy/dream/entity/enums/GenderEnum.java +++ b/entity/src/main/java/hxy/dream/entity/enums/GenderEnum.java @@ -2,14 +2,14 @@ package hxy.dream.entity.enums; import com.baomidou.mybatisplus.annotation.EnumValue; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; public enum GenderEnum implements BaseEnum { - BOY(100, "男"), GIRL(200, "女"),UNKNOWN(0, "未知"); + BOY(100, "男"), GIRL(200, "女"), UNKNOWN(0, "未知"); @EnumValue//标记数据库存的值是code private final Integer code; @@ -21,14 +21,19 @@ public enum GenderEnum implements BaseEnum { this.description = description; } - - public static GenderEnum getEnumByCode(Integer code){ - for (GenderEnum genderEnum:values()){ - if (genderEnum.code.equals(code)){ - return genderEnum; - } + public static GenderEnum getEnumByCode(Integer code) { +// for (GenderEnum genderEnum:values()){ +// if (genderEnum.code.equals(code)){ +// return genderEnum; +// } +// } + Optional first = Arrays.stream(GenderEnum.values()).filter(item -> item.code().equals(code)).findFirst(); + if (first.isEmpty()) { + return null; + } else { + GenderEnum status = first.get(); + return status; } - return null; } @Override diff --git a/entity/src/main/java/hxy/dream/entity/exception/BaseException.java b/entity/src/main/java/hxy/dream/entity/exception/BaseException.java index 8cb0006a5859269fe75a92ce5e1761b754ba5885..b0fde3cf6480c8caa2e8d18105cf2b6e55bf9cec 100644 --- a/entity/src/main/java/hxy/dream/entity/exception/BaseException.java +++ b/entity/src/main/java/hxy/dream/entity/exception/BaseException.java @@ -10,6 +10,8 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = false) public class BaseException extends RuntimeException { + + private static final long serialVersionUID = 8548698263350114411L; private String message; private int code = 0; diff --git a/entity/src/main/java/hxy/dream/entity/exception/WebException.java b/entity/src/main/java/hxy/dream/entity/exception/WebException.java index a591014e9e114bd40920930ade8cc015e19abb6b..549cb6a2ee7aedd12e83a99197347e6fb8227d3a 100644 --- a/entity/src/main/java/hxy/dream/entity/exception/WebException.java +++ b/entity/src/main/java/hxy/dream/entity/exception/WebException.java @@ -10,7 +10,8 @@ import org.springframework.http.HttpStatus; */ public class WebException extends BaseException { - private HttpStatus httpStatus; + private static final long serialVersionUID = -2175679614507721010L; + private HttpStatus httpStatus; private String message; public WebException(String message, HttpStatus httpStatus) { diff --git a/entity/src/main/java/hxy/dream/entity/vo/BaseResponseVO.java b/entity/src/main/java/hxy/dream/entity/vo/BaseResponseVO.java index bc7c6f699a163c704e4c18121a583f3865269878..457b14ba48a857edc40d2da407705e690566b337 100644 --- a/entity/src/main/java/hxy/dream/entity/vo/BaseResponseVO.java +++ b/entity/src/main/java/hxy/dream/entity/vo/BaseResponseVO.java @@ -1,15 +1,19 @@ package hxy.dream.entity.vo; import lombok.Data; +import org.slf4j.MDC; +import org.springframework.http.HttpStatus; import java.io.Serializable; @Data public class BaseResponseVO implements Serializable { - Long timestamp = System.currentTimeMillis(); - String message; - T data; - Integer code; + private static final long serialVersionUID = -7956701197932947718L; + private Long timestamp = System.currentTimeMillis(); + private String message; + private T data; + private Integer code; + private String xid; public BaseResponseVO(Integer code, String message, T data) { this.code = code; @@ -22,22 +26,37 @@ public class BaseResponseVO implements Serializable { } public static BaseResponseVO success(T data) { - return new BaseResponseVO(200, "success", data); + return new BaseResponseVO(200, "success", data); + } + + public static BaseResponseVO success(HttpStatus httpStatus, T data) { + return new BaseResponseVO(httpStatus.value(), "success", data); } public static BaseResponseVO success(String message, T data) { - return new BaseResponseVO(200, message, data); + return new BaseResponseVO(200, message, data); } public static BaseResponseVO error(T data) { - return new BaseResponseVO(500, "error", data); + return new BaseResponseVO(500, "error", data); } public static BaseResponseVO error(String message, T data) { - return new BaseResponseVO(500, message, data); + return new BaseResponseVO(500, message, data); } public static BaseResponseVO badrequest(String message, T data) { - return new BaseResponseVO(500, message, data); + return new BaseResponseVO(500, message, data); + } + + /** + * 这个计划是由 spring-cloud-starter-sleuth + * 实现的。但是貌似spring-cloud-starter-sleuth没咋更新了,样例都是n年前的,还指向了zipkin。 + *

+ * 所以计划可以尝试用tlog实现下。https://tlog.yomahub.com/ https://github.com/dromara/TLog + */ + public String getXid() { + String traceId = MDC.get("traceId"); + return traceId; } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a9715077f4fe68764b2e50867736b0c7f015a2..6b76b32e51fd194bff71bd26bc98f1d97ed81967 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +#distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.3-bin.zip +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.10-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882ed57263a19bf3a504977da292d009345f..0adc8e1a53214b5f72ec3dfc95f7eacd239b7f27 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,99 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e687021ef32db511e8a206129b88ec..93e3f59f135dd2dd498de4beb5c64338cc33beeb 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle b/settings.gradle index 049f6450ff63f3b2c5a7804c4b731f33c52e4531..1a4934bbff5206b76949d05eeb713e48e2412f55 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ pluginManagement { repositories { maven { name "Alibaba spring-plugin"; url "https://maven.aliyun.com/repository/spring-plugin" } maven { name "Alibaba gradle-plugin"; url "https://maven.aliyun.com/repository/gradle-plugin" } + mavenCentral() gradlePluginPortal() } } @@ -9,5 +10,4 @@ rootProject.name = 'multi-gradle' include 'app' include 'entity' include 'dao' -include 'common' - +include 'common' \ No newline at end of file