parent
							
								
									e71702e335
								
							
						
					
					
						commit
						dacd014980
					
				
				 418 changed files with 29589 additions and 0 deletions
			
			
		| @ -0,0 +1,17 @@ | ||||
| target/ | ||||
| !**/src/main/**/target/ | ||||
| !**/src/test/**/target/ | ||||
| 
 | ||||
| ### IntelliJ IDEA ### | ||||
| .idea | ||||
| *.iws | ||||
| *.iml | ||||
| *.ipr | ||||
| **/src/main/resources/rebel.xml | ||||
| **/src/main/resources/rebel-remote.xml | ||||
| **/**/src/main/resources/rebel.xml | ||||
| **/**/src/main/resources/rebel-remote.xml | ||||
| 
 | ||||
| # 业务相关 | ||||
| logs | ||||
| docker/*.jar | ||||
| @ -0,0 +1,22 @@ | ||||
| # 说明 | ||||
| 
 | ||||
| ### 组件&服务 部署 | ||||
| 
 | ||||
| mysql、redis、minio | ||||
| 
 | ||||
| 见[部署文档](doc/部署) | ||||
| 
 | ||||
| ### 本地运行 | ||||
| 
 | ||||
| 1. 修改`app/src/main/resources/application-dev.yml`配置 | ||||
| 2. 启动`App` | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| ### 数据清理 | ||||
| 
 | ||||
| ```sql | ||||
| SELECT CONCAT('DELETE FROM ', TABLE_NAME, ' where is_deleted=1;') | ||||
| FROM information_schema.TABLES | ||||
| WHERE table_schema = 'smallboot'; | ||||
| ``` | ||||
| @ -0,0 +1,56 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| 
 | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <parent> | ||||
|         <artifactId>smallboot-api</artifactId> | ||||
|         <groupId>com.zhengqing</groupId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <artifactId>app</artifactId> | ||||
| 
 | ||||
|     <name>${project.artifactId}</name> | ||||
|     <version>${revision}</version> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <description>业务开发模块</description> | ||||
| 
 | ||||
|     <properties> | ||||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>core</artifactId> | ||||
|             <exclusions> | ||||
|                 <exclusion> | ||||
|                     <artifactId>servlet-api</artifactId> | ||||
|                     <groupId>javax.servlet</groupId> | ||||
|                 </exclusion> | ||||
|             </exclusions> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>system</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|     </dependencies> | ||||
| 
 | ||||
| 
 | ||||
|     <build> | ||||
|         <finalName>${project.name}</finalName> | ||||
|         <plugins> | ||||
|             <plugin> | ||||
|                 <groupId>org.springframework.boot</groupId> | ||||
|                 <artifactId>spring-boot-maven-plugin</artifactId> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|     </build> | ||||
| 
 | ||||
| 
 | ||||
| </project> | ||||
| @ -0,0 +1,17 @@ | ||||
| package com.zhengqing.app; | ||||
| 
 | ||||
| import com.zhengqing.common.base.constant.ServiceConstant; | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.context.annotation.ComponentScan; | ||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
| 
 | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| @EnableTransactionManagement | ||||
| @ComponentScan(basePackages = {ServiceConstant.SERVICE_BASE_PACKAGE}) | ||||
| public class App { | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(App.class, args); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| # TODO 在这里修改相关配置信息 | ||||
| # ================================== ↓↓↓↓↓↓ smallboot配置 ↓↓↓↓↓↓ ================================== | ||||
| smallboot: | ||||
|   ip: 127.0.0.1 | ||||
|   minio: | ||||
|     url: http://172.16.16.244:9001 | ||||
|     accessKey: admin | ||||
|     secretKey: password | ||||
|     bucketName: test | ||||
|   # redis密码 | ||||
|   redis: | ||||
|     database: 0 | ||||
|     host: ${smallboot.ip} | ||||
|     port: 6379 | ||||
|     password: 123456 | ||||
|   # mysql数据源连接参数 | ||||
|   mysql: | ||||
|     # MySQL在高版本需要指明是否进行SSL连接 解决则加上 &useSSL=false | ||||
|     # &serverTimezone=Asia/Shanghai :解决fastjson时差13小时问题 | ||||
|     # rewriteBatchedStatements=true =》可实现多条sql合并提交给mysql,解决MybatisPlus批量插入慢问题 | ||||
|     url-params: ?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true | ||||
|     master: | ||||
|       ip: ${smallboot.ip} | ||||
|       port: 3306 | ||||
|       db-name: smallboot | ||||
|       username: root | ||||
|       password: root | ||||
|       url-params: ${smallboot.mysql.url-params} | ||||
|   # sql日志,默认关闭  true:开启  false:关闭 | ||||
|   mybatis-plus-sql-log: false | ||||
|   # api日志,默认关闭 true:开启  false:关闭 | ||||
|   api-log: true | ||||
| @ -0,0 +1,27 @@ | ||||
| server: | ||||
|   port: 888 | ||||
| 
 | ||||
| spring: | ||||
|   application: | ||||
|     name: app # 应用名称 | ||||
|   profiles: | ||||
|     active: dev # 环境配置 | ||||
|     # 聚合各个基础组件配置 | ||||
|     include: | ||||
|       - auth | ||||
|       - base | ||||
|       - db | ||||
|       - file | ||||
|       - log | ||||
|       - redis | ||||
|       - swagger | ||||
|       - web | ||||
| 
 | ||||
| management: | ||||
|   endpoints: | ||||
|     web: | ||||
|       exposure: | ||||
|         include: '*'  # 暴露监控端点 | ||||
| #  security: | ||||
| #    enabled: false   # 关闭安全验证 | ||||
| 
 | ||||
| @ -0,0 +1,46 @@ | ||||
| package com.zhengqing.app; | ||||
| 
 | ||||
| import cn.hutool.core.date.DatePattern; | ||||
| import cn.hutool.core.date.DateUtil; | ||||
| import cn.hutool.core.lang.UUID; | ||||
| import cn.hutool.crypto.SecureUtil; | ||||
| import cn.hutool.crypto.symmetric.AES; | ||||
| import com.zhengqing.common.base.constant.AppConstant; | ||||
| import com.zhengqing.common.base.util.AutoUpgradeUtil; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| 
 | ||||
| @Slf4j | ||||
| public class AppTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void test() throws Exception { | ||||
|         System.out.println("hello"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void test02() throws Exception { | ||||
|         AES aes = SecureUtil.aes(AppConstant.AES_KEY); | ||||
|         // 加密
 | ||||
|         String encrypt = aes.encryptHex("123456"); | ||||
|         System.out.println(encrypt); | ||||
|         // 解密
 | ||||
|         String decryptStr = aes.decryptStr(encrypt); | ||||
|         System.out.println(decryptStr); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void test03() throws Exception { | ||||
|         String today = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN); | ||||
|         String s = AutoUpgradeUtil.autoUpgrade(null); | ||||
|         System.out.println(s); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void test04() throws Exception { | ||||
|         System.out.println(UUID.randomUUID().toString()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package com.zhengqing.app; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| @Slf4j | ||||
| class ColorMain { | ||||
| 
 | ||||
|     @Test | ||||
|     public void test() throws Exception { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,49 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <parent> | ||||
|         <artifactId>common</artifactId> | ||||
|         <groupId>com.zhengqing</groupId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <artifactId>auth</artifactId> | ||||
| 
 | ||||
|     <name>${project.artifactId}</name> | ||||
|     <version>${revision}</version> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <properties> | ||||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>base</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ --> | ||||
|         <dependency> | ||||
|             <groupId>cn.dev33</groupId> | ||||
|             <artifactId>sa-token-spring-boot-starter</artifactId> | ||||
|             <version>1.27.0</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- Sa-Token 整合 Redis (使用jackson序列化方式) --> | ||||
|         <dependency> | ||||
|             <groupId>cn.dev33</groupId> | ||||
|             <artifactId>sa-token-dao-redis-jackson</artifactId> | ||||
|             <version>1.27.0</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- 提供Redis连接池 --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|             <artifactId>commons-pool2</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
| </project> | ||||
| @ -0,0 +1,94 @@ | ||||
| package com.zhengqing.common.auth.api; | ||||
| 
 | ||||
| 
 | ||||
| import cn.dev33.satoken.SaManager; | ||||
| import cn.dev33.satoken.config.SaTokenConfig; | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.dev33.satoken.util.SaResult; | ||||
| import cn.hutool.core.date.DateTime; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 测试api </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/10/5 2:36 下午 | ||||
|  */ | ||||
| @Slf4j | ||||
| @RestController | ||||
| @RequestMapping("/test") | ||||
| @Api(tags = "测试api") | ||||
| public class TestController { | ||||
| 
 | ||||
|     @GetMapping("time") | ||||
|     @ApiOperation("time") | ||||
|     public String time() { | ||||
|         log.info("time: {}", DateTime.now()); | ||||
|         return DateTime.now().toString(); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("getSaTokenConfig") | ||||
|     @ApiOperation("Sa-Token配置") | ||||
|     public SaTokenConfig getSaTokenConfig() { | ||||
|         log.info("Sa-Token配置:{}", SaManager.getConfig()); | ||||
|         return SaManager.getConfig(); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("doLogin") | ||||
|     @ApiOperation("登录") | ||||
|     public String doLogin(String username, String password) { | ||||
|         // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
 | ||||
|         if ("zhang".equals(username) && "123456".equals(password)) { | ||||
|             StpUtil.logout(); | ||||
|             StpUtil.login(10001); | ||||
|             return "登录成功" + StpUtil.getLoginId(); | ||||
|         } | ||||
|         StpUtil.logout(); | ||||
|         return "登录失败"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("isLogin") | ||||
|     @ApiOperation("查询登录状态") | ||||
|     public String isLogin() { | ||||
|         return "当前会话是否登录:" + StpUtil.isLogin(); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("logout") | ||||
|     @ApiOperation("退出登录") | ||||
|     public String logout(String loginId) { | ||||
|         StpUtil.logoutByLoginId(loginId); | ||||
|         return "SUCCESS"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("checkLogin") | ||||
|     @ApiOperation("检查是否登录") | ||||
|     public String checkLogin() { | ||||
|         try { | ||||
|             StpUtil.checkLogin(); | ||||
|         } catch (Exception e) { | ||||
|             log.info("登录认证失效:{}", e.getMessage()); | ||||
|             return "FAIL:" + e.getMessage(); | ||||
|         } | ||||
|         log.info("登录了..."); | ||||
|         return "SUCCESS"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("tokenInfo") | ||||
|     @ApiOperation("查询Token信息") | ||||
|     public SaResult tokenInfo() { | ||||
|         return SaResult.data(StpUtil.getTokenInfo()); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("getTokenValueByLoginId") | ||||
|     @ApiOperation("获取账号id为10001的token令牌值") | ||||
|     public String getTokenValueByLoginId() { | ||||
|         return StpUtil.getTokenValueByLoginId(10001); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.zhengqing.common.auth.config; | ||||
| 
 | ||||
| import cn.dev33.satoken.strategy.SaStrategy; | ||||
| import cn.dev33.satoken.util.SaFoxUtil; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 自定义sa-token生成策略 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/11/3 12:06 | ||||
|  */ | ||||
| @Configuration | ||||
| public class SaTokenCustomConfig { | ||||
| 
 | ||||
|     /** | ||||
|      * 重写 Sa-Token 框架内部算法策略 | ||||
|      */ | ||||
|     @Autowired | ||||
|     public void rewriteSaStrategy() { | ||||
|         // 重写 Token 生成策略
 | ||||
|         SaStrategy.me.createToken = (loginId, loginType) -> { | ||||
|             // 随机60位长度字符串
 | ||||
|             return SaFoxUtil.getRandomString(60); | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| package com.zhengqing.common.auth.config; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * <p> Sa-Token 拦截/开放 URL 配置类 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/11/3 8:54 下午 | ||||
|  */ | ||||
| @Data | ||||
| @Configuration | ||||
| @ConfigurationProperties(prefix = "sa-token", ignoreUnknownFields = true) | ||||
| public class SaTokenUrlConfig { | ||||
| 
 | ||||
|     /** | ||||
|      * 拦截url | ||||
|      */ | ||||
|     private List<String> interceptUrlList; | ||||
| 
 | ||||
|     /** | ||||
|      * 开放url | ||||
|      */ | ||||
|     private List<String> openUrlList; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.zhengqing.common.auth.config; | ||||
| 
 | ||||
| import cn.dev33.satoken.interceptor.SaRouteInterceptor; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| 
 | ||||
| import javax.annotation.Resource; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 注册 Sa-Token 路由拦截器 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/11/3 12:06 | ||||
|  */ | ||||
| @Configuration | ||||
| public class SaTokenWebMvcConfig implements WebMvcConfigurer { | ||||
| 
 | ||||
|     @Resource | ||||
|     private SaTokenUrlConfig saTokenUrlConfig; | ||||
| 
 | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) { | ||||
|         // 注册1个登录认证拦截器
 | ||||
|         registry.addInterceptor(new SaRouteInterceptor()) | ||||
|                 .addPathPatterns(this.saTokenUrlConfig.getInterceptUrlList()) | ||||
|                 .excludePathPatterns(this.saTokenUrlConfig.getOpenUrlList()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.zhengqing.common.auth.model.bo; | ||||
| 
 | ||||
| import com.zhengqing.common.base.model.bo.BaseBO; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 用户token信息 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/11/28 23:16 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class JwtUserBO extends BaseBO { | ||||
| 
 | ||||
|     @ApiModelProperty(value = "用户ID") | ||||
|     private Integer userId; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "用户名") | ||||
|     private String userName; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| package com.zhengqing.common.auth.model.dto; | ||||
| 
 | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Builder; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| 
 | ||||
| import javax.validation.constraints.NotBlank; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 登录参数 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/4/15 20:55 | ||||
|  */ | ||||
| @Data | ||||
| @Builder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class AuthLoginDTO { | ||||
| 
 | ||||
|     @NotBlank(message = "账号不能为空!") | ||||
|     @ApiModelProperty(value = "账号", required = true, example = "test") | ||||
|     private String username; | ||||
| 
 | ||||
|     @NotBlank(message = "密码不能为空!") | ||||
|     @ApiModelProperty(value = "密码", example = "123456") | ||||
|     private String password; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.zhengqing.common.auth.model.vo; | ||||
| 
 | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Builder; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 登录参数 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/4/15 20:55 | ||||
|  */ | ||||
| @Data | ||||
| @Builder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class AuthLoginVO { | ||||
| 
 | ||||
|     @ApiModelProperty("认证请求头名") | ||||
|     private String tokenName; | ||||
| 
 | ||||
|     @ApiModelProperty("认证值") | ||||
|     private String tokenValue; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| # Sa-Token配置 | ||||
| sa-token: | ||||
|   # token名称 (同时也是cookie名称)  注意不能带冒号: | ||||
|   token-name: Authorization-smallboot | ||||
|   # token值前缀 | ||||
|   #  token-prefix: Bearer | ||||
|   # token有效期,单位s 这里设置1天, -1代表永不过期 | ||||
|   timeout: 86400 | ||||
|   # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 | ||||
|   activity-timeout: -1 | ||||
|   # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) | ||||
|   is-concurrent: false | ||||
|   # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) | ||||
|   is-share: false | ||||
|   # token风格 | ||||
|   token-style: random-128 | ||||
|   # 是否从cookie中读取token | ||||
|   is-read-cookie: false | ||||
|   # 是否从请求体里读取token | ||||
|   is-read-body: false | ||||
|   # 是否从head中读取token | ||||
|   is-read-head: true | ||||
|   # 是否输出操作日志 | ||||
|   is-log: true | ||||
|   # 是否在初始化配置时打印版本字符画 | ||||
|   is-print: false | ||||
|   # 拦截url | ||||
|   intercept-url-list: | ||||
|     - /** | ||||
|   # 开放url | ||||
|   open-url-list: | ||||
|     # Knife4j | ||||
|     - /webjars/** | ||||
|     - /doc.html | ||||
|     - /swagger-resources/** | ||||
|     - /v2/api-docs | ||||
|     - /v3/api-docs | ||||
|     - /favicon.ico | ||||
|     # 其它 | ||||
|     - /auth/** | ||||
| @ -0,0 +1,30 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <parent> | ||||
|         <artifactId>common</artifactId> | ||||
|         <groupId>com.zhengqing</groupId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <artifactId>base</artifactId> | ||||
| 
 | ||||
|     <name>${project.artifactId}</name> | ||||
|     <version>${revision}</version> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <properties> | ||||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>swagger</artifactId> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
| </project> | ||||
| @ -0,0 +1,165 @@ | ||||
| package com.zhengqing.common.base.constant; | ||||
| 
 | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 全局常用变量 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/10/12 14:47 | ||||
|  */ | ||||
| public interface AppConstant extends BaseConstant { | ||||
| 
 | ||||
|     /** | ||||
|      * 解决返回json字符串中文乱码问题 | ||||
|      */ | ||||
|     String CONTENT_TYPE = "application/json;charset=utf-8"; | ||||
| 
 | ||||
|     /** | ||||
|      * 实体类名 | ||||
|      */ | ||||
|     String ENTITY_NAME = "${entity}"; | ||||
| 
 | ||||
|     /** | ||||
|      * 接口url | ||||
|      */ | ||||
|     Map<String, String> URL_MAPPING_MAP = new HashMap<>(); | ||||
| 
 | ||||
|     /** | ||||
|      * 密码加密相关 | ||||
|      */ | ||||
|     String DEFAULT_PASSWORD = "123456"; | ||||
|     String SALT = "zhengqing"; | ||||
|     int HASH_ITERATIONS = 1; | ||||
| 
 | ||||
|     /** | ||||
|      * 用于登录密码加密解密 | ||||
|      */ | ||||
|     String DES_KEY = "deskeyzq"; | ||||
| 
 | ||||
|     /** | ||||
|      * 用于DB中的密码加密解密 | ||||
|      */ | ||||
|     byte[] AES_KEY = "123456789abcdefg".getBytes(StandardCharsets.UTF_8); | ||||
| 
 | ||||
|     /** | ||||
|      * 请求头类型: application/x-www-form-urlencoded : form表单格式 application/json : json格式 | ||||
|      */ | ||||
|     String REQUEST_HEADERS_CONTENT_TYPE = "application/json"; | ||||
| 
 | ||||
|     /** | ||||
|      * 系统超级管理员id | ||||
|      */ | ||||
|     Integer SYSTEM_SUPER_ADMIN_USER_ID = 1; | ||||
| 
 | ||||
|     /** | ||||
|      * 登录者角色 | ||||
|      */ | ||||
|     String ROLE_LOGIN = "role_login"; | ||||
| 
 | ||||
|     /** | ||||
|      * 所有第一级项目关联包父类id、父包名 | ||||
|      */ | ||||
|     Integer PROJECT_RE_PACKAGE_PARENT_ID = 0; | ||||
|     String PROJECT_RE_PACKAGE_PARENT_NAME = "com.zhengqing.demo"; | ||||
| 
 | ||||
|     /** | ||||
|      * 系统权限相关 | ||||
|      */ | ||||
|     String WRONG_PASSWORD = "密码错误!"; | ||||
|     String WRONG_OLD_PASSWORD = "原密码错误!"; | ||||
|     String NO_USERNAME = "用户名不存在!"; | ||||
|     String NO_PERMISSION = "请联系管理员为其分配角色!"; | ||||
|     String NO_TOKEN = "TOKEN已过期请重新登录!"; | ||||
|     String WRONG_USERNAME_PASSWORD = "用户名或密码错误!"; | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ 文件系列 ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * liunx系统分隔符 | ||||
|      */ | ||||
|     String SEPARATOR_SPRIT = "/"; | ||||
|     /** | ||||
|      * win系统分隔符 | ||||
|      */ | ||||
|     String SEPARATOR_BACKSLASH = "\\\\"; | ||||
|     /** | ||||
|      * 分隔符 - 逗号 | ||||
|      */ | ||||
|     String SEPARATOR_COMMA = ","; | ||||
|     /** | ||||
|      * 分隔符 - 点 | ||||
|      */ | ||||
|     String SEPARATOR_SPOT = "."; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取项目根目录 | ||||
|      */ | ||||
|     String PROJECT_ROOT_DIRECTORY = System.getProperty("user.dir") | ||||
|             .replaceAll("\\\\", SEPARATOR_SPRIT); | ||||
| 
 | ||||
|     String IMG_DOMAIN = ""; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * csdn域名前缀 | ||||
|      */ | ||||
|     String CSDN_DOMAIN_PREFIX = "https://blog.csdn.net/"; | ||||
|     /** | ||||
|      * csdn博客临时存储路径 | ||||
|      */ | ||||
|     String FILE_PATH_CSDN_BLOG_IMPORT_SRC = PROJECT_ROOT_DIRECTORY + "/tmp/import/blog"; | ||||
|     String FILE_PATH_CSDN_BLOG_IMPORT_ZIP = PROJECT_ROOT_DIRECTORY + "/tmp/import/blog.zip"; | ||||
|     String FILE_PATH_CSDN_BLOG_EXPORT_SRC = PROJECT_ROOT_DIRECTORY + "/tmp/export/blog"; | ||||
|     String FILE_PATH_CSDN_BLOG_EXPORT_ZIP = PROJECT_ROOT_DIRECTORY + "/tmp/export/blog.zip"; | ||||
|     String FILE_PATH_CSDN_BLOG_EXPORT_EXCEL = PROJECT_ROOT_DIRECTORY + "/tmp/export/blog/excel.xls"; | ||||
|     String FILE_PATH_CSDN_BLOG_EXPORT_HTML = | ||||
|             PROJECT_ROOT_DIRECTORY + "/tmp/export/blog/html/" + System.currentTimeMillis() + "/"; | ||||
|     /** | ||||
|      * 代码生成临时存储路径 | ||||
|      */ | ||||
|     String FILE_PATH_CODE_GENERATOR_DATA_PATH = PROJECT_ROOT_DIRECTORY + "/tmp/upload"; | ||||
|     String FILE_PATH_CODE_GENERATOR_SRC_CODE = PROJECT_ROOT_DIRECTORY + "/tmp/upload/generate_code"; | ||||
|     String FILE_PATH_CODE_GENERATOR_TEMPLATE_CODE = | ||||
|             PROJECT_ROOT_DIRECTORY + "/tmp/upload/template_code"; | ||||
| 
 | ||||
|     String FILE_PATH_CODE_GENERATOR_FILE_NAME_DATA = | ||||
|             PROJECT_ROOT_DIRECTORY + "/tmp/upload/handle_generate_file_name_data"; | ||||
|     String FILE_PATH_CODE_GENERATOR_ZIP = PROJECT_ROOT_DIRECTORY + "/tmp/upload/code.zip"; | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库导出word文档路径 | ||||
|      */ | ||||
|     String FILE_PATH_DB_WORD = PROJECT_ROOT_DIRECTORY + "/tmp/db/数据库信息.doc"; | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ redis缓存系列 ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ 业务系列 ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * 缓存默认过期时间 - 24小时 | ||||
|      */ | ||||
|     Long DEFAULT_EXPIRES_TIME = 24 * 60 * 60 * 1000L; | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ 其它 ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * 限流测试 | ||||
|      */ | ||||
|     String API_LIMIT_KEY = "API_LIMIT"; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,45 @@ | ||||
| package com.zhengqing.common.base.constant; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 全局常用变量 - 工程使用 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/7/20 18:16 | ||||
|  */ | ||||
| public interface ProjectConstant { | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ 文件系列 ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * 系统分隔符 | ||||
|      */ | ||||
|     String SYSTEM_SEPARATOR = "/"; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取项目根目录 | ||||
|      */ | ||||
|     String PROJECT_ROOT_DIRECTORY = System.getProperty("user.dir").replaceAll("\\\\", SYSTEM_SEPARATOR); | ||||
| 
 | ||||
|     /** | ||||
|      * 临时文件相关 | ||||
|      */ | ||||
|     String DEFAULT_FOLDER_TMP = PROJECT_ROOT_DIRECTORY + "/tmp"; | ||||
|     String DEFAULT_FOLDER_TMP_GENERATE = PROJECT_ROOT_DIRECTORY + "/tmp-generate"; | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ other ↓↓↓↓↓↓ ==============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * 实体类 | ||||
|      */ | ||||
|     String ENTITY_PACKAGE = "com.zhengqing.*.entity"; | ||||
|     /** | ||||
|      * mapper | ||||
|      */ | ||||
|     String MAPPER_PACKAGE = "com.zhengqing.*.mapper"; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.zhengqing.common.base.constant; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * <p> RPC常用变量 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/7/20 18:16 | ||||
|  */ | ||||
| public interface RpcConstant extends BaseConstant { | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ rpc ↓↓↓↓↓↓ ================================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ other ↓↓↓↓↓↓ ==============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,77 @@ | ||||
| package com.zhengqing.common.base.constant; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 全局常用变量 - 安全认证 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2022/6/13 7:14 PM | ||||
|  */ | ||||
| public interface SecurityConstant { | ||||
| 
 | ||||
|     /** | ||||
|      * 认证请求头key | ||||
|      */ | ||||
|     String AUTHORIZATION_KEY = "Authorization-smallboot"; | ||||
| 
 | ||||
|     /** | ||||
|      * Basic认证前缀 | ||||
|      */ | ||||
|     String BASIC_PREFIX = "Basic "; | ||||
| 
 | ||||
|     /** | ||||
|      * JWT令牌前缀 | ||||
|      */ | ||||
|     String JWT_PREFIX = "Bearer "; | ||||
| 
 | ||||
|     /** | ||||
|      * JWT存储权限前缀 | ||||
|      */ | ||||
|     String AUTHORITY_PREFIX = "ROLE_"; | ||||
| 
 | ||||
|     /** | ||||
|      * JWT存储权限属性 | ||||
|      */ | ||||
|     String JWT_AUTHORITIES_KEY = "authorities"; | ||||
| 
 | ||||
|     /** | ||||
|      * 客户端ID | ||||
|      */ | ||||
|     String CLIENT_ID_KEY = "client_id"; | ||||
| 
 | ||||
|     /** | ||||
|      * 刷新token | ||||
|      */ | ||||
|     String REFRESH_TOKEN_KEY = "refresh_token"; | ||||
| 
 | ||||
|     /** | ||||
|      * 认证身份标识 | ||||
|      */ | ||||
|     String GRANT_TYPE = "authGrantType"; | ||||
| 
 | ||||
|     /** | ||||
|      * 超级管理员角色标识 | ||||
|      */ | ||||
|     String SUPER_ADMIN_ROLE_CODE = "super_admin"; | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ redis缓存 ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * jwt自定义用户信息 | ||||
|      */ | ||||
|     String JWT_CUSTOM_USER = "smallboot:system:jwt_custom_user:"; | ||||
| 
 | ||||
|     /** | ||||
|      * url权限关联角色 | ||||
|      * [ {接口路径:[角色编码]},...] | ||||
|      */ | ||||
|     String URL_PERM_RE_ROLES = "smallboot:system:perm_rule:url"; | ||||
| 
 | ||||
|     /** | ||||
|      * 验证码 | ||||
|      */ | ||||
|     String CAPTCHA_CODE = "smallboot:auth:captcha:"; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.zhengqing.common.base.constant; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 全局常用变量 - 工程使用 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/7/20 18:16 | ||||
|  */ | ||||
| public interface ServiceConstant { | ||||
| 
 | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ service ↓↓↓↓↓↓ ============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     String SERVICE_BASE_PACKAGE = "com.zhengqing"; | ||||
|     /** | ||||
|      * api基础前缀 | ||||
|      */ | ||||
|     String SERVICE_API_PREFIX_WEB = "/web/api"; | ||||
|     String SERVICE_API_PREFIX_MINI = "/mini/api"; | ||||
|     /** | ||||
|      * 各服务api前缀 | ||||
|      */ | ||||
|     String SERVICE_API_PREFIX_WEB_SYSTEM = SERVICE_API_PREFIX_WEB + "/system"; | ||||
|     String SERVICE_API_PREFIX_WEB_BUS = SERVICE_API_PREFIX_WEB + "/bus"; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| package com.zhengqing.common.base.constant; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 全局常用变量 - 线程池 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/5/27 10:52 | ||||
|  */ | ||||
| public interface ThreadPoolConstant { | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ 线程池 ↓↓↓↓↓↓ ==============================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * SmallTools线程池 | ||||
|      */ | ||||
|     String SMALL_TOOLS_THREAD_POOL = "smallToolsThreadPoolTaskExecutor"; | ||||
| 
 | ||||
|     // ===============================================================================
 | ||||
|     // ============================ ↓↓↓↓↓↓ 线程池 - 线程名前缀 ↓↓↓↓↓↓ ===================
 | ||||
|     // ===============================================================================
 | ||||
| 
 | ||||
|     /** | ||||
|      * 替换原生Spring默认线程池-线程名前缀 | ||||
|      */ | ||||
|     String SPRING_DEFAULT_THREAD_NAME_PREFIX = "MyTaskExecutorInit-"; | ||||
|     /** | ||||
|      * SmallTools线程池-线程名前缀 | ||||
|      */ | ||||
|     String SMALL_TOOLS_THREAD_NAME_PREFIX = "SmallToolsTaskExecutor-"; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| package com.zhengqing.common.base.context; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 认证来源上下文 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description {@link com.zhengqing.common.base.enums.AuthSourceEnum} | ||||
|  * @date 2021/6/30 9:24 下午 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class AuthSourceContext { | ||||
| 
 | ||||
|     public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>(); | ||||
| 
 | ||||
|     public static void set(String authSource) { | ||||
|         THREAD_LOCAL.set(authSource); | ||||
|     } | ||||
| 
 | ||||
|     public static String get() { | ||||
|         return THREAD_LOCAL.get(); | ||||
|     } | ||||
| 
 | ||||
|     public static void remove() { | ||||
|         THREAD_LOCAL.remove(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| package com.zhengqing.common.base.context; | ||||
| 
 | ||||
| 
 | ||||
| import com.zhengqing.common.base.model.bo.JwtCustomUserBO; | ||||
| 
 | ||||
| /** | ||||
|  * <p> jwt自定义用户信息上下文 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 请务必在请求结束时, 调用 @Method remove() | ||||
|  * @date 2020/8/1 19:07 | ||||
|  */ | ||||
| public class JwtCustomUserContext { | ||||
| 
 | ||||
|     public static final ThreadLocal<JwtCustomUserBO> THREAD_LOCAL = new ThreadLocal<>(); | ||||
| 
 | ||||
|     public static JwtCustomUserBO get() { | ||||
|         return THREAD_LOCAL.get(); | ||||
|     } | ||||
| 
 | ||||
|     public static void set(JwtCustomUserBO jwtCustomUserBO) { | ||||
|         THREAD_LOCAL.set(jwtCustomUserBO); | ||||
|     } | ||||
| 
 | ||||
|     public static void remove() { | ||||
|         THREAD_LOCAL.remove(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| package com.zhengqing.common.base.context; | ||||
| 
 | ||||
| import com.zhengqing.common.base.constant.BaseConstant; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * <p> B端系统用户上下文 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 请务必在请求结束时, 调用 @Method remove() | ||||
|  * @date 2020/8/1 19:07 | ||||
|  */ | ||||
| public class SysUserContext { | ||||
| 
 | ||||
|     public static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>(); | ||||
| 
 | ||||
|     public static Object get(String key) { | ||||
|         Map<String, Object> map = THREAD_LOCAL.get(); | ||||
|         if (map == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return map.get(key); | ||||
|     } | ||||
| 
 | ||||
|     public static Integer getUserId() { | ||||
|         Object value = get(BaseConstant.CONTEXT_KEY_SYS_USER_ID); | ||||
|         return value == null ? Integer.valueOf(BaseConstant.DEFAULT_CONTEXT_KEY_USER_ID) : (Integer) value; | ||||
|     } | ||||
| 
 | ||||
|     public static String getUsername() { | ||||
|         Object value = get(BaseConstant.CONTEXT_KEY_USERNAME); | ||||
|         return value == null ? BaseConstant.DEFAULT_CONTEXT_KEY_USERNAME : (String) value; | ||||
|     } | ||||
| 
 | ||||
|     public static void setUserId(Integer userId) { | ||||
|         set(BaseConstant.CONTEXT_KEY_SYS_USER_ID, userId); | ||||
|     } | ||||
| 
 | ||||
|     public static void setUsername(String username) { | ||||
|         set(BaseConstant.CONTEXT_KEY_USERNAME, username); | ||||
|     } | ||||
| 
 | ||||
|     private static void set(String key, Object value) { | ||||
|         Map<String, Object> map = THREAD_LOCAL.get(); | ||||
|         if (map == null) { | ||||
|             map = new HashMap<>(1); | ||||
|             THREAD_LOCAL.set(map); | ||||
|         } | ||||
|         map.put(key, value); | ||||
|     } | ||||
| 
 | ||||
|     public static void remove() { | ||||
|         THREAD_LOCAL.remove(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,49 @@ | ||||
| package com.zhengqing.common.base.context; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 租户ID上下文 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/6/30 9:24 下午 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class TenantIdContext { | ||||
| 
 | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     public static final ThreadLocal<Integer> TENANT_ID_THREAD_LOCAL = new ThreadLocal<>(); | ||||
|     /** | ||||
|      * 租户ID是否启用标识 | ||||
|      * true : 是 -> 执行sql时,自动拼接租户ID | ||||
|      * false: 否 -> 执行sql时,不自动拼接租户ID | ||||
|      */ | ||||
|     public static final ThreadLocal<Boolean> TENANT_ID_FLAG_THREAD_LOCAL = new ThreadLocal<>(); | ||||
| 
 | ||||
|     public static void setTenantId(Integer tenantId) { | ||||
|         TENANT_ID_THREAD_LOCAL.set(tenantId); | ||||
|         TENANT_ID_FLAG_THREAD_LOCAL.set(true); | ||||
|     } | ||||
| 
 | ||||
|     public static Integer getTenantId() { | ||||
|         return TENANT_ID_THREAD_LOCAL.get(); | ||||
|     } | ||||
| 
 | ||||
|     public static void removeFlag() { | ||||
|         TENANT_ID_FLAG_THREAD_LOCAL.set(false); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static Boolean getFlag() { | ||||
|         return TENANT_ID_FLAG_THREAD_LOCAL.get(); | ||||
|     } | ||||
| 
 | ||||
|     public static void remove() { | ||||
|         TENANT_ID_THREAD_LOCAL.remove(); | ||||
|         TENANT_ID_FLAG_THREAD_LOCAL.remove(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| package com.zhengqing.common.base.context; | ||||
| 
 | ||||
| import com.zhengqing.common.base.constant.BaseConstant; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * <p> C端用户上下文 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 请务必在请求结束时, 调用 @Method remove() | ||||
|  * @date 2020/8/1 19:07 | ||||
|  */ | ||||
| public class UmsUserContext { | ||||
| 
 | ||||
|     public static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>(); | ||||
| 
 | ||||
|     public static Object get(String key) { | ||||
|         Map<String, Object> map = THREAD_LOCAL.get(); | ||||
|         if (map == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return map.get(key); | ||||
|     } | ||||
| 
 | ||||
|     public static Long getUserId() { | ||||
|         Object value = get(BaseConstant.CONTEXT_KEY_UMS_USER_ID); | ||||
|         return value == null ? Long.valueOf(BaseConstant.DEFAULT_CONTEXT_KEY_USER_ID) : (Long) value; | ||||
|     } | ||||
| 
 | ||||
|     public static String getUsername() { | ||||
|         Object value = get(BaseConstant.CONTEXT_KEY_USERNAME); | ||||
|         return value == null ? BaseConstant.DEFAULT_CONTEXT_KEY_USERNAME : (String) value; | ||||
|     } | ||||
| 
 | ||||
|     public static void setUserId(Long userId) { | ||||
|         set(BaseConstant.CONTEXT_KEY_UMS_USER_ID, userId); | ||||
|     } | ||||
| 
 | ||||
|     public static void setUsername(String username) { | ||||
|         set(BaseConstant.CONTEXT_KEY_USERNAME, username); | ||||
|     } | ||||
| 
 | ||||
|     private static void set(String key, Object value) { | ||||
|         Map<String, Object> map = THREAD_LOCAL.get(); | ||||
|         if (map == null) { | ||||
|             map = new HashMap<>(1); | ||||
|             THREAD_LOCAL.set(map); | ||||
|         } | ||||
|         map.put(key, value); | ||||
|     } | ||||
| 
 | ||||
|     public static void remove() { | ||||
|         THREAD_LOCAL.remove(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| package com.zhengqing.common.base.enums; | ||||
| 
 | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 响应码枚举 - 可参考HTTP状态码的语义 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/8/22 11:09 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum ApiResultCodeEnum { | ||||
| 
 | ||||
|     // 成功
 | ||||
|     SUCCESS(200, "SUCCESS"), | ||||
|     // 失败
 | ||||
|     FAILURE(400, "FAILURE"), | ||||
|     // 参数校验失败
 | ||||
|     PARAM_VALID_ERROR(400, "参数校验失败"), | ||||
|     // 未登录
 | ||||
|     UN_LOGIN(401, "请求未授权"), | ||||
|     // 未通过认证
 | ||||
|     USER_UNAUTHORIZED(402, "用户名或密码不正确"), | ||||
|     // 用户不存在
 | ||||
|     USER_NOT_EXIST(402, "用户不存在"), | ||||
|     // 未认证(签名错误、token错误)
 | ||||
|     UNAUTHORIZED(403, "未认证"), | ||||
|     // 客户端认证失败
 | ||||
|     CLIENT_AUTHENTICATION_FAILED(405, "客户端认证失败"), | ||||
|     // 接口不存在
 | ||||
|     NOT_FOUND(404, "接口不存在"), | ||||
|     // token过期
 | ||||
|     TOKEN_EXPIRED(-1, "token过期"), | ||||
|     // token丢失
 | ||||
|     TOKEN_NOT_EXIST(-1, "token丢失"), | ||||
|     // token已被禁止访问
 | ||||
|     TOKEN_ACCESS_FORBIDDEN(-1, "token已被禁止访问"), | ||||
|     // 服务器内部错误
 | ||||
|     INTERNAL_SERVER_ERROR(500, "服务器内部错误"), | ||||
|     ; | ||||
| 
 | ||||
|     private final int code; | ||||
|     private final String desc; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.zhengqing.common.base.enums; | ||||
| 
 | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 认证来源枚举 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2022/6/10 15:18 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum AuthSourceEnum implements IBaseEnum<String> { | ||||
| 
 | ||||
|     B("B", "B端系统用户"), | ||||
|     C("C", "C端用户"); | ||||
| 
 | ||||
|     private String value; | ||||
|     private String desc; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,90 @@ | ||||
| package com.zhengqing.common.base.enums; | ||||
| 
 | ||||
| 
 | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| 
 | ||||
| import java.util.EnumSet; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 通用枚举接口 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 符号 T、E、?代表的意思: | ||||
|  * T 表示一种特定的类型 | ||||
|  * E 也是一种类型的意思,只不过通常代表集合中的元素 | ||||
|  * ? 这是一种无限的符号,代表任何类型都可以 | ||||
|  * @date 2019/8/22 11:00 | ||||
|  */ | ||||
| public interface IBaseEnum<T> { | ||||
| 
 | ||||
|     /** | ||||
|      * 值 | ||||
|      */ | ||||
|     T getValue(); | ||||
| 
 | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     String getDesc(); | ||||
| 
 | ||||
|     /** | ||||
|      * 获取所有枚举值 | ||||
|      */ | ||||
|     static <E extends Enum<E>> List<E> list(Class<E> clazz) { | ||||
|         EnumSet<E> allEnums = EnumSet.allOf(clazz); | ||||
|         return allEnums.stream().collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据值获取枚举 | ||||
|      */ | ||||
|     static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) { | ||||
|         Objects.requireNonNull(value); | ||||
|         EnumSet<E> allEnums = EnumSet.allOf(clazz); | ||||
|         E matchEnum = allEnums.stream() | ||||
|                 .filter(e -> ObjectUtil.equal(e.getValue(), value)) | ||||
|                 .findFirst() | ||||
|                 .orElse(null); | ||||
|         return matchEnum; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据值获取描述 | ||||
|      */ | ||||
|     static <E extends Enum<E> & IBaseEnum> Object getValueByDesc(String desc, Class<E> clazz) { | ||||
|         Objects.requireNonNull(desc); | ||||
|         EnumSet<E> allEnums = EnumSet.allOf(clazz); | ||||
|         String finalDesc = desc; | ||||
|         E matchEnum = allEnums.stream() | ||||
|                 .filter(e -> ObjectUtil.equal(e.getDesc(), finalDesc)) | ||||
|                 .findFirst() | ||||
|                 .orElse(null); | ||||
| 
 | ||||
|         Object value = null; | ||||
|         if (matchEnum != null) { | ||||
|             value = matchEnum.getValue(); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据描述获取值 | ||||
|      */ | ||||
|     static <E extends Enum<E> & IBaseEnum> String getDescByValue(Object value, Class<E> clazz) { | ||||
|         Objects.requireNonNull(value); | ||||
|         EnumSet<E> allEnums = EnumSet.allOf(clazz); | ||||
|         E matchEnum = allEnums.stream() | ||||
|                 .filter(e -> ObjectUtil.equal(e.getValue(), value)) | ||||
|                 .findFirst() | ||||
|                 .orElse(null); | ||||
|         String desc = null; | ||||
|         if (matchEnum != null) { | ||||
|             desc = matchEnum.getDesc(); | ||||
|         } | ||||
|         return desc; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| package com.zhengqing.common.base.enums; | ||||
| 
 | ||||
| import com.google.common.collect.Lists; | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 是/否 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/4/12 0:01 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum YesNoEnum implements IBaseEnum<Integer> { | ||||
| 
 | ||||
|     /** | ||||
|      * 是 | ||||
|      */ | ||||
|     YES(1, "是"), | ||||
|     /** | ||||
|      * 否 | ||||
|      */ | ||||
|     NO(0, "否"); | ||||
| 
 | ||||
|     /** | ||||
|      * 类型 | ||||
|      */ | ||||
|     private final Integer value; | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     private final String desc; | ||||
| 
 | ||||
|     private static final List<YesNoEnum> LIST = Lists.newArrayList(); | ||||
| 
 | ||||
|     static { | ||||
|         LIST.addAll(Arrays.asList(YesNoEnum.values())); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据指定类型查找相应枚举类 | ||||
|      */ | ||||
|     public static YesNoEnum getEnum(Integer value) { | ||||
|         for (YesNoEnum itemEnum : LIST) { | ||||
|             if (itemEnum.getValue().equals(value)) { | ||||
|                 return itemEnum; | ||||
|             } | ||||
|         } | ||||
|         throw new MyException("未找到指定类型枚举数据!"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| package com.zhengqing.common.base.exception; | ||||
| 
 | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.CollectionUtils; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.function.Supplier; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 业务断言 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/5/21 20:27 | ||||
|  */ | ||||
| public final class MyAssert extends Assert { | ||||
| 
 | ||||
|     /** | ||||
|      * 断言对象不为空 | ||||
|      * | ||||
|      * @param object 对象 | ||||
|      * @param msg    不满足断言的异常信息 | ||||
|      */ | ||||
|     public static void notNull(Object object, String msg) { | ||||
|         state(object != null, msg); | ||||
|     } | ||||
| 
 | ||||
|     public static void notNull(Object object, Supplier<String> supplier) { | ||||
|         state(object != null, supplier); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 断言字符串不为空 | ||||
|      * | ||||
|      * @param str 字符串 | ||||
|      * @param msg 不满足断言的异常信息 | ||||
|      */ | ||||
|     public static void notEmpty(String str, String msg) { | ||||
|         state(!StringUtils.isEmpty(str), msg); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 断言集合不为空 | ||||
|      * | ||||
|      * @param collection 集合 | ||||
|      * @param msg        不满足断言的异常信息 | ||||
|      */ | ||||
|     public static void notEmpty(Collection<?> collection, String msg) { | ||||
|         state(!CollectionUtils.isEmpty(collection), msg); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 断言一个boolean表达式 | ||||
|      * | ||||
|      * @param expression boolean表达式 | ||||
|      * @param message    不满足断言的异常信息 | ||||
|      */ | ||||
|     public static void state(boolean expression, String message) { | ||||
|         if (!expression) { | ||||
|             throw new MyException(message); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 断言一个boolean表达式,用于需要大量拼接字符串以及一些其他操作等 | ||||
|      * | ||||
|      * @param expression boolean表达式 | ||||
|      * @param supplier   msg生产者 | ||||
|      */ | ||||
|     public static void state(boolean expression, Supplier<String> supplier) { | ||||
|         if (!expression) { | ||||
|             throw new MyException(supplier.get()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package com.zhengqing.common.base.exception; | ||||
| 
 | ||||
| import com.zhengqing.common.base.enums.ApiResultCodeEnum; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 自定义异常类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/8/26 15:11 | ||||
|  */ | ||||
| public class MyException extends RuntimeException { | ||||
| 
 | ||||
|     /** | ||||
|      * 异常状态码 | ||||
|      */ | ||||
|     private Integer code; | ||||
| 
 | ||||
|     public MyException(Throwable cause) { | ||||
|         super(cause); | ||||
|     } | ||||
| 
 | ||||
|     public MyException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| 
 | ||||
|     public MyException(Integer code, String message) { | ||||
|         super(message); | ||||
|         this.code = code; | ||||
|     } | ||||
| 
 | ||||
|     public MyException(String message, Integer code) { | ||||
|         super(message); | ||||
|         this.code = code; | ||||
|     } | ||||
| 
 | ||||
|     public MyException(ApiResultCodeEnum response) { | ||||
|         super(response.getDesc()); | ||||
|         this.code = response.getCode(); | ||||
|     } | ||||
| 
 | ||||
|     public MyException(String message, Throwable cause) { | ||||
|         super(message, cause); | ||||
|     } | ||||
| 
 | ||||
|     public Integer getCode() { | ||||
|         return code; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| package com.zhengqing.common.base.exception; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 参数异常 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/8/1 18:07 | ||||
|  */ | ||||
| public class ParameterException extends MyException { | ||||
| 
 | ||||
|     public ParameterException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| 
 | ||||
|     public ParameterException(String message, Throwable e) { | ||||
|         super(message, e); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| package com.zhengqing.common.base.model.bo; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 基类参数 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/8/17 15:38 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| @ApiModel("基类参数") | ||||
| public class BaseBO implements Serializable { | ||||
| 
 | ||||
|     private static final long serialVersionUID = 1L; | ||||
| 
 | ||||
|     @JsonIgnore // jackson
 | ||||
| //    @JSONField(serialize = false, deserialize = false) // fastjson
 | ||||
|     @ApiModelProperty(value = "隐藏字段-解决子类lombok部分注解(ex:构造器@NoArgsConstructor、@AllArgsConstructor)无法使用问题", hidden = true) | ||||
|     private String xxx; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,48 @@ | ||||
| package com.zhengqing.common.base.model.bo; | ||||
| 
 | ||||
| import com.zhengqing.common.base.enums.AuthSourceEnum; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| /** | ||||
|  * <p> jwt中自定义的用户信息 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description {@link com.zhengqing.auth.security.config.CustomAdditionalInformation} | ||||
|  * @date 2022/6/15 10:34 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class JwtCustomUserBO { | ||||
|     /** | ||||
|      * {@link AuthSourceEnum} | ||||
|      */ | ||||
|     @ApiModelProperty(value = "认证来源") | ||||
|     private String authSource; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "jwt") | ||||
|     private String token; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "jwt的唯一身份标识") | ||||
|     private String jti; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "过期时间(2022-06-01 23:06:53)") | ||||
|     private String expireTime; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "租户ID") | ||||
|     private String tenantId; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "B端系统用户ID") | ||||
|     private String sysUserId; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "C端用户ID") | ||||
|     private String umsUserId; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "用户名") | ||||
|     private String username; | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package com.zhengqing.common.base.model.dto; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 基类查询参数 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/9/13 0013 1:57 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| @ApiModel("基类查询参数") | ||||
| public class BaseDTO implements Serializable { | ||||
| 
 | ||||
|     private static final long serialVersionUID = 1L; | ||||
| 
 | ||||
|     @JsonIgnore // 字段忽略
 | ||||
| //    @JsonProperty("userId") // 字段别名
 | ||||
|     @ApiModelProperty(value = "当前用户ID", hidden = true) | ||||
|     private Integer currentUserId; | ||||
| 
 | ||||
|     @JsonIgnore | ||||
|     @ApiModelProperty(value = "当前用户名称", hidden = true) | ||||
|     private String currentUsername; | ||||
| 
 | ||||
| //    @JsonIgnore
 | ||||
| //    @ApiModelProperty(value = "令牌", hidden = true)
 | ||||
| //    private String token;
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| package com.zhengqing.common.base.model.dto; | ||||
| 
 | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 基础分页检索参数 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/9/13 0013 1:57 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ApiModel("基础分页检索参数") | ||||
| public class BasePageDTO extends BaseDTO { | ||||
| 
 | ||||
|     @ApiModelProperty(value = "当前页", required = true, position = 0, example = "1") | ||||
|     private Integer pageNum = 1; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "每页显示数量", required = true, position = 1, example = "10") | ||||
|     private Integer pageSize = 10; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,155 @@ | ||||
| package com.zhengqing.common.base.model.vo; | ||||
| 
 | ||||
| import com.zhengqing.common.base.enums.ApiResultCodeEnum; | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.Setter; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * API返回参数 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/7/20 11:09 | ||||
|  */ | ||||
| @Setter | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| @NoArgsConstructor | ||||
| @ApiModel(value = "API返回参数") | ||||
| public class ApiResult<T> { | ||||
| 
 | ||||
|     @ApiModelProperty(value = "响应码", required = true) | ||||
|     private Integer code; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "消息内容", required = false) | ||||
|     private String msg; | ||||
| 
 | ||||
|     @ApiModelProperty(value = "响应数据", required = false) | ||||
|     private T data; | ||||
| 
 | ||||
|     public ApiResult(T data) { | ||||
|         this.code = ApiResultCodeEnum.SUCCESS.getCode(); | ||||
|         this.msg = "OK"; | ||||
|         this.data = data; | ||||
|     } | ||||
| 
 | ||||
|     public ApiResult(Integer code, String msg) { | ||||
|         this.code = code; | ||||
|         this.msg = msg; | ||||
|     } | ||||
| 
 | ||||
|     public static ApiResult ok() { | ||||
|         return new ApiResult(ApiResultCodeEnum.SUCCESS.getCode(), "OK", null); | ||||
|     } | ||||
| 
 | ||||
|     public static <E> ApiResult<E> ok(E o) { | ||||
|         // 支持Controller层直接返回ApiResult
 | ||||
|         ApiResult result = new ApiResult(ApiResultCodeEnum.SUCCESS); | ||||
|         if (o instanceof ApiResult) { | ||||
|             result = ((ApiResult) o); | ||||
|         } else { | ||||
|             // 其他obj封装进data,保持返回格式统一
 | ||||
|             result.setData(o); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     public static ApiResult ok(String data) { | ||||
|         return new ApiResult(ApiResultCodeEnum.SUCCESS.getCode(), "OK", data); | ||||
|     } | ||||
| 
 | ||||
|     public static ApiResult ok(Object data, String msg) { | ||||
|         return new ApiResult(ApiResultCodeEnum.SUCCESS.getCode(), msg, data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 自定义返回码 | ||||
|      */ | ||||
|     public static ApiResult ok(Integer code, String msg) { | ||||
|         return new ApiResult(code, msg); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 自定义 | ||||
|      * | ||||
|      * @param code 验证码 | ||||
|      * @param msg  返回消息内容 | ||||
|      * @param data 返回数据 | ||||
|      * @return 响应体 | ||||
|      */ | ||||
|     public static ApiResult ok(Integer code, String msg, Object data) { | ||||
|         return new ApiResult(code, msg, data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 过期 | ||||
|      * | ||||
|      * @param msg 消息内容 | ||||
|      * @return 响应体 | ||||
|      */ | ||||
|     public static ApiResult expired(String msg) { | ||||
|         return new ApiResult(ApiResultCodeEnum.TOKEN_EXPIRED.getCode(), msg, null); | ||||
|     } | ||||
| 
 | ||||
|     public static ApiResult fail(String msg) { | ||||
|         return new ApiResult(ApiResultCodeEnum.FAILURE.getCode(), msg, null); | ||||
|     } | ||||
| 
 | ||||
|     public static ApiResult busy() { | ||||
|         return new ApiResult(ApiResultCodeEnum.FAILURE.getCode(), "服务繁忙", null); | ||||
|     } | ||||
| 
 | ||||
|     /*** | ||||
|      * 自定义错误返回码 | ||||
|      * | ||||
|      * @param code 验证码 | ||||
|      * @param msg 消息内容 | ||||
|      * @return 响应体 | ||||
|      */ | ||||
|     public static ApiResult fail(Integer code, String msg) { | ||||
|         return new ApiResult(code, msg, null); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 是否成功 | ||||
|      * | ||||
|      * @return true->成功,false->失败 | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/6/1 12:45 | ||||
|      */ | ||||
|     public boolean checkIsSuccess() { | ||||
|         return ApiResultCodeEnum.SUCCESS.getCode() == this.code; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 是否失败 | ||||
|      * | ||||
|      * @return true->失败,false->成功 | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/6/1 12:45 | ||||
|      */ | ||||
|     public boolean checkIsFail() { | ||||
|         return ApiResultCodeEnum.SUCCESS.getCode() != this.code; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * rpc校验结果是否异常 | ||||
|      * | ||||
|      * @return void | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/10/14 16:21 | ||||
|      */ | ||||
|     public void checkForRpc() { | ||||
|         if (this.checkIsFail()) { | ||||
|             throw new MyException(this.msg); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| package com.zhengqing.common.base.model.vo; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 基类响应参数 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/8/18 16:14 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| @ApiModel("基类响应参数") | ||||
| public class BaseVO implements Serializable { | ||||
| 
 | ||||
|     private static final long serialVersionUID = 1L; | ||||
| 
 | ||||
|     @JsonIgnore | ||||
|     @ApiModelProperty(value = "隐藏字段-解决子类lombok部分注解(ex:构造器@NoArgsConstructor、@AllArgsConstructor)无法使用问题", hidden = true) | ||||
|     private String xxx; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,77 @@ | ||||
| package com.zhengqing.common.base.model.vo; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonGenerator; | ||||
| import com.fasterxml.jackson.databind.JsonSerializer; | ||||
| import com.fasterxml.jackson.databind.SerializerProvider; | ||||
| import com.fasterxml.jackson.databind.annotation.JsonSerialize; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.experimental.SuperBuilder; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 分页响应参数 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description {@link com.baomidou.mybatisplus.core.metadata.IPage } | ||||
|  * @date 2021/8/18 16:14 | ||||
|  */ | ||||
| @Data | ||||
| @SuperBuilder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| @ApiModel("基类响应参数") | ||||
| public class PageVO<T> implements Serializable { | ||||
| 
 | ||||
|     private static final long serialVersionUID = 1L; | ||||
| 
 | ||||
|     /** | ||||
|      * 当前页 | ||||
|      */ | ||||
|     @JsonSerialize(using = ToLongSerializer.class) | ||||
|     Long current; | ||||
|     /** | ||||
|      * 每页显示条数 | ||||
|      */ | ||||
|     @JsonSerialize(using = ToLongSerializer.class) | ||||
|     Long size; | ||||
|     /** | ||||
|      * 当前分页总页数 | ||||
|      */ | ||||
|     @JsonSerialize(using = ToLongSerializer.class) | ||||
|     Long pages; | ||||
|     /** | ||||
|      * 当前满足条件总行数 | ||||
|      */ | ||||
|     @JsonSerialize(using = ToLongSerializer.class) | ||||
|     Long total; | ||||
|     /** | ||||
|      * 分页记录列表 | ||||
|      */ | ||||
|     List<T> records; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * <p> jackson 转Long类型 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 解决全局将Long类型转换为String类型后,部分类又需要Long还是原本Long类型问题 | ||||
|  * 使用方式:添加注解`@JsonSerialize(using = ToLongSerializer.class)` | ||||
|  * {@link com.zhengqing.common.web.config.jackson.ToLongSerializer } | ||||
|  * 由于此为base包,不额外引用其它包中的东西,这里单独处理一下 | ||||
|  * @date 2022/8/5 17:22 | ||||
|  */ | ||||
| class ToLongSerializer extends JsonSerializer<Long> { | ||||
| 
 | ||||
|     @Override | ||||
|     public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException { | ||||
|         gen.writeNumber(value); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.context.ApplicationContextAware; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 全局上下文工具类配置 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 解决静态方法中mapper的调用 | ||||
|  * @date 2019/10/17 14:44 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Component | ||||
| public class ApplicationContextUtil implements ApplicationContextAware { | ||||
|     private static ApplicationContext applicationContext; | ||||
| 
 | ||||
|     @Override | ||||
|     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { | ||||
|         ApplicationContextUtil.applicationContext = applicationContext; | ||||
|     } | ||||
| 
 | ||||
|     public static ApplicationContext getApplicationContext() { | ||||
|         return applicationContext; | ||||
|     } | ||||
| 
 | ||||
|     public static Object getBean(String beanName) { | ||||
|         return applicationContext.getBean(beanName); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 自增编号工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/10/13$ 14:08$ | ||||
|  */ | ||||
| @Slf4j | ||||
| public class AutoUpgradeUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 自增编号 ex:0001 -> 0002 | ||||
|      * | ||||
|      * @param code 编号 | ||||
|      * @return 自增后的编号 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/10/13 14:13 | ||||
|      */ | ||||
|     public static String autoUpgrade(String code) { | ||||
|         String codeNew = "0001"; | ||||
|         if (StringUtils.isNotBlank(code)) { | ||||
|             code = code.trim(); | ||||
|             codeNew = String.format("%0" + code.length() + "d", Long.valueOf(code) + 1); | ||||
|         } | ||||
|         log.debug("《自增编号》 旧数据:【{}】  新数据:【{}】", code, codeNew); | ||||
|         return codeNew; | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         autoUpgrade("9999"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 自增版本号工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/10/13 14:08 | ||||
|  */ | ||||
| public class AutoUpgradeVersionUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 自动自增版本号 ex:0.0.1 -> 0.0.2 | ||||
|      * | ||||
|      * @param version 版本号 | ||||
|      * @return 自增后的版本号 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/10/13 14:13 | ||||
|      */ | ||||
|     public static String autoUpgradeVersion(String version) { | ||||
|         if (StringUtils.isBlank(version)) { | ||||
|             version = "0.0.1"; | ||||
|         } | ||||
|         // 将版本号拆解成整数数组
 | ||||
|         String[] versionArray = version.split("\\."); | ||||
|         int[] versionList = Arrays.stream(versionArray).mapToInt(Integer::valueOf).toArray(); | ||||
| 
 | ||||
|         // 递归调用
 | ||||
|         autoUpgradeVersion(versionList, versionList.length - 1); | ||||
| 
 | ||||
|         // 数组转字符串
 | ||||
|         version = StringUtils.join(versionList, '.'); | ||||
|         return version; | ||||
|     } | ||||
| 
 | ||||
|     private static void autoUpgradeVersion(int[] ints, int index) { | ||||
|         if (index == 0) { | ||||
|             ints[0] = ints[0] + 1; | ||||
|         } else { | ||||
|             int value = ints[index] + 1; | ||||
|             if (value < 10) { | ||||
|                 ints[index] = value; | ||||
|             } else { | ||||
|                 ints[index] = 0; | ||||
|                 autoUpgradeVersion(ints, index - 1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,282 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| 
 | ||||
| import java.math.BigDecimal; | ||||
| import java.text.NumberFormat; | ||||
| 
 | ||||
| /** | ||||
|  * <p> BigDecimal工具类 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/8/24 10:45 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class BigDecimalUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 默认除法运算精度 | ||||
|      */ | ||||
|     private static final int DEF_DIV_SCALE = 2; | ||||
| 
 | ||||
|     /** | ||||
|      * 建立货币格式化引用 | ||||
|      */ | ||||
|     private static final NumberFormat CURRENCY = NumberFormat.getCurrencyInstance(); | ||||
| 
 | ||||
|     /** | ||||
|      * 建立百分比格式化引用 | ||||
|      */ | ||||
|     private static final NumberFormat PERCENT = NumberFormat.getPercentInstance(); | ||||
| 
 | ||||
|     /** | ||||
|      * 加法 | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:49 | ||||
|      */ | ||||
|     public static BigDecimal add(BigDecimal num1, BigDecimal num2) { | ||||
|         return num1.add(num2); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 加法(默认四舍五入,根据scale保留小数位数) | ||||
|      * | ||||
|      * @param num1  数1 | ||||
|      * @param num2  数2 | ||||
|      * @param scale 保留小数位数 | ||||
|      * @return 计算结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:52 | ||||
|      */ | ||||
|     public static BigDecimal add(BigDecimal num1, BigDecimal num2, int scale) { | ||||
|         return num1.add(num2).setScale(scale, BigDecimal.ROUND_HALF_UP); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 减法 | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static BigDecimal sub(BigDecimal num1, BigDecimal num2) { | ||||
|         return num1.subtract(num2); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 减法(默认四舍五入,根据scale保留小数位数) | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static BigDecimal sub(BigDecimal num1, BigDecimal num2, int scale) { | ||||
|         return num1.subtract(num2).setScale(scale, BigDecimal.ROUND_HALF_UP); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 乘法 | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static BigDecimal multiply(BigDecimal num1, BigDecimal num2) { | ||||
|         return num1.multiply(num2); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 乘法(默认四舍五入,根据scale保留小数位数) | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static BigDecimal multiply(BigDecimal num1, BigDecimal num2, int scale) { | ||||
|         return num1.multiply(num2).setScale(scale, BigDecimal.ROUND_HALF_UP); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 除法(除不尽会抛异常) | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 = 数1 / 数2 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static BigDecimal divide(BigDecimal num1, BigDecimal num2) { | ||||
|         return num1.divide(num2, DEF_DIV_SCALE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 除法(默认四舍五入保留两位小数) | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 计算结果 = 数1 / 数2 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static BigDecimal divide(BigDecimal num1, BigDecimal num2, int scale) { | ||||
|         return num1.divide(num2, scale, BigDecimal.ROUND_HALF_UP); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 比较BigDecimal | ||||
|      * | ||||
|      * @param num1 数1 | ||||
|      * @param num2 数2 | ||||
|      * @return 相等返回0, num1>num2返回1, num1<num2返回-1 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 10:54 | ||||
|      */ | ||||
|     public static int compareTo(BigDecimal num1, BigDecimal num2) { | ||||
|         return num1.compareTo(num2); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * BigDecimal货币格式化 | ||||
|      * | ||||
|      * @param money 钱钱 | ||||
|      * @return java.lang.String | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:03 | ||||
|      */ | ||||
|     public static String currencyFormat(BigDecimal money) { | ||||
|         return CURRENCY.format(money); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * BigDecimal百分比格式化 | ||||
|      * | ||||
|      * @param money 钱钱 | ||||
|      * @return java.lang.String | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:03 | ||||
|      */ | ||||
|     public static String rateFormat(BigDecimal money) { | ||||
|         return PERCENT.format(money); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 保留小数位数 | ||||
|      * | ||||
|      * @param x     目标小数 | ||||
|      * @param scale 要保留小数位数 | ||||
|      * @return 结果四舍五入 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:04 | ||||
|      */ | ||||
|     public static BigDecimal scale(BigDecimal x, int scale) { | ||||
|         if (x == null) { | ||||
|             x = BigDecimal.valueOf(0.00); | ||||
|         } | ||||
|         return x.setScale(scale, BigDecimal.ROUND_HALF_UP); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 元转分 | ||||
|      * | ||||
|      * @param money 钱钱 (如果2位小数以上,自动四舍五入) | ||||
|      * @return 分 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:04 | ||||
|      */ | ||||
|     public static Integer yuanToFen(BigDecimal money) { | ||||
|         if (money == null) { | ||||
|             money = BigDecimal.valueOf(0.00); | ||||
|         } | ||||
|         return Integer.valueOf(multiply(money, BigDecimal.valueOf(100), 0).toString()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 分转元 | ||||
|      * | ||||
|      * @param money 钱钱 (如果2位小数以上,自动四舍五入) | ||||
|      * @return 分 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:04 | ||||
|      */ | ||||
|     public static String fenToYuan(String money) { | ||||
|         if (StringUtils.isBlank(money)) { | ||||
|             money = "0"; | ||||
|         } | ||||
|         return String.valueOf(divide(new BigDecimal(money), BigDecimal.valueOf(100), 2)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 分转元 | ||||
|      * | ||||
|      * @param money 钱钱 (如果2位小数以上,自动四舍五入) | ||||
|      * @return 分 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:04 | ||||
|      */ | ||||
|     public static String fenToYuan(Integer money) { | ||||
|         if (money == null) { | ||||
|             money = 0; | ||||
|         } | ||||
|         return String.valueOf(divide(BigDecimal.valueOf(money), BigDecimal.valueOf(100), 2)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * BigDecimal转Integer | ||||
|      * | ||||
|      * @param money 钱钱 (如果有小数,自动四舍五入) | ||||
|      * @return Integer | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/24 11:04 | ||||
|      */ | ||||
|     public static Integer toInt(BigDecimal money) { | ||||
|         if (money == null) { | ||||
|             return 0; | ||||
|         } | ||||
|         return Integer.valueOf(scale(money, 0).toString()); | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         BigDecimal num1 = new BigDecimal("80"); | ||||
|         BigDecimal num2 = new BigDecimal("60"); | ||||
| 
 | ||||
|         // 加法
 | ||||
|         log.info("加法: {}", BigDecimalUtil.add(num1, num2)); | ||||
|         log.info("加法: {}", BigDecimalUtil.add(num1, num2, 1)); | ||||
| 
 | ||||
|         // 减法
 | ||||
|         log.info("减法: {}", BigDecimalUtil.sub(num1, num2)); | ||||
|         log.info("减法: {}", BigDecimalUtil.sub(num1, num2, 2)); | ||||
| 
 | ||||
|         // 乘法
 | ||||
|         log.info("乘法: {}", BigDecimalUtil.multiply(num1, num2)); | ||||
|         log.info("乘法: {}", BigDecimalUtil.multiply(num1, num2, 3)); | ||||
| 
 | ||||
|         // 除法
 | ||||
|         log.info("除法: {}", BigDecimalUtil.divide(num1, num2)); | ||||
|         log.info("除法: {}", BigDecimalUtil.divide(num1, num2, 4)); | ||||
| 
 | ||||
|         log.info("比较: {}", BigDecimalUtil.compareTo(num1, num2)); | ||||
|         log.info("货币格式化: {}", BigDecimalUtil.currencyFormat(num2)); | ||||
|         log.info("百分比格式化: {}", BigDecimalUtil.rateFormat(num2)); | ||||
|         log.info("元转分: {}", BigDecimalUtil.yuanToFen(num2)); | ||||
|         log.info("分转元: {}", BigDecimalUtil.fenToYuan("0.01")); | ||||
|         log.info("分转元: {}", BigDecimalUtil.fenToYuan(0)); | ||||
|         log.info("BigDecimal转Integer: {}", BigDecimalUtil.toInt(num2)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,195 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import com.google.common.collect.Lists; | ||||
| import com.google.common.collect.Maps; | ||||
| import lombok.SneakyThrows; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.util.CollectionUtils; | ||||
| 
 | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * bean 工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya <br/> | ||||
|  * @date 2019/11/6$ 18:34$ <br/> | ||||
|  */ | ||||
| @Slf4j | ||||
| public class MyBeanUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 对象属性拷贝 | ||||
|      * | ||||
|      * @param source 源对象 | ||||
|      * @param target 目标对象 | ||||
|      * @return void | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/5 23:22 | ||||
|      */ | ||||
|     public static void copyProperties(Object source, Object target) { | ||||
|         BeanUtils.copyProperties(source, target); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 对象属性拷贝 : 将源对象的属性拷贝到目标对象 | ||||
|      * | ||||
|      * @param source 源对象 | ||||
|      * @param clz    目标对象class | ||||
|      * @return 对象数据 | ||||
|      */ | ||||
|     public static <T> T copyProperties(Object source, Class<T> clz) { | ||||
|         if (source == null) { | ||||
|             return null; | ||||
|         } | ||||
|         T target = BeanUtils.instantiate(clz); | ||||
|         try { | ||||
|             BeanUtils.copyProperties(source, target); | ||||
|         } catch (BeansException e) { | ||||
|             log.error("BeanUtil property copy  failed :BeansException", e); | ||||
|         } catch (Exception e) { | ||||
|             log.error("BeanUtil property copy failed:Exception", e); | ||||
|         } | ||||
|         return target; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 拷贝list | ||||
|      * | ||||
|      * @param inList 输入list | ||||
|      * @param outClz 输出目标对象class | ||||
|      * @return 返回集合 | ||||
|      */ | ||||
|     public static <E, T> List<T> copyList(List<E> inList, Class<T> outClz) { | ||||
|         List<T> output = Lists.newArrayList(); | ||||
|         if (!CollectionUtils.isEmpty(inList)) { | ||||
|             for (E source : inList) { | ||||
|                 T target = BeanUtils.instantiate(outClz); | ||||
|                 BeanUtils.copyProperties(source, target); | ||||
|                 output.add(target); | ||||
|             } | ||||
|         } | ||||
|         return output; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取任意泛型类的指定属性值(暂只支持同时访问父类和子类属性,如果有多继承的话,还需要再修改) | ||||
|      * | ||||
|      * @param tList        list对象数据 | ||||
|      * @param field        要获取指定字段属性值对应的字段名 | ||||
|      * @param isSuperfield true->父类 false->子类 | ||||
|      * @return 字段属性值 | ||||
|      */ | ||||
|     public static <T> List<?> getFieldList(List<T> tList, String field, Boolean isSuperfield) { | ||||
|         if (StringUtils.isBlank(field)) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         // 拼接方法
 | ||||
|         field = | ||||
|                 new StringBuffer("get").append(field.substring(0, 1).toUpperCase()).append(field.substring(1)).toString(); | ||||
|         List<Object> idList = Lists.newArrayList(); | ||||
|         Method method = null; | ||||
|         try { | ||||
|             if (isSuperfield) { | ||||
|                 method = tList.get(0).getClass().getSuperclass().getMethod(field); | ||||
|             } else { | ||||
|                 method = tList.get(0).getClass().getMethod(field); | ||||
|             } | ||||
|         } catch (NoSuchMethodException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         try { | ||||
|             for (T t : tList) { | ||||
|                 Object s = method.invoke(t); | ||||
|                 idList.add(s); | ||||
|             } | ||||
|         } catch (NullPointerException | InvocationTargetException | IllegalAccessException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return idList; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取任意泛型类的指定属性值(暂只支持同时访问父类和子类属性,如果有多继承的话,还需要再修改) | ||||
|      * | ||||
|      * @param tList list对象数据 | ||||
|      * @param field 要获取指定字段属性值对应的字段名 | ||||
|      * @return 字段属性值 | ||||
|      */ | ||||
|     public static <T> List<?> getFieldList(List<Map<String, Object>> tList, String field) { | ||||
|         if (StringUtils.isBlank(field)) { | ||||
|             return null; | ||||
|         } | ||||
|         List<Object> idList = Lists.newArrayList(); | ||||
|         for (Map<String, Object> map : tList) { | ||||
|             idList.add(map.get(field)); | ||||
|         } | ||||
|         return idList; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 将Map转换为对象 (注:暂无法转换含嵌套对象的map数据源) | ||||
|      * | ||||
|      * @param map 需转换map | ||||
|      * @param clz 目标对象class | ||||
|      * @return T | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/11/27 18:39 | ||||
|      */ | ||||
|     public static <T> T mapToObj(Map<String, Object> map, Class<T> clz) { | ||||
|         return JSONUtil.toBean(JSONUtil.toJsonStr(map), clz); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 对象 转 map (通过反射获取类里面的值和名称) | ||||
|      * | ||||
|      * @param obj 对象 | ||||
|      * @return map | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/1/26 16:09 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Map<String, String> objToMapStr(Object obj) { | ||||
|         Map<String, String> map = Maps.newHashMap(); | ||||
|         Class<?> clazz = obj.getClass(); | ||||
|         for (Field field : clazz.getDeclaredFields()) { | ||||
|             field.setAccessible(true); | ||||
|             String fieldName = field.getName(); | ||||
|             Object value = field.get(obj); | ||||
|             map.put(fieldName, value == null ? "" : value.toString()); | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 对象 转 map (通过反射获取类里面的值和名称) | ||||
|      * | ||||
|      * @param obj 对象 | ||||
|      * @return map | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/1/26 16:09 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Map<String, Object> objToMap(Object obj) { | ||||
|         Map<String, Object> map = Maps.newHashMap(); | ||||
|         Class<?> clazz = obj.getClass(); | ||||
|         for (Field field : clazz.getDeclaredFields()) { | ||||
|             field.setAccessible(true); | ||||
|             String fieldName = field.getName(); | ||||
|             Object value = field.get(obj); | ||||
|             map.put(fieldName, value == null ? "" : value.toString()); | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,421 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import lombok.SneakyThrows; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| 
 | ||||
| import java.lang.management.ManagementFactory; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.time.ZoneId; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.Calendar; | ||||
| import java.util.Date; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 时间工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/8/18 12:57 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class MyDateUtil { | ||||
| 
 | ||||
|     public static final String GMT = "GMT+8"; | ||||
|     public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; | ||||
|     public static final String DATE_TIME_MS_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; | ||||
|     public static final String DATE_TIME_START_FORMAT = "yyyy-MM-dd 00:00:00"; | ||||
|     public static final String DATE_TIME_END_FORMAT = "yyyy-MM-dd 23:59:59"; | ||||
|     public static final String MINUTE_FORMAT = "yyyy-MM-dd HH:mm"; | ||||
|     public static final String HOUR_FORMAT = "yyyy-MM-dd HH"; | ||||
|     public static final String DATE_FORMAT = "yyyy-MM-dd"; | ||||
|     public static final String MONTH_FORMAT = "yyyy-MM"; | ||||
|     public static final String YEAR_FORMAT = "yyyy"; | ||||
|     public static final String MINUTE_ONLY_FORMAT = "mm"; | ||||
|     public static final String HOUR_ONLY_FORMAT = "HH"; | ||||
|     public static final String MINUTE_JAVA_AUTHOR_FORMAT = "yyyy/MM/dd HH:mm"; | ||||
|     public static final String DAY_FORMAT = "yyyy/MM/dd"; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取当前时间的字符串格式时间 -- 秒 | ||||
|      * | ||||
|      * @return 字符串格式时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     public static String nowStr() { | ||||
|         return new SimpleDateFormat(DATE_TIME_FORMAT).format(new Date()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取当前时间的字符串格式时间 -- 毫秒 | ||||
|      * | ||||
|      * @return 字符串格式时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     public static String nowMsStr() { | ||||
|         return new SimpleDateFormat(DATE_TIME_MS_FORMAT).format(new Date()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取当前时间字符串的指定格式时间 | ||||
|      * | ||||
|      * @param format 时间格式 | ||||
|      * @return 字符串格式时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     public static String nowStr(String format) { | ||||
|         return new SimpleDateFormat(format).format(new Date()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Date转Str | ||||
|      * | ||||
|      * @param date   时间 | ||||
|      * @param format 时间格式 | ||||
|      * @return 字符串时间类型 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     public static String dateToStr(Date date, String format) { | ||||
|         return new SimpleDateFormat(format).format(date); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Date转Str {@link MyDateUtil.DATE_TIME_FORMAT} | ||||
|      * | ||||
|      * @param date 时间 | ||||
|      * @return 字符串时间类型 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     public static String dateToStr(Date date) { | ||||
|         return new SimpleDateFormat(DATE_TIME_FORMAT).format(date); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Date 时间开始处理 (ex: Sat Sep 04 10:11:25 CST 2021 -> 2021-09-04 00:00:00) | ||||
|      * | ||||
|      * @param date 时间 | ||||
|      * @return 时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/9/4 10:12 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date dateToStartTime(Date date) { | ||||
|         String dateTime = dateToStr(date, DATE_TIME_START_FORMAT); | ||||
|         return strToDate(dateTime, DATE_TIME_FORMAT); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Date 时间结束处理 (ex: Sat Sep 04 10:11:25 CST 2021 -> 2021-09-04 23:59:59) | ||||
|      * | ||||
|      * @param date 时间 | ||||
|      * @return 时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/9/4 10:12 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date dateToEndTime(Date date) { | ||||
|         String dateTime = dateToStr(date, DATE_TIME_END_FORMAT); | ||||
|         return strToDate(dateTime, DATE_TIME_FORMAT); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Str(yyyy-MM-dd HH:mm:ss)转DateTime | ||||
|      * | ||||
|      * @param dateStr 字符串时间 | ||||
|      * @return Date时间类型 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/20 9:41 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date strToDateTime(String dateStr) { | ||||
|         return new SimpleDateFormat(DATE_TIME_FORMAT).parse(dateStr); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Str转Date | ||||
|      * | ||||
|      * @param dateStr 字符串时间 | ||||
|      * @param format  时间格式 | ||||
|      * @return Date时间类型 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date strToDate(String dateStr, String format) { | ||||
|         return new SimpleDateFormat(format).parse(dateStr); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Str转long(10位-秒级别) | ||||
|      * | ||||
|      * @param dateStr 字符串时间 | ||||
|      * @param format  时间格式 | ||||
|      * @return 秒 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:07 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static long strToLong(String dateStr, String format) { | ||||
|         return new SimpleDateFormat(format).parse(dateStr).getTime() / 1000; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取服务器启动时间 | ||||
|      */ | ||||
|     public static Date getServerStartDate() { | ||||
|         long time = ManagementFactory.getRuntimeMXBean().getStartTime(); | ||||
|         return new Date(time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 判断结束时间是否大于等于开始时间 | ||||
|      * | ||||
|      * @param startTime 开始时间 | ||||
|      * @param endTime   结束时间 | ||||
|      * @param format    时间格式 | ||||
|      * @return 判断结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/22 13:21 | ||||
|      */ | ||||
|     public static boolean verifyTime(String startTime, String endTime, String format) { | ||||
|         Date startDate = strToDate(startTime, format); | ||||
|         Date endDate = strToDate(endTime, format); | ||||
|         return endDate.getTime() >= startDate.getTime(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取今日开始时间 | ||||
|      * | ||||
|      * @return 今日开始时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/18 18:58 | ||||
|      */ | ||||
|     public static Date todayStartTime() { | ||||
|         LocalDateTime todayStartTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); | ||||
|         return Date.from(todayStartTime.atZone(ZoneId.systemDefault()).toInstant()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取今日开始时间 | ||||
|      * | ||||
|      * @return 今日开始时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/18 18:58 | ||||
|      */ | ||||
|     public static String todayStartTimeStr() { | ||||
|         DateTimeFormatter df = DateTimeFormatter.ofPattern(DATE_TIME_START_FORMAT); | ||||
|         LocalDateTime time = LocalDateTime.now(); | ||||
|         return df.format(time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取今日结束时间 | ||||
|      * | ||||
|      * @return 今日结束时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/18 18:58 | ||||
|      */ | ||||
|     public static Date todayEndTime() { | ||||
|         LocalDateTime todayStartTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); | ||||
|         return Date.from(todayStartTime.atZone(ZoneId.systemDefault()).toInstant()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取今日结束时间 | ||||
|      * | ||||
|      * @return 今日结束时间 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/8/18 18:58 | ||||
|      */ | ||||
|     public static String todayEndTimeStr() { | ||||
|         DateTimeFormatter df = DateTimeFormatter.ofPattern(DATE_TIME_END_FORMAT); | ||||
|         LocalDateTime time = LocalDateTime.now(); | ||||
|         return df.format(time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 计算两时间分钟差 | ||||
|      * | ||||
|      * @param startTimeStr 开始时间 ex: 2020-09-09 10:00:10 | ||||
|      * @param endTimeStr   结束时间 ex: 2020-09-09 10:30:10 | ||||
|      * @return 分钟差 ex:30 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/9/9 10:21 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static int diffMinute(String startTimeStr, String endTimeStr) { | ||||
|         SimpleDateFormat simpleFormat = new SimpleDateFormat(MINUTE_FORMAT); | ||||
|         long startTime = simpleFormat.parse(startTimeStr).getTime(); | ||||
|         long endTime = simpleFormat.parse(endTimeStr).getTime(); | ||||
|         return (int) ((endTime - startTime) / (1000 * 60)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 计算两时间毫秒差 | ||||
|      * | ||||
|      * @param startTime 开始时间 | ||||
|      * @param endTime   结束时间 | ||||
|      * @return 毫秒差 ex:30 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/9/9 10:21 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static long diffMillisecond(Date startTime, Date endTime) { | ||||
|         return endTime.getTime() - startTime.getTime(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 计算两个时间差 | ||||
|      * | ||||
|      * @param startDate 开始时间 | ||||
|      * @param endDate   结束时间 | ||||
|      * @return 时间差 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/7/23 10:17 | ||||
|      */ | ||||
|     public static String getDateDiffStr(Date startDate, Date endDate) { | ||||
|         long nd = 1000 * 24 * 60 * 60; | ||||
|         long nh = 1000 * 60 * 60; | ||||
|         long nm = 1000 * 60; | ||||
|         // long ns = 1000;
 | ||||
|         // 获得两个时间的毫秒时间差异
 | ||||
|         long diff = endDate.getTime() - startDate.getTime(); | ||||
|         // 计算差多少天
 | ||||
|         long day = diff / nd; | ||||
|         // 计算差多少小时
 | ||||
|         long hour = diff % nd / nh; | ||||
|         // 计算差多少分钟
 | ||||
|         long min = diff % nd % nh / nm; | ||||
|         // 计算差多少秒//输出结果
 | ||||
|         // long sec = diff % nd % nh % nm / ns;
 | ||||
|         return day + "天" + hour + "小时" + min + "分钟"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 在当前时间上加指定时间 | ||||
|      * | ||||
|      * @param timeUnit 时间单位 | ||||
|      * @param time     时间 | ||||
|      * @return 结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/10/18 16:57 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date addTime(TimeUnit timeUnit, int time) { | ||||
|         return addAndSubTime(timeUnit, +time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 在当前时间上减指定时间 | ||||
|      * | ||||
|      * @param timeUnit 时间单位 | ||||
|      * @param time     时间 | ||||
|      * @return 结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/10/18 16:57 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date subTime(TimeUnit timeUnit, int time) { | ||||
|         return addAndSubTime(timeUnit, -time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 在当前时间上 加或减 指定时间 | ||||
|      * | ||||
|      * @param timeUnit 时间单位 | ||||
|      * @param time     时间 | ||||
|      * @return 结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/10/18 16:57 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date addAndSubTime(TimeUnit timeUnit, int time) { | ||||
|         return timeAddAndSubTime(new Date(), timeUnit, time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 在指定时间上 加或减 指定时间 | ||||
|      * | ||||
|      * @param timeUnit 时间单位 | ||||
|      * @param time     时间 | ||||
|      * @return 结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2021/10/18 16:57 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static Date timeAddAndSubTime(Date sourceDate, TimeUnit timeUnit, int time) { | ||||
|         Calendar calendar = Calendar.getInstance(); | ||||
|         calendar.setTime(sourceDate); | ||||
|         switch (timeUnit) { | ||||
|             case SECONDS: | ||||
|                 calendar.add(Calendar.SECOND, time); | ||||
|                 break; | ||||
|             case MINUTES: | ||||
|                 calendar.add(Calendar.MINUTE, time); | ||||
|                 break; | ||||
|             case HOURS: | ||||
|                 calendar.add(Calendar.HOUR, time); | ||||
|                 break; | ||||
|             case DAYS: | ||||
|                 calendar.add(Calendar.DATE, time); | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new Exception("暂不支持该时间类型!"); | ||||
|         } | ||||
|         return calendar.getTime(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         Date nowDateTime = new Date(); | ||||
|         String dateStr = dateToStr(nowDateTime, DATE_TIME_FORMAT); | ||||
|         System.out.println(nowStr(YEAR_FORMAT)); | ||||
|         boolean result = verifyTime("2020-08-18 00:00:00", "2020-08-17 23:59:59", DATE_TIME_FORMAT); | ||||
|         System.out.println(result); | ||||
| 
 | ||||
|         Date todayStartTime = todayStartTime(); | ||||
|         Date todayEndTime = todayEndTime(); | ||||
| 
 | ||||
|         String todayStartTimeStr = todayStartTimeStr(); | ||||
|         String todayEndTimeStr = todayEndTimeStr(); | ||||
| 
 | ||||
|         Date date = strToDateTime("2020-08-18 00:00:10"); | ||||
| 
 | ||||
|         Long aLong = strToLong("2020-08-18 00:00:10", DATE_TIME_FORMAT); | ||||
| 
 | ||||
|         Date dateTimeStartFormat = dateToStartTime(nowDateTime); | ||||
|         Date dateTimeEndFormat = dateToEndTime(nowDateTime); | ||||
| 
 | ||||
|         int diffMinute = diffMinute("2020-09-09 10:00:10", "2020-09-09 10:30:10"); | ||||
|         long diffMillisecond = diffMillisecond(todayStartTime, todayEndTime); | ||||
| 
 | ||||
|         log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.SECONDS, 20), DATE_TIME_FORMAT)); | ||||
|         log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.MINUTES, 10), DATE_TIME_FORMAT)); | ||||
|         log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.HOURS, 10), DATE_TIME_FORMAT)); | ||||
|         log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.DAYS, 10), DATE_TIME_FORMAT)); | ||||
| 
 | ||||
|         log.info("--------------------------------"); | ||||
| 
 | ||||
|         log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.SECONDS, 20), DATE_TIME_FORMAT)); | ||||
|         log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.MINUTES, 10), DATE_TIME_FORMAT)); | ||||
|         log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.HOURS, 10), DATE_TIME_FORMAT)); | ||||
|         log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.DAYS, 10), DATE_TIME_FORMAT)); | ||||
| 
 | ||||
|         log.info("--------------------------------"); | ||||
| 
 | ||||
|         log.info("nowTime:{} timeAddAndsubTime: {}", nowStr(), dateToStr(timeAddAndSubTime(todayStartTime, TimeUnit.MINUTES, 10), DATE_TIME_FORMAT)); | ||||
| 
 | ||||
|         log.info("当前时间到今天开始时间差:{}", MyDateUtil.getDateDiffStr(MyDateUtil.todayStartTime(), new Date())); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,177 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import cn.hutool.core.util.ZipUtil; | ||||
| import lombok.SneakyThrows; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.InputStream; | ||||
| import java.nio.charset.Charset; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 文件工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/8/27 19:21 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class MyFileUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 多文件或目录压缩:将`srcPath`目录以及其目录下的所有文件目录打包到`zipPath`+`suffixFileName`文件中 【采用hutool工具类进行打包文件】 | ||||
|      * | ||||
|      * @param srcPath        需打包的源目录 | ||||
|      * @param zipPath        打包后的路径+文件后缀名 | ||||
|      * @param isWithSrcDir   是否带目录显示 (true:表示带目录显示) | ||||
|      * @param isDeleteSrcZip 是否删除源目录 | ||||
|      * @return java.lang.String | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/27 19:25 | ||||
|      */ | ||||
|     public static File zip(String srcPath, String zipPath, boolean isWithSrcDir, boolean isDeleteSrcZip) { | ||||
|         log.debug("【压缩文件】 源目录路径: 【{}】 打包后的路径+文件后缀名: 【{}】", srcPath, zipPath); | ||||
|         File zipFile = ZipUtil.zip(srcPath, zipPath, isWithSrcDir); | ||||
|         // 删除目录 -> 保证下次生成文件的时候不会累计上次留下的文件
 | ||||
|         if (isDeleteSrcZip) { | ||||
|             MyFileUtil.deleteFileOrFolder(srcPath); | ||||
|         } | ||||
|         return zipFile; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据路径删除指定的目录或文件,无论存在与否 | ||||
|      * | ||||
|      * @param fullFileOrDirPath 要删除的目录或文件 | ||||
|      * @return 删除成功返回 true,否则返回 false | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/5 20:56 | ||||
|      */ | ||||
|     public static boolean deleteFileOrFolder(String fullFileOrDirPath) { | ||||
|         return FileUtil.del(fullFileOrDirPath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据路径创建文件 | ||||
|      * | ||||
|      * @param fullFilePath 文件生成路径 | ||||
|      * @return 文件信息 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/8 21:41 | ||||
|      */ | ||||
|     public static File touch(String fullFilePath) { | ||||
|         return FileUtil.touch(fullFilePath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 解压 | ||||
|      * | ||||
|      * @param inputStream 流 | ||||
|      * @param zipFilePath zip文件路径 | ||||
|      * @param outFileDir  解压后的目录路径 | ||||
|      * @param isDeleteZip 是否删除源zip文件 | ||||
|      * @return 解压后的文件File信息 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/5 20:50 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static File unzip(InputStream inputStream, String zipFilePath, String outFileDir, boolean isDeleteZip) { | ||||
|         log.debug("【解压文件】 zip文件路径: 【{}】 解压后的目录路径: 【{}】", zipFilePath, outFileDir); | ||||
|         // zip压缩文件
 | ||||
| //        File zipFile = FileUtil.newFile(zipFilePath);
 | ||||
|         // 写入文件
 | ||||
| //        FileUtils.copyInputStreamToFile(inputStream, zipFile);
 | ||||
|         // 编码方式 "UTF-8" 、"GBK" 【注: gbk编码才能解决报错: java.lang.IllegalArgumentException: MALFORMED】
 | ||||
|         File outFile = ZipUtil.unzip(zipFilePath, outFileDir, Charset.forName("GBK")); | ||||
|         // 删除zip -> 保证下次解压后的文件数据不会累计上次解压留下的文件
 | ||||
|         if (isDeleteZip) { | ||||
|             MyFileUtil.deleteFileOrFolder(zipFilePath); | ||||
|         } | ||||
|         return outFile; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 读取文件内容 | ||||
|      * | ||||
|      * @param file 文件数据 | ||||
|      * @return 文件内容 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/5 23:00 | ||||
|      */ | ||||
|     public static String readFileContent(File file) { | ||||
|         return FileUtil.readUtf8String(file); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 读取文件内容 | ||||
|      * | ||||
|      * @param filePath 文件路径 | ||||
|      * @return 文件内容 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/5 23:00 | ||||
|      */ | ||||
|     public static String readFileContent(String filePath) { | ||||
|         return FileUtil.readUtf8String(filePath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 读取文件数据 | ||||
|      * | ||||
|      * @param filePath 文件路径 | ||||
|      * @return 文件字节码 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/9/5 23:00 | ||||
|      */ | ||||
|     public static byte[] readBytes(String filePath) { | ||||
|         return FileUtil.readBytes(filePath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 写入文件内容 | ||||
|      * | ||||
|      * @param fileContent 文件内容 | ||||
|      * @param filePath    文件路径 | ||||
|      * @return 文件信息 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/11/17 21:38 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static File writeFileContent(String fileContent, String filePath) { | ||||
|         return FileUtil.writeUtf8String(fileContent, filePath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 字节码写入文件 | ||||
|      * | ||||
|      * @param data     字节码 | ||||
|      * @param filePath 文件路径 | ||||
|      * @return 文件信息 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/11/24 14:36 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static File writeFileContent(byte[] data, String filePath) { | ||||
|         return FileUtil.writeBytes(data, filePath); | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         try { | ||||
|             String filePath = "E:\\IT_zhengqing\\code\\me-workspace\\最新代码生成器\\code-api\\document\\import\\blog.zip"; | ||||
|             String filePathX = "E:\\IT_zhengqing\\code\\me-workspace\\最新代码生成器\\code-api\\document\\import"; | ||||
|             // File file =
 | ||||
|             // FileUtil.newFile(filePath);
 | ||||
|             // InputStream fileInputStream = new FileInputStream(file);
 | ||||
|             File unzip = ZipUtil.unzip(filePath, filePathX); | ||||
|             System.out.println(unzip); | ||||
| 
 | ||||
|             String fileContent = FileUtil.readUtf8String(filePath); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,118 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import com.google.common.base.CaseFormat; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| 
 | ||||
| import java.util.StringJoiner; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 字符串工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya <br/> | ||||
|  * @date 2019/9/14 0014$ 19:49$ <br/> | ||||
|  */ | ||||
| public class MyStringUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 将驼峰式命名的字符串转换为下划线大写方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。</br> | ||||
|      * 例如:HelloWorld->HELLO_WORLD | ||||
|      * | ||||
|      * @param name 转换前的驼峰式命名的字符串 | ||||
|      * @return 转换后下划线大写方式命名的字符串 | ||||
|      */ | ||||
|     public static String humpToMark(String name) { | ||||
|         StringBuilder result = new StringBuilder(); | ||||
|         if (name != null && name.length() > 0) { | ||||
|             // 将第一个字符处理成大写
 | ||||
|             result.append(name.substring(0, 1).toUpperCase()); | ||||
|             // 循环处理其余字符
 | ||||
|             for (int i = 1; i < name.length(); i++) { | ||||
|                 String s = name.substring(i, i + 1); | ||||
|                 // 在大写字母前添加下划线
 | ||||
|                 if (s.equals(s.toUpperCase()) && !Character.isDigit(s.charAt(0))) { | ||||
|                     result.append("_"); | ||||
|                 } | ||||
|                 // 其他字符直接转成大写
 | ||||
|                 result.append(s.toUpperCase()); | ||||
|             } | ||||
|         } | ||||
|         return result.toString(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 将字符串转换为驼峰式 </br> | ||||
|      * 例如:HELLO.WORLD -> HelloWorld | ||||
|      * <p> | ||||
|      * System.out.println(CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, "test-data"));//testData
 | ||||
|      * System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "test_data"));//testData
 | ||||
|      * System.out.println(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "test_data"));//TestData
 | ||||
|      * <p> | ||||
|      * System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "testdata"));//testdata
 | ||||
|      * System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "TestData"));//test_data
 | ||||
|      * System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, "testData"));//test-data
 | ||||
|      * | ||||
|      * @param str  转换前的字符串 | ||||
|      * @param mark 以xx符号分割 ex: `,` `_` `-` `.` | ||||
|      * @return 转换后的驼峰式命名的字符串 | ||||
|      */ | ||||
|     public static String strToHumpByMark(String str, String mark) { | ||||
|         // 替换指定的符号mark为`_`
 | ||||
|         String replaceStr = StringUtils.replace(str, mark, "_"); | ||||
|         // 再使用谷歌开发工具包转驼峰命名 ex:test_data -> TestData
 | ||||
|         String result = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, replaceStr); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库表名/表字段 转 首字母小写驼峰命名 ex:test_data -> testData | ||||
|      * | ||||
|      * @param dbStr 数据库表名/表字段 | ||||
|      * @return 驼峰命名字符串 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/11/15 17:38 | ||||
|      */ | ||||
|     public static String dbStrToHumpLower(String dbStr) { | ||||
|         // 使用谷歌开发工具包转驼峰命名
 | ||||
|         return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, dbStr); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库表名/表字段 转 首字母大写驼峰命名 ex:test_data -> TestData | ||||
|      * | ||||
|      * @param dbStr 数据库表名/表字段 | ||||
|      * @return 驼峰命名字符串 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/11/15 17:38 | ||||
|      */ | ||||
|     public static String dbStrToHumpUpper(String dbStr) { | ||||
|         // 使用谷歌开发工具包转驼峰命名
 | ||||
|         return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, dbStr); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取表名缩写 | ||||
|      * | ||||
|      * @param tableName 表名 | ||||
|      * @return 缩写 | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/12/12 15:29 | ||||
|      */ | ||||
|     public static String tableNameToAbbr(String tableName) { | ||||
|         StringJoiner sj = new StringJoiner(""); | ||||
|         String[] strArray = tableName.split("_"); | ||||
|         for (String str : strArray) { | ||||
|             sj.add(str.substring(0, 1)); | ||||
|         } | ||||
|         return sj.toString(); | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         String str = dbStrToHumpLower("t_sys_user"); | ||||
|         String str2 = dbStrToHumpUpper("t_sys_user"); | ||||
|         String str3 = tableNameToAbbr("t_sys_user"); | ||||
|         System.out.println(str3); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,55 @@ | ||||
| package com.zhengqing.common.base.util; | ||||
| 
 | ||||
| import org.springframework.core.LocalVariableTableParameterNameDiscoverer; | ||||
| import org.springframework.expression.EvaluationContext; | ||||
| import org.springframework.expression.ExpressionParser; | ||||
| import org.springframework.expression.spel.standard.SpelExpressionParser; | ||||
| import org.springframework.expression.spel.support.StandardEvaluationContext; | ||||
| 
 | ||||
| import java.lang.reflect.Method; | ||||
| 
 | ||||
| /** | ||||
|  * spring El表达式 工具类 | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/4/12 0:40 | ||||
|  */ | ||||
| public class SpringElUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 表达式解析器 | ||||
|      */ | ||||
|     private static final ExpressionParser PARSER = new SpelExpressionParser(); | ||||
| 
 | ||||
|     /** | ||||
|      * 获取方法的参数名 | ||||
|      */ | ||||
|     private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); | ||||
| 
 | ||||
|     /** | ||||
|      * 解析spring EL表达式 | ||||
|      * | ||||
|      * @param key    表达式 | ||||
|      * @param method 方法 | ||||
|      * @param args   方法参数 | ||||
|      * @return 结果 | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/1/18 14:35 | ||||
|      */ | ||||
|     public static String parse(String key, Method method, Object[] args) { | ||||
|         // 获取方法的参数名
 | ||||
|         String[] paramNameArray = DISCOVERER.getParameterNames(method); | ||||
|         if (paramNameArray == null) { | ||||
|             return ""; | ||||
|         } | ||||
|         // 表达式的上下文
 | ||||
|         EvaluationContext context = new StandardEvaluationContext(); | ||||
|         for (int i = 0; i < paramNameArray.length; i++) { | ||||
|             // 为了让表达式可以访问该对象, 先把对象放到上下文中
 | ||||
|             context.setVariable(paramNameArray[i], args[i]); | ||||
|         } | ||||
|         return PARSER.parseExpression(key).getValue(context, String.class); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <parent> | ||||
|         <artifactId>common</artifactId> | ||||
|         <groupId>com.zhengqing</groupId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <artifactId>core</artifactId> | ||||
| 
 | ||||
|     <name>${project.artifactId}</name> | ||||
|     <version>${revision}</version> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <properties> | ||||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>base</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>auth</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>log</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>swagger</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>web</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>db</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>redis</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>file</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
| 
 | ||||
|         <!-- AOP依赖 --> | ||||
|         <!--        <dependency>--> | ||||
|         <!--            <groupId>org.springframework.boot</groupId>--> | ||||
|         <!--            <artifactId>spring-boot-starter-aop</artifactId>--> | ||||
|         <!--        </dependency>--> | ||||
| 
 | ||||
| 
 | ||||
|     </dependencies> | ||||
| 
 | ||||
| </project> | ||||
| @ -0,0 +1,32 @@ | ||||
| package com.zhengqing.common.core.api; | ||||
| 
 | ||||
| import com.zhengqing.common.base.context.SysUserContext; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * Controller基类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/8/17 0017 19:53 | ||||
|  */ | ||||
| @Slf4j | ||||
| @RestController | ||||
| //@Api(tags = "base接口")
 | ||||
| public class BaseController { | ||||
| 
 | ||||
|     /** | ||||
|      * 获取当前登录人ID | ||||
|      * | ||||
|      * @return 当前登录人ID | ||||
|      * @author zhengqingya | ||||
|      * @date 2020/8/30 15:41 | ||||
|      */ | ||||
|     protected Integer appGetCurrentUserId() { | ||||
|         return SysUserContext.getUserId(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,89 @@ | ||||
| package com.zhengqing.common.core.aspect; | ||||
| 
 | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import com.zhengqing.common.base.context.SysUserContext; | ||||
| import com.zhengqing.common.web.util.ServletUtil; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Before; | ||||
| import org.aspectj.lang.annotation.Pointcut; | ||||
| import org.aspectj.lang.reflect.MethodSignature; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 日志切面 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/8/1 19:09 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| @Component | ||||
| @ConditionalOnProperty( | ||||
|         value = {"smallboot.api-log"}, | ||||
|         havingValue = "true", | ||||
|         // true表示缺少此配置属性时也会加载该bean
 | ||||
|         matchIfMissing = true | ||||
| ) | ||||
| public class ApiLogAspect { | ||||
| 
 | ||||
|     /** | ||||
|      * 配置织入点 | ||||
|      */ | ||||
|     @Pointcut("execution(* com.zhengqing.*..*.*Controller.*(..))") | ||||
|     public void logPointCut() { | ||||
|     } | ||||
| 
 | ||||
|     @Before("logPointCut()") | ||||
|     public void doAround(JoinPoint joinPoint) throws Throwable { | ||||
|         HttpServletRequest request = ServletUtil.getRequest(); | ||||
|         if (request != null) { | ||||
|             Integer userId = SysUserContext.getUserId(); | ||||
|             String username = SysUserContext.getUsername(); | ||||
| 
 | ||||
|             // 从切面织入点处通过反射机制获取织入点处的方法
 | ||||
|             MethodSignature signature = (MethodSignature) joinPoint.getSignature(); | ||||
|             // 获取切入点所在的方法
 | ||||
|             ApiOperation apiOperation = signature.getMethod().getAnnotation(ApiOperation.class); | ||||
| 
 | ||||
| //            log.debug("========================== ↓↓↓↓↓↓ 《ApiLogAspect》 Start... ↓↓↓↓↓↓ ==========================");
 | ||||
| //            log.debug("《ApiLogAspect》 controller method: {}",
 | ||||
| //                    signature.getDeclaringTypeName() + "." + signature.getName());
 | ||||
| //            log.debug("《ApiLogAspect》 controller method description: {}", apiOperation.value());
 | ||||
| //            log.debug("《ApiLogAspect》 operatorId: {}", userId);
 | ||||
| //            log.debug("《ApiLogAspect》 operatorName: {}", username);
 | ||||
| //            log.debug("《ApiLogAspect》 request header: {}", request.getHeader(SwaggerConstant.REQUEST_HEADER_AUTHORIZATION));
 | ||||
| //            log.debug("《ApiLogAspect》 request ip: {}", request.getRemoteAddr());
 | ||||
| //            log.debug("《ApiLogAspect》 request url: {}", request.getRequestURL().toString());
 | ||||
| //            log.debug("《ApiLogAspect》 request http method: {}", request.getMethod());
 | ||||
| //            log.debug("《ApiLogAspect》 request params: {}", this.getRequestValue(request));
 | ||||
| //            log.debug("========================== ↑↑↑↑↑↑ 《ApiLogAspect》 End... ↑↑↑↑↑↑ ==========================");
 | ||||
| 
 | ||||
|             log.debug("[{}] [{}] 请求参数:{}", | ||||
|                     request.getMethod(), | ||||
|                     signature.getDeclaringTypeName() + "." + signature.getName(), | ||||
|                     this.getRequestValue(request) | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取请求的参数 | ||||
|      */ | ||||
|     private String getRequestValue(HttpServletRequest request) { | ||||
|         Map<String, String[]> map = request.getParameterMap(); | ||||
|         String params = JSONUtil.toJsonStr(map); | ||||
|         return StringUtils.substring(params, 0, 2000); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| package com.zhengqing.common.core.aspect; | ||||
| 
 | ||||
| import com.zhengqing.common.base.context.SysUserContext; | ||||
| import com.zhengqing.common.base.model.dto.BaseDTO; | ||||
| import com.zhengqing.common.core.custom.parameter.ParamCheck; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Before; | ||||
| import org.aspectj.lang.annotation.Pointcut; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * Controller 切面 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/1/9 17:47 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| @Component | ||||
| public class ControllerAspect { | ||||
| 
 | ||||
|     /** | ||||
|      * 配置织入点 | ||||
|      */ | ||||
|     @Pointcut("execution(* com.zhengqing.*..*.*Controller.*(..))") | ||||
|     public void controllerPointCut() { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Before增强:在目标方法被执行的时候织入增强 | ||||
|      * <p> | ||||
|      * Controller所有方法为切入点 | ||||
|      */ | ||||
|     @Before("controllerPointCut()") | ||||
|     public void controllerPointCut(JoinPoint joinPoint) { | ||||
|         // 参数处理
 | ||||
|         Object[] paramObjArray = joinPoint.getArgs(); | ||||
|         // 遍历所有传入参数,赋值
 | ||||
|         for (Object paramObj : paramObjArray) { | ||||
|             // dto参数处理
 | ||||
|             if (paramObj instanceof BaseDTO) { | ||||
|                 BaseDTO baseDTO = (BaseDTO) paramObj; | ||||
|                 baseDTO.setCurrentUserId(SysUserContext.getUserId()); | ||||
|                 baseDTO.setCurrentUsername(SysUserContext.getUsername()); | ||||
|             } | ||||
| 
 | ||||
|             // 参数校验处理
 | ||||
|             if (paramObj instanceof ParamCheck) { | ||||
|                 ParamCheck paramCheck = (ParamCheck) paramObj; | ||||
|                 paramCheck.checkParam(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| package com.zhengqing.common.core.aspect; | ||||
| 
 | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import com.zhengqing.common.base.exception.ParameterException; | ||||
| import com.zhengqing.common.swagger.constant.SwaggerConstant; | ||||
| import com.zhengqing.common.web.util.ServletUtil; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Before; | ||||
| import org.aspectj.lang.annotation.Pointcut; | ||||
| import org.springframework.core.annotation.Order; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * Mapper分页参数注入切面 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/8/1 18:18 | ||||
|  */ | ||||
| @Aspect | ||||
| @Order(2) | ||||
| @Component | ||||
| public class MapperAspect { | ||||
| 
 | ||||
|     private static final int DEFAULT_PAGE_NUM = 1; | ||||
|     private static final int DEFAULT_PAGE_SIZE = 10; | ||||
| 
 | ||||
|     /** | ||||
|      * 配置织入点 | ||||
|      */ | ||||
|     @Pointcut("execution(* com.zhengqing.*..*.*Mapper.*(..))") | ||||
|     public void mapperPointCut() { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Before增强:在目标方法被执行的时候织入增强 | ||||
|      * <p> | ||||
|      * Mapper所有方法作为切入点 | ||||
|      */ | ||||
|     @Before("mapperPointCut()") | ||||
|     public void mapperPointCut(JoinPoint joinPoint) { | ||||
|         Object[] paramObjArray = joinPoint.getArgs(); | ||||
|         int pageNum = ServletUtil.getParameterToInt(SwaggerConstant.PAGE_NUM, DEFAULT_PAGE_NUM); | ||||
|         int pageSize = ServletUtil.getParameterToInt(SwaggerConstant.PAGE_SIZE, DEFAULT_PAGE_SIZE); | ||||
|         // 遍历所有传入参数,赋值
 | ||||
|         for (Object paramObj : paramObjArray) { | ||||
|             if (paramObj instanceof IPage) { | ||||
|                 if (pageNum < 1 || pageSize < 1) { | ||||
|                     throw new ParameterException("传递的分页参数有误!"); | ||||
|                 } | ||||
|                 IPage<?> page = ((IPage<?>) paramObj); | ||||
|                 page.setCurrent(pageNum); | ||||
|                 page.setSize(pageSize); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package com.zhengqing.common.core.aspect.config; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 定义BeanPostProcessor 需要使用的标识接口 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 即我们自定义的BeanPostProcessor (InjectBeanSelfProcessor)如果发现我们的Bean是实现了该标识接口就调用setSelf注入代理对象 | ||||
|  * @date 2021/1/9 1:10 | ||||
|  */ | ||||
| public interface BeanSelfAware { | ||||
|     void setSelf(Object proxyBean); | ||||
| } | ||||
| @ -0,0 +1,59 @@ | ||||
| package com.zhengqing.common.core.aspect.config; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.aop.support.AopUtils; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.context.ApplicationContextAware; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 通过BeanPostProcessor 在目标对象中注入代理对象 -> 解决Spring AOP不拦截对象内部调用方法问题 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/1/9 0:55 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Component | ||||
| public class InjectBeanSelfProcessor implements BeanPostProcessor, ApplicationContextAware { | ||||
| 
 | ||||
|     private ApplicationContext applicationContext; | ||||
| 
 | ||||
|     /** | ||||
|      * 1、注入ApplicationContext | ||||
|      */ | ||||
|     @Override | ||||
|     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { | ||||
|         this.applicationContext = applicationContext; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { | ||||
|         // 2、如果Bean没有实现BeanSelfAware标识接口 跳过
 | ||||
|         if (!(bean instanceof BeanSelfAware)) { | ||||
|             return bean; | ||||
|         } | ||||
| 
 | ||||
|         log.debug("inject proxy:【{}】", bean.getClass()); | ||||
| 
 | ||||
|         if (AopUtils.isAopProxy(bean)) { | ||||
|             // 3、如果当前对象是AOP代理对象,直接注入
 | ||||
|             ((BeanSelfAware) bean).setSelf(bean); | ||||
|         } else { | ||||
|             // 4、如果当前对象不是AOP代理,则通过context.getBean(beanName)获取代理对象并注入
 | ||||
|             // 此种方式不适合解决prototype Bean的代理对象注入
 | ||||
|             ((BeanSelfAware) bean).setSelf(this.applicationContext.getBean(beanName)); | ||||
|         } | ||||
|         return bean; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|         return bean; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.zhengqing.common.core.config; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.boot.CommandLineRunner; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 服务初始化之后,执行方法 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/5/22 19:29 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Component | ||||
| public abstract class AppCommonRunner implements CommandLineRunner { | ||||
| 
 | ||||
|     public void appRun() { | ||||
|         log.info("《AppCommonRunner》: 服务初始化之后,执行方法 start..."); | ||||
| 
 | ||||
|         log.info("《AppCommonRunner》: 服务初始化之后,执行方法 end..."); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| package com.zhengqing.common.core.config; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 公共基础配置参数 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/8/19 9:07 | ||||
|  */ | ||||
| @Data | ||||
| public class CommonProperty { | ||||
| 
 | ||||
|     /** | ||||
|      * ip | ||||
|      */ | ||||
|     private String ip; | ||||
| 
 | ||||
|     /** | ||||
|      * MySQL参数 | ||||
|      */ | ||||
|     private Mysql mysql; | ||||
| 
 | ||||
|     @Data | ||||
|     public static class Mysql { | ||||
|         private MysqlConn master; | ||||
|         private MysqlConn dbTest; | ||||
|     } | ||||
| 
 | ||||
|     @Data | ||||
|     public static class MysqlConn { | ||||
|         private String ip; | ||||
|         private String port; | ||||
|         private String username; | ||||
|         private String password; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.zhengqing.common.core.config; | ||||
| 
 | ||||
| import com.zhengqing.common.core.config.interceptor.HandlerInterceptorForTokenUser; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 注册拦截器 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description ex: 租户ID | ||||
|  * @date 2021/1/13 14:41 | ||||
|  */ | ||||
| @Configuration | ||||
| public class WebAppConfig implements WebMvcConfigurer { | ||||
| 
 | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) { | ||||
|         // 可添加多个
 | ||||
|         registry.addInterceptor(new HandlerInterceptorForTokenUser()).addPathPatterns("/**"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package com.zhengqing.common.core.config.interceptor; | ||||
| 
 | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import com.zhengqing.common.auth.model.bo.JwtUserBO; | ||||
| import com.zhengqing.common.base.constant.AppConstant; | ||||
| import com.zhengqing.common.base.context.JwtCustomUserContext; | ||||
| import com.zhengqing.common.base.context.SysUserContext; | ||||
| import com.zhengqing.common.base.context.UmsUserContext; | ||||
| import com.zhengqing.common.core.config.WebAppConfig; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
| import org.springframework.web.servlet.ModelAndView; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 拦截器 -- token用户信息 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 注册使用参考 {@link WebAppConfig} | ||||
|  * @date 2022/1/10 16:28 | ||||
|  */ | ||||
| public class HandlerInterceptorForTokenUser implements HandlerInterceptor { | ||||
| 
 | ||||
|     /** | ||||
|      * 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理 | ||||
|      * {@link com.zhengqing.gateway.filter.AuthFilter#filter } | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | ||||
|         String token = request.getHeader(AppConstant.REQUEST_HEADER_TOKEN); | ||||
|         if (StringUtils.isBlank(token)) { | ||||
|             return true; | ||||
|         } | ||||
|         JwtUserBO jwtUserBO = JSONUtil.toBean(StpUtil.getLoginId().toString(), JwtUserBO.class); | ||||
|         SysUserContext.setUserId(jwtUserBO.getUserId()); | ||||
|         SysUserContext.setUsername(jwtUserBO.getUserName()); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 在业务处理器处理请求执行完成后,生成视图之前执行。 | ||||
|      * 后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView | ||||
|      */ | ||||
|     @Override | ||||
|     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { | ||||
|         HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面) | ||||
|      */ | ||||
|     @Override | ||||
|     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { | ||||
|         HandlerInterceptor.super.afterCompletion(request, response, handler, ex); | ||||
|         SysUserContext.remove(); | ||||
|         UmsUserContext.remove(); | ||||
|         JwtCustomUserContext.remove(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,71 @@ | ||||
| package com.zhengqing.common.core.config.threadpool; | ||||
| 
 | ||||
| import com.zhengqing.common.base.constant.ThreadPoolConstant; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||||
| import org.springframework.scheduling.annotation.EnableAsync; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
| 
 | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 原生(Spring)异步任务线程池装配类 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 配置默认的线程池,重写spring默认线程池的方式使用的时候,只需要加@Async注解就可以,不用去声明线程池类。 | ||||
|  * @date 2021/5/27 10:31 | ||||
|  */ | ||||
| @Slf4j | ||||
| @EnableAsync | ||||
| @Configuration | ||||
| public class NativeAsyncTaskExecutePool implements AsyncConfigurer { | ||||
| 
 | ||||
|     @Override | ||||
|     public Executor getAsyncExecutor() { | ||||
|         ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); | ||||
|         // 核心线程池大小
 | ||||
|         threadPoolTaskExecutor.setCorePoolSize(5); | ||||
|         // 最大线程数
 | ||||
|         threadPoolTaskExecutor.setMaxPoolSize(10); | ||||
|         // 队列容量
 | ||||
|         threadPoolTaskExecutor.setQueueCapacity(200); | ||||
|         // 活跃时间 60s
 | ||||
|         threadPoolTaskExecutor.setKeepAliveSeconds(60); | ||||
|         // 线程名字前缀
 | ||||
|         threadPoolTaskExecutor | ||||
|                 .setThreadNamePrefix(ThreadPoolConstant.SPRING_DEFAULT_THREAD_NAME_PREFIX); | ||||
|         // 设置在关闭线程池时是否等待任务完成
 | ||||
|         threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); | ||||
|         // 允许核心线程超时
 | ||||
|         threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true); | ||||
|         // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
 | ||||
|         threadPoolTaskExecutor.setAwaitTerminationSeconds(60); | ||||
|         // 修改拒绝策略为使用当前线程执行
 | ||||
|         // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
 | ||||
|         // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
 | ||||
|         threadPoolTaskExecutor | ||||
|                 .setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | ||||
|         //初始化线程池
 | ||||
|         threadPoolTaskExecutor.initialize(); | ||||
|         return threadPoolTaskExecutor; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 异步任务中异常处理 (注:只能捕获到@Async下无返回值的方法) | ||||
|      */ | ||||
|     @Override | ||||
|     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||||
|         return new AsyncUncaughtExceptionHandler() { | ||||
|             @Override | ||||
|             public void handleUncaughtException(Throwable throwable, Method method, | ||||
|                                                 Object... objects) { | ||||
|                 log.error("exception method: 【{}】", method); | ||||
|                 log.error("exception msg: ", throwable); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package com.zhengqing.common.core.config.threadpool; | ||||
| 
 | ||||
| import com.zhengqing.common.base.constant.ThreadPoolConstant; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
| 
 | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 自定义线程池配置 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/5/27 10:03 | ||||
|  */ | ||||
| @Configuration | ||||
| public class SmallToolsThreadPoolConfig { | ||||
| 
 | ||||
|     @Bean(ThreadPoolConstant.SMALL_TOOLS_THREAD_POOL) | ||||
|     public Executor threadPoolExecutor() { | ||||
|         ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); | ||||
|         // 核心线程池大小
 | ||||
|         threadPoolTaskExecutor.setCorePoolSize(5); | ||||
|         // 最大线程数
 | ||||
|         threadPoolTaskExecutor.setMaxPoolSize(10); | ||||
|         // 队列容量
 | ||||
|         threadPoolTaskExecutor.setQueueCapacity(200); | ||||
|         // 活跃时间 60s
 | ||||
|         threadPoolTaskExecutor.setKeepAliveSeconds(60); | ||||
|         // 线程名字前缀
 | ||||
|         threadPoolTaskExecutor | ||||
|                 .setThreadNamePrefix(ThreadPoolConstant.SMALL_TOOLS_THREAD_NAME_PREFIX); | ||||
|         // 设置在关闭线程池时是否等待任务完成
 | ||||
|         threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); | ||||
|         // 允许核心线程超时
 | ||||
|         threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true); | ||||
|         // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
 | ||||
|         threadPoolTaskExecutor.setAwaitTerminationSeconds(60); | ||||
|         // 修改拒绝策略为使用当前线程执行
 | ||||
|         // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
 | ||||
|         // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
 | ||||
|         threadPoolTaskExecutor | ||||
|                 .setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | ||||
|         //初始化线程池
 | ||||
|         threadPoolTaskExecutor.initialize(); | ||||
|         return threadPoolTaskExecutor; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| package com.zhengqing.common.core.custom.fieldrepeat; | ||||
| 
 | ||||
| import javax.validation.Constraint; | ||||
| import javax.validation.Payload; | ||||
| import java.lang.annotation.*; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 自定义字段对应数据库内容重复校验 注解 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/9/10 9:32 | ||||
|  */ | ||||
| // 元注解: 给其他普通的标签进行解释说明 【@Retention、@Documented、@Target、@Inherited、@Repeatable】
 | ||||
| @Documented | ||||
| /** | ||||
|  * 指明生命周期: RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 | ||||
|  * RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| /** | ||||
|  * 指定注解运用的地方: ElementType.ANNOTATION_TYPE 可以给一个注解进行注解 ElementType.CONSTRUCTOR 可以给构造方法进行注解 ElementType.FIELD 可以给属性进行注解 | ||||
|  * ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 ElementType.METHOD 可以给方法进行注解 ElementType.PACKAGE 可以给一个包进行注解 | ||||
|  * ElementType.PARAMETER 可以给一个方法内的参数进行注解 ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举 | ||||
|  */ | ||||
| @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE}) | ||||
| @Constraint(validatedBy = FieldRepeatValidatorClass.class) | ||||
| // @Repeatable(LinkVals.class)(可重复注解同一字段,或者类,java1.8后支持)
 | ||||
| public @interface FieldRepeatValidator { | ||||
| 
 | ||||
|     /** | ||||
|      * 表名 | ||||
|      */ | ||||
|     String tableName(); | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库主键id字段属性名 - 默认为id (该值可无) | ||||
|      */ | ||||
|     String idDbName() default "id"; | ||||
| 
 | ||||
|     /** | ||||
|      * 注解属性 - 对应校验字段名组 ex: { "name", "code" } 【 注:第一个字段名为所校验的字段,其后的字段为辅助校验字段!!! 】 | ||||
|      */ | ||||
|     String[] fieldNames() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库校验字段属性名 - 对应数据库校验字段名组 ex: { "name", "code" } | ||||
|      */ | ||||
|     String[] dbFieldNames() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * 校验字段组固定值 ex: { "", "1" } | ||||
|      */ | ||||
|     String[] fieldFixedValues() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * 默认错误提示信息 | ||||
|      */ | ||||
|     String message() default "字段内容重复!"; | ||||
| 
 | ||||
|     Class<?>[] groups() default {}; | ||||
| 
 | ||||
|     Class<? extends Payload>[] payload() default {}; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package com.zhengqing.common.core.custom.fieldrepeat; | ||||
| 
 | ||||
| import javax.validation.ConstraintValidator; | ||||
| import javax.validation.ConstraintValidatorContext; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * `@FieldRepeatValidator`注解接口实现类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 技巧01:必须实现ConstraintValidator接口 | ||||
|  * 技巧02:实现了ConstraintValidator接口后即使不进行Bean配置,spring也会将这个类进行Bean管理 | ||||
|  * 技巧03:可以在实现了ConstraintValidator接口的类中依赖注入其它Bean | ||||
|  * 技巧04:实现了ConstraintValidator接口后必须重写 initialize 和 isValid 这两个方法 | ||||
|  * initialize 方法主要来进行初始化,通常用来获取自定义注解的属性值; | ||||
|  * isValid 方法主要进行校验逻辑,返回true表示校验通过,返回false表示校验失败,通常根据注解属性值和实体类属性值进行校验判断 [Object:校验字段的属性值] | ||||
|  * @date 2019/9/10 9:22 | ||||
|  */ | ||||
| public class FieldRepeatValidatorClass implements ConstraintValidator<FieldRepeatValidator, Object> { | ||||
| 
 | ||||
|     private String tableName; | ||||
|     private String idDbName; | ||||
|     private String[] fieldNames; | ||||
|     private String[] dbFieldNames; | ||||
|     private String[] fieldFixedValues; | ||||
|     private String message; | ||||
| 
 | ||||
|     @Override | ||||
|     public void initialize(FieldRepeatValidator fieldRepeatValidator) { | ||||
|         this.tableName = fieldRepeatValidator.tableName(); | ||||
|         this.idDbName = fieldRepeatValidator.idDbName(); | ||||
|         this.fieldNames = fieldRepeatValidator.fieldNames(); | ||||
|         this.dbFieldNames = fieldRepeatValidator.dbFieldNames(); | ||||
|         this.fieldFixedValues = fieldRepeatValidator.fieldFixedValues(); | ||||
|         this.message = fieldRepeatValidator.message(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { | ||||
|         FieldRepeatValidatorUtil fieldRepeatValidator = | ||||
|                 new FieldRepeatValidatorUtil(this.tableName, this.idDbName, this.fieldNames, this.dbFieldNames, this.fieldFixedValues, o, this.message); | ||||
|         return fieldRepeatValidator.fieldRepeat(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,210 @@ | ||||
| package com.zhengqing.common.core.custom.fieldrepeat; | ||||
| 
 | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import com.zhengqing.common.db.mapper.MyBaseMapper; | ||||
| import com.zhengqing.common.base.util.ApplicationContextUtil; | ||||
| import com.zhengqing.common.base.util.MyBeanUtil; | ||||
| import com.zhengqing.common.base.util.MyStringUtil; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.util.CollectionUtils; | ||||
| 
 | ||||
| import java.lang.reflect.Field; | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 数据库字段内容重复判断处理工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @date 2019/9/10 9:28 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class FieldRepeatValidatorUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 表名 | ||||
|      */ | ||||
|     private String TABLE_NAME; | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库对应实体类主键id字段属性名 | ||||
|      */ | ||||
|     private String idName; | ||||
|     /** | ||||
|      * 数据库主键id字段属性名 | ||||
|      */ | ||||
|     private String idDbName; | ||||
| 
 | ||||
|     /** | ||||
|      * 数据库主键id字段属性值 | ||||
|      */ | ||||
|     private Integer idValue; | ||||
| 
 | ||||
|     /** | ||||
|      * 校验字段名组 | ||||
|      */ | ||||
|     private String[] fieldNames; | ||||
| 
 | ||||
|     /** | ||||
|      * 校验辅助字段组值 - 字符串、数字、对象... | ||||
|      */ | ||||
|     private List<Object> fieldValues = new LinkedList<>(); | ||||
| 
 | ||||
|     /** | ||||
|      * 校验辅助字段组固定值 - 字符串、数字、对象... | ||||
|      */ | ||||
|     private String[] fieldFixedValues; | ||||
| 
 | ||||
|     /** | ||||
|      * 储存校验辅助字段对应数据库字段值 | ||||
|      */ | ||||
|     private List<String> DB_FIELDS; | ||||
| 
 | ||||
|     /** | ||||
|      * 顶级父包名 | ||||
|      */ | ||||
|     private final String PACKAGE_NAME = "java.lang.Object"; | ||||
| 
 | ||||
|     /** | ||||
|      * 实体类对象值 | ||||
|      */ | ||||
|     private Object object; | ||||
| 
 | ||||
|     /** | ||||
|      * 错误提示信息 | ||||
|      */ | ||||
|     private String message; | ||||
| 
 | ||||
|     /** | ||||
|      * @param tableName:                数据库表名 | ||||
|      * @param idDbName:                 数据库主键id字段属性名 | ||||
|      * @param fieldNames:校验字段组 | ||||
|      * @param dbFieldNames:数据库校验字段组 | ||||
|      * @param fieldFixedValues:校验字段组固定值 | ||||
|      * @param object:对象数据 | ||||
|      * @param message:回调到前端提示消息 | ||||
|      */ | ||||
|     public FieldRepeatValidatorUtil(String tableName, String idDbName, String[] fieldNames, String[] dbFieldNames, | ||||
|                                     String[] fieldFixedValues, Object object, String message) { | ||||
|         this.TABLE_NAME = tableName; | ||||
| 
 | ||||
|         this.idDbName = idDbName; | ||||
|         // id 转 驼峰命名
 | ||||
|         this.idName = MyStringUtil.dbStrToHumpLower(idDbName); | ||||
| 
 | ||||
|         this.fieldNames = fieldNames; | ||||
|         this.DB_FIELDS = new ArrayList<>(Arrays.asList(dbFieldNames)); | ||||
|         this.fieldFixedValues = fieldFixedValues; | ||||
|         this.object = object; | ||||
|         this.message = message; | ||||
|         this.getFieldValue(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 校验数据 | ||||
|      * | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public boolean fieldRepeat() { | ||||
|         // 4、 校验字段内容是否重复
 | ||||
|         // 工厂模式 + ar动态语法 -> 【这里已修改为`MyBaseMapper`方式查询数据】
 | ||||
|         // Model entity = (Model)object;
 | ||||
|         // if (object.getClass().getAnnotation(TableName.class) == null) {
 | ||||
|         // try {
 | ||||
|         // entity = (Model)object.getClass().getSuperclass().newInstance();
 | ||||
|         // } catch (Exception e) {
 | ||||
|         // e.printStackTrace();
 | ||||
|         // }
 | ||||
|         // }
 | ||||
|         // list = entity.selectList((Wrapper)new QueryWrapper().allEq(queryMap));
 | ||||
| 
 | ||||
|         // 5、拼接sql
 | ||||
|         Map<Object, Object> queryMap = new HashMap<>(5); | ||||
|         if (this.fieldFixedValues.length == 0) { | ||||
|             for (int i = 0; i < this.fieldNames.length; i++) { | ||||
|                 queryMap.put(this.DB_FIELDS.get(i), this.fieldValues.get(i)); | ||||
|             } | ||||
|         } else { | ||||
|             for (int i = 0; i < this.fieldNames.length; i++) { | ||||
|                 if (StringUtils.isBlank(this.fieldFixedValues[i])) { | ||||
|                     queryMap.put(this.DB_FIELDS.get(i), this.fieldValues.get(i)); | ||||
|                 } else { | ||||
|                     queryMap.put(this.DB_FIELDS.get(i), this.fieldFixedValues[i]); | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         List<Map<String, Object>> list = | ||||
|                 ApplicationContextUtil.getApplicationContext().getBean(MyBaseMapper.class).selectList(this.TABLE_NAME, queryMap); | ||||
| 
 | ||||
|         // 6、如果数据重复返回false -> 再返回自定义错误消息到前端
 | ||||
|         if (!CollectionUtils.isEmpty(list)) { | ||||
|             if (this.idValue == null) { | ||||
|                 throw new MyException(this.message); | ||||
|             } else { | ||||
|                 if (list.size() > 1) { | ||||
|                     throw new MyException(this.message); | ||||
|                 } | ||||
|                 // 获取list中指定字段属性值 - 这里只获取主键id
 | ||||
|                 List<Object> idList = (List<Object>) MyBeanUtil.getFieldList(list, this.idDbName); | ||||
|                 boolean isContainsIdValue = false; | ||||
|                 for (Object itemId : idList) { | ||||
|                     if (itemId.toString().equals(this.idValue.toString())) { | ||||
|                         isContainsIdValue = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (!isContainsIdValue) { | ||||
|                     throw new MyException(this.message); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取主键id、校验字段、以及对应数据库字段值 | ||||
|      */ | ||||
|     private void getFieldValue() { | ||||
|         try { | ||||
|             // 1、获取所有的字段
 | ||||
|             Class clz = this.object.getClass(); | ||||
|             // 当父类为null的时候说明到达了最上层的父类(Object类) -> 作递归取父类属性值使用
 | ||||
|             Map<String, Field> fieldMap = new HashMap<>(); | ||||
|             while (clz != null && !this.PACKAGE_NAME.equals(clz.getName().toLowerCase())) { | ||||
|                 fieldMap.putAll( | ||||
|                         Arrays.stream(clz.getDeclaredFields()).collect(Collectors.toMap(Field::getName, field -> field))); | ||||
|                 // 得到父类,然后赋给自己
 | ||||
|                 clz = clz.getSuperclass(); | ||||
|             } | ||||
| 
 | ||||
|             // 2、取校验字段值
 | ||||
|             for (int i = 0; i < this.fieldNames.length; i++) { | ||||
|                 Field field = fieldMap.get(this.fieldNames[i]); | ||||
|                 if (field == null) { | ||||
|                     this.fieldValues.add(null); | ||||
|                 } else { | ||||
|                     // 设置对象中成员 属性private为可读
 | ||||
|                     field.setAccessible(true); | ||||
|                     // 校验字段名的值 【 fieldNames中第一个字段为校验字段,其后为辅助校验字段 】
 | ||||
|                     Object fieldValue = field.get(this.object); | ||||
|                     this.fieldValues.add(fieldValue); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // 3、取主键id字段值 -> 作用:判断是插入还是更新操作
 | ||||
|             Field fieldId = fieldMap.get(this.idName); | ||||
|             if (fieldId != null) { | ||||
|                 fieldId.setAccessible(true); | ||||
|                 this.idValue = (Integer) fieldId.get(this.object); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             throw new MyException("数据库字段内容验重校验取值失败:" + e.toString()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| package com.zhengqing.common.core.custom.limit; | ||||
| 
 | ||||
| import org.redisson.api.RateIntervalUnit; | ||||
| import org.redisson.api.RateType; | ||||
| 
 | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 自定义注解-基于redis的限流 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/10/8 9:31 | ||||
|  */ | ||||
| // 作用到方法上
 | ||||
| @Target(ElementType.METHOD) | ||||
| // 运行时有效
 | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface ApiLimit { | ||||
| 
 | ||||
|     /** | ||||
|      * 限流器key | ||||
|      */ | ||||
|     String key(); | ||||
| 
 | ||||
|     /** | ||||
|      * 速率类型,默认所有客户端加总限流 | ||||
|      */ | ||||
|     RateType rateType() default RateType.OVERALL; | ||||
| 
 | ||||
|     /** | ||||
|      * 速率数,默认1 | ||||
|      */ | ||||
|     long rate() default 1; | ||||
| 
 | ||||
|     /** | ||||
|      * 速率间隔时间,默认3秒 | ||||
|      */ | ||||
|     long rateInterval() default 3; | ||||
| 
 | ||||
|     /** | ||||
|      * 速率间隔时间单位,默认秒 | ||||
|      */ | ||||
|     RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS; | ||||
| 
 | ||||
|     /** | ||||
|      * 默认限流提示信息 | ||||
|      */ | ||||
|     String msg() default "操作频繁,请稍后再试!"; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| package com.zhengqing.common.core.custom.limit; | ||||
| 
 | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import com.zhengqing.common.base.util.SpringElUtil; | ||||
| import lombok.SneakyThrows; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||
| import org.aspectj.lang.annotation.Around; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.reflect.MethodSignature; | ||||
| import org.redisson.api.RRateLimiter; | ||||
| import org.redisson.api.RateIntervalUnit; | ||||
| import org.redisson.api.RateType; | ||||
| import org.redisson.api.RedissonClient; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import javax.annotation.Resource; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
|  * <p> aop切面-redis限流处理 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/10/8 9:36 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| @Component | ||||
| public class ApiLimitAspect { | ||||
| 
 | ||||
|     @Resource | ||||
|     private RedissonClient redissonClient; | ||||
| 
 | ||||
|     /** | ||||
|      * 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 | ||||
|      * 定义切入点表达式: execution(public * (…)) 表达式解释: execution:主体 public:可省略 *:标识方法的任意返回值 任意包+类+方法(…) 任意参数 | ||||
|      * com.zhengqing.mall.controller.*Controller.*(..)) : 标识AOP所切服务的包名,即需要进行横切的业务类 .*Controller : 标识类名,*即所有类 .*(..) : | ||||
|      * 标识任何方法名,括号表示参数,两个点表示任何参数类型 | ||||
|      * | ||||
|      * @param pjp      切入点对象 | ||||
|      * @param apiLimit 自定义的注解对象 | ||||
|      * @return java.lang.Object | ||||
|      */ | ||||
|     @SneakyThrows | ||||
|     @Around("( execution(* com.zhengqing.*..*.*Controller.*(..))" + | ||||
|             " || execution(* com.zhengqing.*.feign.*(..)) ) && @annotation(apiLimit)") | ||||
|     public Object doAround(ProceedingJoinPoint pjp, ApiLimit apiLimit) { | ||||
|         // 数据获取
 | ||||
|         String key = apiLimit.key(); | ||||
|         Assert.notBlank(key, "限流器key不能为空!"); | ||||
| 
 | ||||
|         // 支持spring El表达式
 | ||||
|         Method method = ((MethodSignature) pjp.getSignature()).getMethod(); | ||||
|         Object[] args = pjp.getArgs(); | ||||
|         key = SpringElUtil.parse(key, method, args); | ||||
| 
 | ||||
|         RateType rateType = apiLimit.rateType(); | ||||
|         long rate = apiLimit.rate(); | ||||
|         long rateInterval = apiLimit.rateInterval(); | ||||
|         RateIntervalUnit rateIntervalUnit = apiLimit.rateIntervalUnit(); | ||||
|         String msg = apiLimit.msg(); | ||||
| 
 | ||||
|         // 1、声明一个限流器
 | ||||
|         RRateLimiter rRateLimiter = this.redissonClient.getRateLimiter(key); | ||||
|         // 2、设置速率,[rateInterval]秒中产生[rate]个令牌
 | ||||
|         rRateLimiter.trySetRate(rateType, rate, rateInterval, rateIntervalUnit); | ||||
|         rRateLimiter.expire(1, TimeUnit.HOURS); | ||||
|         // 3、试图获取一个令牌,获取到返回true
 | ||||
|         if (rRateLimiter.tryAcquire()) { | ||||
|             return pjp.proceed(); | ||||
|         } else { | ||||
|             throw new MyException(msg); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package com.zhengqing.common.core.custom.lock; | ||||
| 
 | ||||
| import java.lang.annotation.*; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 自定义注解-基于redisson的分布式锁 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2022/1/18 14:22 | ||||
|  */ | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Inherited | ||||
| public @interface RedisLock { | ||||
| 
 | ||||
|     /** | ||||
|      * 锁的资源,key。支持spring El表达式 | ||||
|      */ | ||||
| //    @AliasFor("key")
 | ||||
|     String key() default "'smallboot:redis_lock'"; | ||||
| 
 | ||||
|     /** | ||||
|      * 锁类型 | ||||
|      */ | ||||
|     RedisLockType lockType() default RedisLockType.REENTRANT_LOCK; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取锁等待时间,默认3秒 | ||||
|      */ | ||||
|     long waitTime() default 3000L; | ||||
| 
 | ||||
|     /** | ||||
|      * 锁自动释放时间,默认30秒 | ||||
|      */ | ||||
|     long leaseTime() default 30000L; | ||||
| 
 | ||||
|     /** | ||||
|      * 时间单位,默认毫秒(获取锁等待时间和持锁时间都用此单位) | ||||
|      */ | ||||
|     TimeUnit unit() default TimeUnit.MILLISECONDS; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,82 @@ | ||||
| package com.zhengqing.common.core.custom.lock; | ||||
| 
 | ||||
| import com.zhengqing.common.base.util.SpringElUtil; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||
| import org.aspectj.lang.annotation.Around; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.reflect.MethodSignature; | ||||
| import org.redisson.api.RLock; | ||||
| import org.redisson.api.RedissonClient; | ||||
| import org.redisson.spring.starter.RedissonAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.AutoConfigureAfter; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import javax.annotation.Resource; | ||||
| import java.lang.reflect.Method; | ||||
| 
 | ||||
| /** | ||||
|  * <p> aop切面-redisson分布式锁 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2022/1/18 14:24 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| @Component | ||||
| @ConditionalOnBean(RedissonClient.class) | ||||
| @AutoConfigureAfter(RedissonAutoConfiguration.class) | ||||
| public class RedisLockAspect { | ||||
| 
 | ||||
|     @Resource | ||||
|     private RedissonClient redissonClient; | ||||
| 
 | ||||
|     @Around("@annotation(com.zhengqing.common.core.custom.lock.RedisLock)") | ||||
|     public Object around(ProceedingJoinPoint pjp) throws Throwable { | ||||
|         Method method = ((MethodSignature) pjp.getSignature()).getMethod(); | ||||
|         RedisLock redisLock = method.getAnnotation(RedisLock.class); | ||||
|         String key = redisLock.key(); | ||||
|         Object[] args = pjp.getArgs(); | ||||
|         // 支持spring El表达式
 | ||||
|         key = SpringElUtil.parse(key, method, args); | ||||
|         // 获取锁
 | ||||
|         RLock lock = this.getLock(key, redisLock); | ||||
|         lock.lock(redisLock.leaseTime(), redisLock.unit()); | ||||
|         try { | ||||
|             return pjp.proceed(); | ||||
|         } catch (Exception e) { | ||||
|             throw e; | ||||
|         } finally { | ||||
|             // 释放锁
 | ||||
|             lock.unlock(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 获取锁 | ||||
|      * | ||||
|      * @param key       key | ||||
|      * @param redisLock 分布式锁注解 | ||||
|      * @return 锁 | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/1/18 14:28 | ||||
|      */ | ||||
|     private RLock getLock(String key, RedisLock redisLock) { | ||||
|         switch (redisLock.lockType()) { | ||||
|             case REENTRANT_LOCK: | ||||
|                 return this.redissonClient.getLock(key); | ||||
|             case FAIR_LOCK: | ||||
|                 return this.redissonClient.getFairLock(key); | ||||
|             case READ_LOCK: | ||||
|                 return this.redissonClient.getReadWriteLock(key).readLock(); | ||||
|             case WRITE_LOCK: | ||||
|                 return this.redissonClient.getReadWriteLock(key).writeLock(); | ||||
|             default: | ||||
|                 throw new RuntimeException("do not support lock type:" + redisLock.lockType().name()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| package com.zhengqing.common.core.custom.lock; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 基于redisson的分布式锁类型 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description https://www.bookstack.cn/read/redisson-wiki-zh/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8.md
 | ||||
|  * @date 2022/1/18 14:23 | ||||
|  */ | ||||
| public enum RedisLockType { | ||||
| 
 | ||||
|     /** | ||||
|      * 可重入锁 | ||||
|      */ | ||||
|     REENTRANT_LOCK, | ||||
| 
 | ||||
|     /** | ||||
|      * 公平锁 | ||||
|      */ | ||||
|     FAIR_LOCK, | ||||
| 
 | ||||
|     /** | ||||
|      * 读锁 | ||||
|      */ | ||||
|     READ_LOCK, | ||||
| 
 | ||||
|     /** | ||||
|      * 写锁 | ||||
|      */ | ||||
|     WRITE_LOCK; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.zhengqing.common.core.custom.parameter; | ||||
| 
 | ||||
| import com.zhengqing.common.base.exception.ParameterException; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 参数校验 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/8/1 18:08 | ||||
|  */ | ||||
| public interface ParamCheck { | ||||
| 
 | ||||
|     /** | ||||
|      * 传入参数验证 | ||||
|      * | ||||
|      * @throws ParameterException 参数异常 | ||||
|      */ | ||||
|     void checkParam() throws ParameterException; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package com.zhengqing.common.core.custom.post; | ||||
| 
 | ||||
| import org.springframework.core.annotation.AliasFor; | ||||
| import org.springframework.web.bind.annotation.ValueConstants; | ||||
| 
 | ||||
| import java.lang.annotation.*; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 自定义注解`RequestPostSingleParam` - 处理接收单个参数的`post`请求 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 使用时注意,用了`RequestPostSingleParam`接收参数就只能作用于一个参数,不能在controller层方法中写多个参数了!!! | ||||
|  * @date 2021/1/13 14:41 | ||||
|  */ | ||||
| @Target(ElementType.PARAMETER) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface RequestPostSingleParam { | ||||
| 
 | ||||
|     /** | ||||
|      * Alias for {@link #name}. | ||||
|      */ | ||||
|     @AliasFor("name") | ||||
|     String value() default ""; | ||||
| 
 | ||||
|     /** | ||||
|      * The name of the request parameter to bind to. | ||||
|      * | ||||
|      * @since 4.2 | ||||
|      */ | ||||
|     @AliasFor("value") | ||||
|     String name() default ""; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether the parameter is required. | ||||
|      * <p> | ||||
|      * Defaults to {@code true}, leading to an exception being thrown if the parameter is missing in the request. Switch | ||||
|      * this to {@code false} if you prefer a {@code null} value if the parameter is not present in the request. | ||||
|      * <p> | ||||
|      * Alternatively, provide a {@link #defaultValue}, which implicitly sets this flag to {@code false}. | ||||
|      */ | ||||
|     boolean required() default true; | ||||
| 
 | ||||
|     /** | ||||
|      * The default value to use as a fallback when the request parameter is not provided or has an empty value. | ||||
|      * <p> | ||||
|      * Supplying a default value implicitly sets {@link #required} to {@code false}. | ||||
|      */ | ||||
|     String defaultValue() default ValueConstants.DEFAULT_NONE; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,113 @@ | ||||
| package com.zhengqing.common.core.custom.post; | ||||
| 
 | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import com.google.common.collect.Maps; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.beanutils.ConvertUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.core.MethodParameter; | ||||
| import org.springframework.web.bind.support.WebDataBinderFactory; | ||||
| import org.springframework.web.context.request.NativeWebRequest; | ||||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||||
| import org.springframework.web.method.support.ModelAndViewContainer; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.io.BufferedReader; | ||||
| import java.io.IOException; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 编写参数解析器 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/1/13 14:41 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class RequestPostSingleParamMethodArgumentResolver implements HandlerMethodArgumentResolver { | ||||
| 
 | ||||
|     private static final String POST = "post"; | ||||
|     private static final String APPLICATION_JSON = "application/json"; | ||||
| 
 | ||||
|     /** | ||||
|      * 判断是否需要处理该参数 | ||||
|      * | ||||
|      * @param parameter the method parameter to check | ||||
|      * @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean supportsParameter(MethodParameter parameter) { | ||||
|         // 只处理带有@RequestPostSingleParam注解的参数
 | ||||
|         return parameter.hasParameterAnnotation(RequestPostSingleParam.class); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, | ||||
|                                   NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { | ||||
|         HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); | ||||
|         String contentType = Objects.requireNonNull(servletRequest).getContentType(); | ||||
| 
 | ||||
|         if (contentType == null || !contentType.contains(APPLICATION_JSON)) { | ||||
|             log.error("《RequestPostSingleParam》 contentType需为【{}】", APPLICATION_JSON); | ||||
|             throw new RuntimeException("《RequestPostSingleParam》 contentType需为application/json"); | ||||
|         } | ||||
| 
 | ||||
|         if (!POST.equalsIgnoreCase(servletRequest.getMethod())) { | ||||
|             log.error("《RequestPostSingleParam》 请求类型必须为post"); | ||||
|             throw new RuntimeException("《RequestPostSingleParam》 请求类型必须为post"); | ||||
|         } | ||||
|         return this.bindRequestParams(parameter, servletRequest); | ||||
|     } | ||||
| 
 | ||||
|     private Object bindRequestParams(MethodParameter parameter, HttpServletRequest servletRequest) { | ||||
|         RequestPostSingleParam requestPostSingleParam = parameter.getParameterAnnotation(RequestPostSingleParam.class); | ||||
|         Class<?> parameterType = parameter.getParameterType(); | ||||
|         String requestBody = this.getRequestBody(servletRequest); | ||||
|         Map paramObj = JSONUtil.toBean(requestBody, Map.class); | ||||
|         if (paramObj == null) { | ||||
|             paramObj = Maps.newHashMap(); | ||||
|         } | ||||
|         // if (paramObj.size() > 1) {
 | ||||
|         // throw new RuntimeException("《RequestPostSingleParam》 post请求只支持接收单个参数!");
 | ||||
|         // }
 | ||||
| 
 | ||||
|         String parameterName = StringUtils.isBlank(requestPostSingleParam.value()) ? parameter.getParameterName() | ||||
|                 : requestPostSingleParam.value(); | ||||
|         Object value = paramObj.get(parameterName); | ||||
| 
 | ||||
|         if (requestPostSingleParam.required()) { | ||||
|             if (value == null) { | ||||
|                 log.error("《RequestPostSingleParam》 require=true,参数【{}】不能为空!", parameterName); | ||||
|                 throw new RuntimeException("《RequestPostSingleParam》 " + parameterName + "不能为空!"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ConvertUtils.convert(value, parameterType); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取请求body | ||||
|      * | ||||
|      * @param servletRequest: request | ||||
|      * @return 请求body | ||||
|      */ | ||||
|     private String getRequestBody(HttpServletRequest servletRequest) { | ||||
|         StringBuilder stringBuilder = new StringBuilder(); | ||||
|         try { | ||||
|             BufferedReader reader = servletRequest.getReader(); | ||||
|             char[] buf = new char[1024]; | ||||
|             int length; | ||||
|             while ((length = reader.read(buf)) != -1) { | ||||
|                 stringBuilder.append(buf, 0, length); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             log.error("《RequestPostSingleParam》 读取流异常", e); | ||||
|             throw new RuntimeException("《RequestPostSingleParam》 读取流异常"); | ||||
|         } | ||||
|         return stringBuilder.toString(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| package com.zhengqing.common.core.custom.post; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 注册参数解析器 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2021/1/13 14:41 | ||||
|  */ | ||||
| @Configuration | ||||
| public class RequestPostSingleParamResolverConfig implements WebMvcConfigurer { | ||||
| 
 | ||||
|     @Override | ||||
|     public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { | ||||
|         resolvers.add(new RequestPostSingleParamMethodArgumentResolver()); | ||||
|         WebMvcConfigurer.super.addArgumentResolvers(resolvers); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.zhengqing.common.core.custom.repeatsubmit; | ||||
| 
 | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 注解:校验表单重复提交 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/11/27 9:59 | ||||
|  */ | ||||
| // 作用到方法上
 | ||||
| @Target(ElementType.METHOD) | ||||
| // 运行时有效
 | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface NoRepeatSubmit { | ||||
|     /** | ||||
|      * 默认时间3秒 | ||||
|      */ | ||||
|     int time() default 3 * 1000; | ||||
| } | ||||
| @ -0,0 +1,84 @@ | ||||
| package com.zhengqing.common.core.custom.repeatsubmit; | ||||
| 
 | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import com.zhengqing.common.redis.util.RedisUtil; | ||||
| import com.zhengqing.common.web.util.IpUtil; | ||||
| import lombok.SneakyThrows; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||
| import org.aspectj.lang.annotation.Around; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.context.request.RequestContextHolder; | ||||
| import org.springframework.web.context.request.ServletRequestAttributes; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * aop切面 校验表单重复提交 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/11/27 11:26 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| @Component | ||||
| public class NoRepeatSubmitAop { | ||||
| 
 | ||||
|     /** | ||||
|      * 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 | ||||
|      * 定义切入点表达式: execution(public * (…)) 表达式解释: | ||||
|      * -                          execution => 主体 | ||||
|      * -                          public => 可省略 | ||||
|      * -                          * => 标识方法的任意返回值 任意包+类+方法(…) 任意参数 | ||||
|      * -                 com.zhengqing.*.*.api => 标识AOP所切服务的包名,即需要进行横切的业务类 | ||||
|      * -                          .*Controller => 标识类名,*即所有类 | ||||
|      * -                          .*(..) => 标识任何方法名,括号表示参数,两个点表示任何参数类型 | ||||
|      * | ||||
|      * @param pjp            切入点对象 | ||||
|      * @param noRepeatSubmit 自定义的注解对象 | ||||
|      * @return java.lang.Object | ||||
|      */ | ||||
|     @SneakyThrows | ||||
|     @Around("( execution(* com.zhengqing.*.api.*Controller.*(..)) || execution(* com.zhengqing..*.api.*Controller.*(..)) ) && @annotation(noRepeatSubmit)") | ||||
|     public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) { | ||||
|         HttpServletRequest request = | ||||
|                 ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())) | ||||
|                         .getRequest(); | ||||
| 
 | ||||
|         // 拿到ip地址、请求路径
 | ||||
|         String ip = IpUtil.getIpAdrress(request); | ||||
|         String url = request.getRequestURL().toString(); | ||||
| 
 | ||||
|         // 现在时间
 | ||||
|         long now = System.currentTimeMillis(); | ||||
| 
 | ||||
|         // 自定义key值方式
 | ||||
|         String key = "REQUEST_FORM_" + ip; | ||||
|         if (RedisUtil.hasKey(key)) { | ||||
|             // 上次表单提交时间
 | ||||
|             long lastTime = Long.parseLong(RedisUtil.get(key)); | ||||
|             // 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理
 | ||||
|             if ((now - lastTime) > noRepeatSubmit.time()) { | ||||
|                 // 非重复提交操作 - 重新记录操作时间
 | ||||
|                 RedisUtil.set(key, String.valueOf(now)); | ||||
|                 // 进入处理业务
 | ||||
|                 // ApiResult result = (ApiResult)pjp.proceed();
 | ||||
|                 return pjp.proceed(); | ||||
|             } else { | ||||
|                 throw new MyException("请勿重复提交!"); | ||||
|             } | ||||
|         } else { | ||||
|             // 这里是第一次操作
 | ||||
|             RedisUtil.set(key, String.valueOf(now)); | ||||
|             // ApiResult result = (ApiResult)pjp.proceed();
 | ||||
|             return pjp.proceed(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package com.zhengqing.common.core.custom.requestparamalias; | ||||
| 
 | ||||
| import java.lang.annotation.*; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 自定义注解`@RequestParamAlias` - get请求`@ModelAttribute`接收对象属性中的字段别名设置 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 通过自定义spring属性编辑器解决 | ||||
|  * tips: 字段别名注解`@JsonProperty`和`@JsonAlias`应用于post请求,不支持get请求,所以这里才单独自定义注解 | ||||
|  * @date 2022/10/20 10:58 | ||||
|  */ | ||||
| @Target(ElementType.FIELD) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface RequestParamAlias { | ||||
|     String value(); | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| package com.zhengqing.common.core.custom.requestparamalias; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 如果是实体类,并且用到了注解@RequestParamAlias,请实现本类 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2022/10/20 11:18 | ||||
|  */ | ||||
| public interface RequestParamAliasBean { | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| package com.zhengqing.common.core.custom.requestparamalias; | ||||
| 
 | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 请求的字段别名-参数解析器 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 添加到第一个参数解析器中的bean配置,通过自定义spring属性编辑器解决 | ||||
|  * @date 2022/10/20 10:58 | ||||
|  */ | ||||
| @Configuration | ||||
| public class RequestParamAliasConfig { | ||||
|     private ApplicationContext applicationContext; | ||||
| 
 | ||||
|     @Autowired | ||||
|     public void setApplicationContext(ApplicationContext applicationContext) { | ||||
|         this.applicationContext = applicationContext; | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public RequestParamAliasProcessor requestParamAliasProcessor() { | ||||
|         return new RequestParamAliasProcessor(this.applicationContext); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public BeanPostProcessor beanPostProcessor() { | ||||
|         return new BeanPostProcessor() { | ||||
|             @Override | ||||
|             public Object postProcessBeforeInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { | ||||
|                 return bean; | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { | ||||
|                 if (bean instanceof RequestMappingHandlerAdapter) { | ||||
|                     RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; | ||||
|                     List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); | ||||
|                     resolvers.add(RequestParamAliasConfig.this.requestParamAliasProcessor()); | ||||
|                     if (adapter.getArgumentResolvers() != null) { | ||||
|                         resolvers.addAll(adapter.getArgumentResolvers()); | ||||
|                     } | ||||
|                     adapter.setArgumentResolvers(resolvers); | ||||
|                 } | ||||
|                 return bean; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| package com.zhengqing.common.core.custom.requestparamalias; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| import org.springframework.beans.MutablePropertyValues; | ||||
| import org.springframework.beans.PropertyValue; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder; | ||||
| 
 | ||||
| import javax.servlet.ServletRequest; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 请求的字段别名-数据绑定处理 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 通过自定义spring属性编辑器解决 | ||||
|  * @date 2022/10/20 10:59 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class RequestParamAliasDataBinder extends ExtendedServletRequestDataBinder { | ||||
|     private final Map<String, String> cacheMap; | ||||
| 
 | ||||
|     public RequestParamAliasDataBinder(Object target, String objectName, Map<String, String> asMap) { | ||||
|         super(target, objectName); | ||||
|         this.cacheMap = asMap; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 复写addBindValues方法 | ||||
|      * | ||||
|      * @param mpv 这里面存的就是请求参数的key-value | ||||
|      * @param req 请求本身, 这里没有用到 | ||||
|      */ | ||||
|     @Override | ||||
|     protected void addBindValues(@NotNull MutablePropertyValues mpv, @NotNull ServletRequest req) { | ||||
|         super.addBindValues(mpv, req); | ||||
|         Object obj; | ||||
|         PropertyValue pv; | ||||
|         log.info("字段别名-缓存,{}", this.cacheMap); | ||||
|         for (Map.Entry<String, String> entry : this.cacheMap.entrySet()) { | ||||
|             String alias = entry.getKey(), fieldName = entry.getValue(); | ||||
|             if (mpv.contains(alias)) { | ||||
|                 pv = mpv.getPropertyValue(alias); | ||||
|                 if (null == pv) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 obj = pv.getValue(); | ||||
|                 log.debug("字段别名-数据绑定处理,{}<>{},{}", alias, fieldName, obj); | ||||
|                 // 给原始字段赋值
 | ||||
|                 mpv.add(fieldName, obj); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| package com.zhengqing.common.core.custom.requestparamalias; | ||||
| 
 | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.core.MethodParameter; | ||||
| import org.springframework.web.bind.WebDataBinder; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.context.request.NativeWebRequest; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor; | ||||
| 
 | ||||
| import javax.servlet.ServletRequest; | ||||
| import java.lang.reflect.Field; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| 
 | ||||
| /** | ||||
|  * <p> 请求的字段别名-参数解析器 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 通过自定义spring属性编辑器解决 | ||||
|  * @date 2022/10/20 11:00 | ||||
|  */ | ||||
| public class RequestParamAliasProcessor extends ServletModelAttributeMethodProcessor { | ||||
|     private static final Map<Class<?>, Map<String, String>> PARAM_CACHE_MAP = new ConcurrentHashMap<>(); | ||||
|     private final ApplicationContext context; | ||||
| 
 | ||||
|     public RequestParamAliasProcessor(ApplicationContext applicationContext) { | ||||
|         super(true); | ||||
|         this.context = applicationContext; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean supportsParameter(MethodParameter parameter) { | ||||
|         // 接口方法不含 注解 RequestParam、入参不是 简单参数、并且参数对象内的字段包含自定义注解 RequestParamAlias
 | ||||
|         return !parameter.hasParameterAnnotation(RequestParam.class) | ||||
|                 && !parameter.hasParameterAnnotation(RequestBody.class) | ||||
|                 && !BeanUtils.isSimpleProperty(parameter.getParameterType()) | ||||
|                 && Arrays.stream(parameter.getParameterType().getDeclaredFields()) | ||||
|                 .anyMatch(field -> field.getAnnotation(RequestParamAlias.class) != null); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void bindRequestParameters(@NotNull WebDataBinder binder, @NotNull NativeWebRequest request) { | ||||
|         Map<String, String> asMap = this.cacheMap(Objects.requireNonNull(binder.getTarget()).getClass()); | ||||
|         RequestParamAliasDataBinder dataBinder = new RequestParamAliasDataBinder(binder.getTarget(), binder.getObjectName(), asMap); | ||||
|         RequestMappingHandlerAdapter adapter = this.context.getBean(RequestMappingHandlerAdapter.class); | ||||
|         Objects.requireNonNull(adapter.getWebBindingInitializer()).initBinder(dataBinder); | ||||
|         dataBinder.bind(Objects.requireNonNull(request.getNativeRequest(ServletRequest.class))); | ||||
|         super.bindRequestParameters(binder, request); | ||||
|     } | ||||
| 
 | ||||
|     private Map<String, String> cacheMap(Class<?> target) { | ||||
|         if (PARAM_CACHE_MAP.containsKey(target)) { | ||||
|             return PARAM_CACHE_MAP.get(target); | ||||
|         } | ||||
|         Map<String, String> map = this.analyzeClass(target, "", ""); | ||||
|         PARAM_CACHE_MAP.put(target, map); | ||||
|         return map; | ||||
|     } | ||||
| 
 | ||||
|     private Map<String, String> analyzeClass(Class<?> target, String prtAs, String prtField) { | ||||
|         Field[] fields = target.getDeclaredFields(); | ||||
|         Map<String, String> map = new HashMap<>(fields.length); | ||||
|         RequestParamAlias rpa; | ||||
|         boolean boo; | ||||
|         String as; | ||||
|         for (Field field : fields) { | ||||
|             boo = (null == (rpa = field.getAnnotation(RequestParamAlias.class)) || rpa.value().isEmpty()); | ||||
|             as = boo ? field.getName() : rpa.value(); | ||||
|             if (this.isExtendBean(field.getType().getInterfaces())) { | ||||
|                 // 如果字段类实现了自定义接口,就认为是自定义是对象类,继续解析字段
 | ||||
|                 // 通过自定义注解在类上面使用,在这里判断也可以
 | ||||
|                 map.putAll(this.analyzeClass(field.getType(), prtAs + as + '.', prtField + field.getName() + '.')); | ||||
|             } else if (!boo || !"".equals(prtAs)) { | ||||
|                 // 只绑定需要的映射
 | ||||
|                 map.put(prtAs + as, prtField + field.getName()); | ||||
|             } | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
| 
 | ||||
|     private boolean isExtendBean(Class<?>[] interfaces) { | ||||
|         for (Class<?> face : interfaces) { | ||||
|             if (face == RequestParamAliasBean.class) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.zhengqing.common.core.custom.validator.common; | ||||
| 
 | ||||
| import javax.validation.groups.Default; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 使用groups的校验 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 同一个对象要复用, 比如UserDTO在更新时候要校验userId, 在保存的时候不需要校验userId, 在两种情况下都要校验username, 那就用上groups了 | ||||
|  * 在需要校验的地方@Validated声明校验组 ` update(@RequestBody @Validated(Update.class) UserDTO userDTO) ` | ||||
|  * 在DTO中的字段上定义好groups = {}的分组类型 ` @NotNull(message = "用户id不能为空", groups = Update.class) | ||||
|  * 或 groups = {Create.class, Update.class} private Long userId; ` | ||||
|  * 【注】注意:在声明分组的时候尽量加上 extend javax.validation.groups.Default | ||||
|  * 否则,在你声明@Validated(Update.class)的时候,就会出现你在默认没添加groups = {}的时候的校验组@Email(message = "邮箱格式不对"),会不去校验,因为默认的校验组是groups = {Default.class}. | ||||
|  * @date 2019/9/9 16:51 | ||||
|  */ | ||||
| public interface CreateGroup extends Default { | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.zhengqing.common.core.custom.validator.common; | ||||
| 
 | ||||
| import javax.validation.groups.Default; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 使用groups的校验 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 同一个对象要复用, 比如UserDTO在更新时候要校验userId, 在保存的时候不需要校验userId, 在两种情况下都要校验username, 那就用上groups了 | ||||
|  * 在需要校验的地方@Validated声明校验组 ` update(@RequestBody @Validated(Update.class) UserDTO userDTO) ` | ||||
|  * 在DTO中的字段上定义好groups = {}的分组类型 ` @NotNull(message = "用户id不能为空", groups = Update.class) | ||||
|  * 或 groups = {Create.class, Update.class} private Long userId; ` | ||||
|  * 【注】注意:在声明分组的时候尽量加上 extend javax.validation.groups.Default | ||||
|  * 否则,在你声明@Validated(Update.class)的时候,就会出现你在默认没添加groups = {}的时候的校验组@Email(message = "邮箱格式不对"),会不去校验,因为默认的校验组是groups = {Default.class}. | ||||
|  * @date 2019/9/9 16:51 | ||||
|  */ | ||||
| public interface UpdateGroup extends Default { | ||||
| } | ||||
| @ -0,0 +1,143 @@ | ||||
| package com.zhengqing.common.core.custom.validator.common; | ||||
| 
 | ||||
| import javax.validation.Valid; | ||||
| import java.util.*; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 自定义list | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 解决`@Validated`只能验证单个实体类,在验证List集合时不生效问题 | ||||
|  * @date 2021/1/20 15:16 | ||||
|  */ | ||||
| public class ValidList<E> implements List<E> { | ||||
| 
 | ||||
|     @Valid | ||||
|     private List<E> list = new LinkedList<>(); | ||||
| 
 | ||||
|     @Override | ||||
|     public int size() { | ||||
|         return this.list.size(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isEmpty() { | ||||
|         return this.list.isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean contains(Object o) { | ||||
|         return this.list.contains(o); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Iterator<E> iterator() { | ||||
|         return this.list.iterator(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object[] toArray() { | ||||
|         return this.list.toArray(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public <T> T[] toArray(T[] a) { | ||||
|         return this.list.toArray(a); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean add(E e) { | ||||
|         return this.list.add(e); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean remove(Object o) { | ||||
|         return this.list.remove(o); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean containsAll(Collection<?> c) { | ||||
|         return this.list.containsAll(c); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean addAll(Collection<? extends E> c) { | ||||
|         return this.list.addAll(c); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean addAll(int index, Collection<? extends E> c) { | ||||
|         return this.list.addAll(index, c); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean removeAll(Collection<?> c) { | ||||
|         return this.list.removeAll(c); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean retainAll(Collection<?> c) { | ||||
|         return this.list.retainAll(c); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void clear() { | ||||
|         this.list.clear(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public E get(int index) { | ||||
|         return this.list.get(index); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public E set(int index, E element) { | ||||
|         return this.list.set(index, element); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void add(int index, E element) { | ||||
|         this.list.add(index, element); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public E remove(int index) { | ||||
|         return this.list.remove(index); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int indexOf(Object o) { | ||||
|         return this.list.indexOf(o); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int lastIndexOf(Object o) { | ||||
|         return this.list.lastIndexOf(o); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ListIterator<E> listIterator() { | ||||
|         return this.list.listIterator(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ListIterator<E> listIterator(int index) { | ||||
|         return this.list.listIterator(index); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<E> subList(int fromIndex, int toIndex) { | ||||
|         return this.list.subList(fromIndex, toIndex); | ||||
|     } | ||||
| 
 | ||||
|     public List<E> getList() { | ||||
|         return this.list; | ||||
|     } | ||||
| 
 | ||||
|     public void setList(List<E> list) { | ||||
|         this.list = list; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,76 @@ | ||||
| package com.zhengqing.common.core.enums; | ||||
| 
 | ||||
| import com.baomidou.mybatisplus.annotation.EnumValue; | ||||
| import com.google.common.collect.Lists; | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 性别枚举类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 0->未知;1->男;2->女 | ||||
|  * @date 2020/11/28 22:56 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum UserSexEnum { | ||||
| 
 | ||||
|     /** | ||||
|      * 未知 | ||||
|      */ | ||||
|     未知((byte) 0, "未知"), | ||||
|     /** | ||||
|      * 男 | ||||
|      */ | ||||
|     男((byte) 1, "男"), | ||||
|     /** | ||||
|      * 女 | ||||
|      */ | ||||
|     女((byte) 2, "女"); | ||||
| 
 | ||||
|     /** | ||||
|      * mybatis-plus 需配置扫包 `type-enums-package` | ||||
|      * 类型值 | ||||
|      * {@link com.baomidou.mybatisplus.annotation.EnumValue} 标记数据库存的值是type | ||||
|      */ | ||||
|     @EnumValue | ||||
|     private final Byte type; | ||||
|     /** | ||||
|      * 类型描述 | ||||
|      * 标识前端展示 | ||||
|      */ | ||||
| //    @JsonValue
 | ||||
|     private final String desc; | ||||
| 
 | ||||
| 
 | ||||
|     private static final List<UserSexEnum> LIST = Lists.newArrayList(); | ||||
| 
 | ||||
|     static { | ||||
|         LIST.addAll(Arrays.asList(UserSexEnum.values())); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 根据指定的规则类型查找相应枚举类 | ||||
|      * | ||||
|      * @param type 类型 | ||||
|      * @return 类型枚举信息 | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/1/10 12:52 | ||||
|      */ | ||||
|     public static UserSexEnum getEnum(Byte type) { | ||||
|         for (UserSexEnum itemEnum : LIST) { | ||||
|             if (itemEnum.getType().equals(type)) { | ||||
|                 return itemEnum; | ||||
|             } | ||||
|         } | ||||
|         throw new MyException("未找到指定类型数据!"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,179 @@ | ||||
| package com.zhengqing.common.core.exception; | ||||
| 
 | ||||
| import cn.dev33.satoken.exception.NotLoginException; | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import com.zhengqing.common.base.exception.ParameterException; | ||||
| import com.zhengqing.common.base.model.vo.ApiResult; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.dao.DuplicateKeyException; | ||||
| import org.springframework.validation.BindException; | ||||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||||
| import org.springframework.web.servlet.NoHandlerFoundException; | ||||
| 
 | ||||
| import javax.validation.ConstraintViolationException; | ||||
| import javax.validation.ValidationException; | ||||
| import java.lang.reflect.UndeclaredThrowableException; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 全局异常处理器 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 在spring 3.2中,新增了@ControllerAdvice | ||||
|  * 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中 | ||||
|  * @date 2019/8/25 0025 18:56 | ||||
|  */ | ||||
| @Slf4j | ||||
| @RestControllerAdvice | ||||
| public class MyGlobalExceptionHandler { | ||||
| 
 | ||||
|     /** | ||||
|      * 自定义异常处理 | ||||
|      */ | ||||
|     @ExceptionHandler(value = MyException.class) | ||||
|     public ApiResult myException(MyException e) { | ||||
|         log.error("自定义异常:", e); | ||||
|         if (e.getCode() != null) { | ||||
|             return ApiResult.fail(e.getCode(), e.getMessage()); | ||||
|         } | ||||
|         return ApiResult.fail(e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     @ExceptionHandler(value = ParameterException.class) | ||||
|     public ApiResult myException(ParameterException e) { | ||||
|         log.error("参数异常:", e); | ||||
|         if (e.getCode() != null) { | ||||
|             return ApiResult.fail(e.getCode(), e.getMessage()); | ||||
|         } | ||||
|         return ApiResult.fail(e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     // 参数校验异常处理 ===========================================================================
 | ||||
|     // MethodArgumentNotValidException是springBoot中进行绑定参数校验时的异常,需要在springBoot中处理,其他需要处理ConstraintViolationException异常进行处理.
 | ||||
| 
 | ||||
|     /** | ||||
|      * 方法参数校验 | ||||
|      */ | ||||
|     @ExceptionHandler(MethodArgumentNotValidException.class) | ||||
|     public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { | ||||
|         log.error("方法参数校验:" + e.getMessage(), e); | ||||
|         return ApiResult.fail(e.getBindingResult().getFieldError().getDefaultMessage()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * ValidationException | ||||
|      */ | ||||
|     @ExceptionHandler(ValidationException.class) | ||||
|     public ApiResult handleValidationException(ValidationException e) { | ||||
|         log.error("ValidationException:", e); | ||||
|         Throwable cause = e.getCause(); | ||||
|         if (cause == null) { | ||||
|             return ApiResult.fail(e.getMessage()); | ||||
|         } | ||||
|         String message = cause.getMessage(); | ||||
|         return ApiResult.fail(message); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * jsr303参数校验异常 | ||||
|      */ | ||||
|     @ExceptionHandler({BindException.class}) | ||||
|     public ApiResult<String> exception(BindException e) { | ||||
|         log.error("BindException:", e); | ||||
|         return ApiResult.fail(e.getBindingResult().getFieldError().getDefaultMessage()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * ConstraintViolationException | ||||
|      */ | ||||
|     @ExceptionHandler(ConstraintViolationException.class) | ||||
|     public ApiResult handleConstraintViolationException(ConstraintViolationException e) { | ||||
|         log.error("ValidationException:" + e.getMessage(), e); | ||||
|         return ApiResult.fail(e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     @ExceptionHandler(IllegalArgumentException.class) | ||||
|     public ApiResult<String> handleRuntimeException(IllegalArgumentException e) { | ||||
|         log.error("参数不合法:", e); | ||||
|         return ApiResult.fail(e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     @ExceptionHandler(NoHandlerFoundException.class) | ||||
|     public ApiResult handlerNoFoundException(Exception e) { | ||||
|         log.error("404:", e); | ||||
|         return ApiResult.fail(404, "路径不存在,请检查路径是否正确"); | ||||
|     } | ||||
| 
 | ||||
|     @ExceptionHandler(DuplicateKeyException.class) | ||||
|     public ApiResult handleDuplicateKeyException(DuplicateKeyException e) { | ||||
|         log.error("数据重复,请检查后提交:", e); | ||||
|         return ApiResult.fail("数据重复,请检查后提交:" + e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     // ===============================================
 | ||||
| 
 | ||||
|     @ExceptionHandler(RuntimeException.class) | ||||
|     public ApiResult handleRuntimeException(RuntimeException e) { | ||||
|         log.error("系统异常:", e); | ||||
|         return ApiResult.fail("系统异常,操作失败:" + e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     @ExceptionHandler(NotLoginException.class) | ||||
|     public ApiResult handleNotLoginException(NotLoginException e) { | ||||
|         log.error("认证异常:", e); | ||||
|         return ApiResult.expired(e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 空指针异常 | ||||
|      */ | ||||
|     @ExceptionHandler(NullPointerException.class) | ||||
|     public ApiResult nullPointerExceptionHandler(NullPointerException e) { | ||||
|         log.error("空指针异常:", e); | ||||
|         return ApiResult.fail("空指针异常:" + e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 类型转换异常 | ||||
|      */ | ||||
|     @ExceptionHandler(ClassCastException.class) | ||||
|     public ApiResult classCastExceptionHandler(ClassCastException e) { | ||||
|         log.error("类型转换异常:", e); | ||||
|         return ApiResult.fail("类型转换异常:" + e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 数组越界异常 | ||||
|      */ | ||||
|     @ExceptionHandler(ArrayIndexOutOfBoundsException.class) | ||||
|     public ApiResult arrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) { | ||||
|         log.error("数组越界异常:", e); | ||||
|         return ApiResult.fail("数组越界异常:" + e.getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 包含调用处理程序抛出的未声明的检查异常 | ||||
|      */ | ||||
|     @ExceptionHandler({UndeclaredThrowableException.class}) | ||||
|     public ApiResult exception(UndeclaredThrowableException e) { | ||||
|         log.error("UndeclaredThrowableException:", e); | ||||
|         Throwable cause = e.getCause(); | ||||
|         if (cause == null) { | ||||
|             return ApiResult.fail(e.getMessage()); | ||||
|         } | ||||
|         return ApiResult.fail(500, cause.toString()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 其他错误 | ||||
|      */ | ||||
|     @ExceptionHandler({Exception.class}) | ||||
|     public ApiResult exception(Exception e) { | ||||
|         log.error("其他错误:", e); | ||||
|         return ApiResult.fail(500, "其他错误:" + e); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,94 @@ | ||||
| package com.zhengqing.common.core.util; | ||||
| 
 | ||||
| import com.zhengqing.common.base.constant.AppConstant; | ||||
| import lombok.SneakyThrows; | ||||
| import sun.misc.BASE64Decoder; | ||||
| import sun.misc.BASE64Encoder; | ||||
| 
 | ||||
| import javax.crypto.Cipher; | ||||
| import javax.crypto.SecretKey; | ||||
| import javax.crypto.SecretKeyFactory; | ||||
| import javax.crypto.spec.DESKeySpec; | ||||
| import java.security.SecureRandom; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * DES 加密/解密工具类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2020/4/12 0:40 | ||||
|  */ | ||||
| public class DesUtil { | ||||
| 
 | ||||
|     private static final String DES_ALGORITHM = "DES"; | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         String helloWorld = encrypt("hello world", AppConstant.DES_KEY); | ||||
|         System.out.println(helloWorld); | ||||
|         System.out.println(decrypt(helloWorld, AppConstant.DES_KEY)); | ||||
|         System.out.println(decrypt("UhWnn6jPKf5a+b+fzS7BqQ==", AppConstant.DES_KEY)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * des加密 | ||||
|      * | ||||
|      * @param data      需要加密的参数 | ||||
|      * @param secretKey 自定义密钥字符串 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static String encrypt(String data, String secretKey) { | ||||
|         //将自定义密钥转为字节型
 | ||||
|         byte[] encryptKey = secretKey.getBytes(); | ||||
| 
 | ||||
|         String encryptedData = null; | ||||
|         //DES算法要求一个可信任的随机数源
 | ||||
|         SecureRandom sr = new SecureRandom(); | ||||
|         DESKeySpec deskey = new DESKeySpec(encryptKey); | ||||
| 
 | ||||
|         //创建一个密钥工厂,然后用它把DESKeySpec转换成一个SecretKey对象
 | ||||
|         SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM); | ||||
|         SecretKey key = keyFactory.generateSecret(deskey); | ||||
| 
 | ||||
|         //加密对象
 | ||||
|         Cipher cipher = Cipher.getInstance(DES_ALGORITHM); | ||||
|         cipher.init(Cipher.ENCRYPT_MODE, key, sr); | ||||
| 
 | ||||
|         //加密,把字节数组编码成字符串
 | ||||
|         encryptedData = new BASE64Encoder().encode(cipher.doFinal(data.getBytes())); | ||||
| 
 | ||||
|         return encryptedData; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 解密字符串 | ||||
|      * | ||||
|      * @param secretData 需要解密的参数 | ||||
|      * @param secretKey  自定义密钥字符串 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     public static String decrypt(String secretData, String secretKey) { | ||||
|         //将自定义密钥转为字节型
 | ||||
|         byte[] encryptKey = secretKey.getBytes(); | ||||
| 
 | ||||
|         String decryptedData = null; | ||||
| 
 | ||||
|         //DES算法要求有一个可信任的随机数源
 | ||||
|         SecureRandom sr = new SecureRandom(); | ||||
|         DESKeySpec desKey = new DESKeySpec(encryptKey); | ||||
| 
 | ||||
|         //创建一个密钥工厂,把DESKeySpec转换成一个SecretKey对象
 | ||||
|         SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM); | ||||
|         SecretKey key = keyFactory.generateSecret(desKey); | ||||
| 
 | ||||
|         //解密对象
 | ||||
|         Cipher cipher = Cipher.getInstance(DES_ALGORITHM); | ||||
|         cipher.init(Cipher.DECRYPT_MODE, key, sr); | ||||
| 
 | ||||
|         //字符串解码 ——> 字节数组,并解密
 | ||||
|         decryptedData = new String(cipher.doFinal(new BASE64Decoder().decodeBuffer(secretData))); | ||||
|         return decryptedData; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,126 @@ | ||||
| package com.zhengqing.common.core.util; | ||||
| 
 | ||||
| import cn.hutool.core.lang.Snowflake; | ||||
| import cn.hutool.core.net.NetUtil; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
| import com.zhengqing.common.base.exception.MyException; | ||||
| import com.zhengqing.common.base.util.MyDateUtil; | ||||
| import com.zhengqing.common.redis.constant.RedisConstant; | ||||
| import com.zhengqing.common.redis.util.RedisUtil; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import javax.annotation.PostConstruct; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * <p> Hutool之雪花算法生成唯一ID配置 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 可参考 https://www.bookstack.cn/read/hutool/bfd2d43bcada297e.md
 | ||||
|  * @date 2021/10/29 16:57 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Component | ||||
| public class IdGeneratorUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 终端ID | ||||
|      */ | ||||
|     private static long workerId = 0; | ||||
|     /** | ||||
|      * 数据中心ID | ||||
|      */ | ||||
|     private static long datacenterId = 1; | ||||
| 
 | ||||
|     private static Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId); | ||||
| 
 | ||||
|     @PostConstruct | ||||
|     public void init() { | ||||
|         try { | ||||
|             workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()); | ||||
|             log.info("当前机器的IP:[{}], workerId:[{}]", NetUtil.getLocalhostStr(), workerId); | ||||
|         } catch (Exception e) { | ||||
|             log.error("获取当前机器workerId 异常", e); | ||||
|             workerId = NetUtil.getLocalhostStr().hashCode(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 使用默认的 workerId 和 datacenterId | ||||
|      */ | ||||
|     public synchronized static long snowflakeId() { | ||||
|         return snowflake.nextId(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 字符串类型 | ||||
|      */ | ||||
|     public static String nextStrId() { | ||||
|         return String.valueOf(snowflakeId()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 使用自定义的 workerId 和 datacenterId | ||||
|      */ | ||||
|     public synchronized static long snowflakeId(long workerId, long datacenterId) { | ||||
|         return IdUtil.getSnowflake(workerId, datacenterId).nextId(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 使用redis确保分布式系统主键ID唯一性 | ||||
|      * | ||||
|      * @return ID | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/7/4 12:47 | ||||
|      */ | ||||
|     public synchronized static long nextId() { | ||||
|         long id = snowflake.nextId(); | ||||
|         String key = RedisConstant.ID_GENERATE_KEY_PREFIX + id; | ||||
|         if (!RedisUtil.setIfAbsent(key, String.valueOf(id))) { | ||||
|             // 记录下重复数据
 | ||||
|             RedisUtil.hPutIfAbsent(RedisConstant.ID_GENERATE_REPEAT_KEY, String.valueOf(id), MyDateUtil.nowStr()); | ||||
|             // 循环继续获取
 | ||||
|             return snowflakeId(); | ||||
|         } | ||||
|         // 设置3分钟过期
 | ||||
|         RedisUtil.expire(key, 3, TimeUnit.MINUTES); | ||||
|         return id; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 随机码生成 -- 12位英文大写字母+数字 | ||||
|      * | ||||
|      * @return 随机码 | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/4/20 16:48 | ||||
|      */ | ||||
|     public static String generateRandomCode() { | ||||
|         String key = RedisConstant.GENERATE_RANDOM_CODE_KEY; | ||||
|         String code = RandomUtil.randomStringUpper(12); | ||||
|         if (!RedisUtil.hPutIfAbsent(key, code, code)) { | ||||
|             // 如果重试次数超过5次则告警...
 | ||||
|             Long retryNum = RedisUtil.incrBy(RedisConstant.GENERATE_RANDOM_CODE_RETRY_NUM_KEY, 1); | ||||
|             if (retryNum > RedisConstant.GENERATE_RANDOM_CODE_MAX_RETRY_NUM) { | ||||
|                 // 先删除key,防止下次进来直接异常退出程序
 | ||||
|                 RedisUtil.delete(RedisConstant.GENERATE_RANDOM_CODE_RETRY_NUM_KEY); | ||||
|                 throw new MyException("随机码已用尽,请联系系统管理员!"); | ||||
|             } | ||||
| 
 | ||||
|             // 如果存在了,继续拿数据
 | ||||
|             return generateRandomCode(); | ||||
|         } | ||||
|         // 正常拿到数据返回
 | ||||
|         return code; | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         for (int i = 0; i < 10; i++) { | ||||
| //            log.info("ID: {}", IdGeneratorUtil.snowflakeId(i % 2, i % 2));
 | ||||
|             log.info("ID: {}", IdGeneratorUtil.snowflakeId()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <parent> | ||||
|         <artifactId>common</artifactId> | ||||
|         <groupId>com.zhengqing</groupId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <artifactId>db</artifactId> | ||||
| 
 | ||||
|     <name>${project.artifactId}</name> | ||||
|     <version>${revision}</version> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <properties> | ||||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>base</artifactId> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.zhengqing</groupId> | ||||
|             <artifactId>swagger</artifactId> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- 数据库相关 --> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-jdbc</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- mysql --> | ||||
|         <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> | ||||
|         <dependency> | ||||
|             <groupId>mysql</groupId> | ||||
|             <artifactId>mysql-connector-java</artifactId> | ||||
|             <version>8.0.25</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- 阿里druid数据库连接池 --> | ||||
|         <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> | ||||
|         <dependency> | ||||
|             <groupId>com.alibaba</groupId> | ||||
|             <artifactId>druid-spring-boot-starter</artifactId> | ||||
|             <version>1.2.11</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- 动态数据源 --> | ||||
|         <!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter --> | ||||
|         <dependency> | ||||
|             <groupId>com.baomidou</groupId> | ||||
|             <artifactId>dynamic-datasource-spring-boot-starter</artifactId> | ||||
|             <version>3.5.1</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- mybatis-plus --> | ||||
|         <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> | ||||
|         <dependency> | ||||
|             <groupId>com.baomidou</groupId> | ||||
|             <artifactId>mybatis-plus-boot-starter</artifactId> | ||||
|             <version>3.5.2</version> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
| </project> | ||||
| @ -0,0 +1,141 @@ | ||||
| //package com.zhengqing.common.db.config;
 | ||||
| //
 | ||||
| //import com.alibaba.druid.pool.DruidDataSource;
 | ||||
| //import com.alibaba.druid.support.http.StatViewServlet;
 | ||||
| //import com.alibaba.druid.support.http.WebStatFilter;
 | ||||
| //import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
 | ||||
| //import org.springframework.aop.support.DefaultPointcutAdvisor;
 | ||||
| //import org.springframework.aop.support.JdkRegexpMethodPointcut;
 | ||||
| //import org.springframework.beans.factory.annotation.Value;
 | ||||
| //import org.springframework.boot.context.properties.ConfigurationProperties;
 | ||||
| //import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | ||||
| //import org.springframework.boot.web.servlet.ServletRegistrationBean;
 | ||||
| //import org.springframework.context.annotation.Bean;
 | ||||
| //import org.springframework.context.annotation.Configuration;
 | ||||
| //import org.springframework.context.annotation.Scope;
 | ||||
| //import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 | ||||
| //
 | ||||
| //import javax.sql.DataSource;
 | ||||
| //import java.util.HashMap;
 | ||||
| //import java.util.Map;
 | ||||
| //
 | ||||
| ///**
 | ||||
| // * <p>
 | ||||
| // * Druid核心配置类 - 注册bean
 | ||||
| // * </p>
 | ||||
| // *
 | ||||
| // * @author zhengqingya
 | ||||
| // * @description Druid连接池监控平台 http://127.0.0.1:5000/druid/index.html
 | ||||
| // * @date 2019/12/19 18:20
 | ||||
| // */
 | ||||
| //@Configuration
 | ||||
| //public class DruidConfig {
 | ||||
| //
 | ||||
| //    @Value("${spring.datasource.druid.stat-view-servlet.login-username}")
 | ||||
| //    private String loginUsername;
 | ||||
| //
 | ||||
| //    @Value("${spring.datasource.druid.stat-view-servlet.login-password}")
 | ||||
| //    private String loginPassword;
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * 配置Druid监控
 | ||||
| //     */
 | ||||
| //    @Bean
 | ||||
| //    public ServletRegistrationBean druidServlet() {
 | ||||
| //        // 注册服务
 | ||||
| //        ServletRegistrationBean servletRegistrationBean =
 | ||||
| //                new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
 | ||||
| //        // IP白名单(为空表示,所有的都可以访问,多个IP的时候用逗号隔开)
 | ||||
| //        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
 | ||||
| //        // IP黑名单 (存在共同时,deny优先于allow)
 | ||||
| //        servletRegistrationBean.addInitParameter("deny", "127.0.0.2");
 | ||||
| //        // 设置控制台登录的用户名和密码
 | ||||
| //        servletRegistrationBean.addInitParameter("loginUsername", this.loginUsername);
 | ||||
| //        servletRegistrationBean.addInitParameter("loginPassword", this.loginPassword);
 | ||||
| //        // 是否能够重置数据
 | ||||
| //        servletRegistrationBean.addInitParameter("resetEnable", "false");
 | ||||
| //        return servletRegistrationBean;
 | ||||
| //    }
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * 配置web监控的filter
 | ||||
| //     */
 | ||||
| //    @Bean
 | ||||
| //    public FilterRegistrationBean webStatFilter() {
 | ||||
| //        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
 | ||||
| //        // 添加过滤规则
 | ||||
| //        Map<String, String> initParams = new HashMap<>(1);
 | ||||
| //        // 设置忽略请求
 | ||||
| //        initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
 | ||||
| //        filterRegistrationBean.setInitParameters(initParams);
 | ||||
| //        filterRegistrationBean.addInitParameter("profileEnable", "true");
 | ||||
| //        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
 | ||||
| //        filterRegistrationBean.addInitParameter("principalSessionName", "");
 | ||||
| //        filterRegistrationBean.addInitParameter("aopPatterns", "com.example.demo.service");
 | ||||
| //        // 验证所有请求
 | ||||
| //        filterRegistrationBean.addUrlPatterns("/*");
 | ||||
| //        return filterRegistrationBean;
 | ||||
| //    }
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * 配置数据源 【 将所有前缀为spring.datasource下的配置项都加载到DataSource中 】
 | ||||
| //     */
 | ||||
| //    @Bean(name = "dataSource")
 | ||||
| //    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
 | ||||
| //    public DataSource dataSource() {
 | ||||
| //        return new DruidDataSource();
 | ||||
| //    }
 | ||||
| //
 | ||||
| //    // @Bean(name = "dataSource2")
 | ||||
| //    // @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db-test")
 | ||||
| //    // public DataSource dataSource2() {
 | ||||
| //    // return new DruidDataSource();
 | ||||
| //    // }
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * 配置事物管理器
 | ||||
| //     */
 | ||||
| //    @Bean(name = "transactionManager")
 | ||||
| //    public DataSourceTransactionManager transactionManager() {
 | ||||
| //        return new DataSourceTransactionManager(this.dataSource());
 | ||||
| //    }
 | ||||
| //
 | ||||
| //    // @Bean(name = "transactionManager2")
 | ||||
| //    // public DataSourceTransactionManager transactionManager2() {
 | ||||
| //    // return new DataSourceTransactionManager(dataSource2());
 | ||||
| //    // }
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * ↓↓↓↓↓↓ 配置spring监控 ↓↓↓↓↓↓
 | ||||
| //     * DruidStatInterceptor: druid提供的拦截器
 | ||||
| //     */
 | ||||
| //    @Bean
 | ||||
| //    public DruidStatInterceptor druidStatInterceptor() {
 | ||||
| //        DruidStatInterceptor dsInterceptor = new DruidStatInterceptor();
 | ||||
| //        return dsInterceptor;
 | ||||
| //    }
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * 使用正则表达式配置切点
 | ||||
| //     */
 | ||||
| //    @Bean
 | ||||
| //    @Scope("prototype")
 | ||||
| //    public JdkRegexpMethodPointcut druidStatPointcut() {
 | ||||
| //        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
 | ||||
| //        pointcut.setPattern("com.zhengqing.*.api.*");
 | ||||
| //        return pointcut;
 | ||||
| //    }
 | ||||
| //
 | ||||
| //    /**
 | ||||
| //     * DefaultPointcutAdvisor类定义advice及 pointcut 属性。advice指定使用的通知方式,也就是druid提供的DruidStatInterceptor类,pointcut指定切入点
 | ||||
| //     */
 | ||||
| //    @Bean
 | ||||
| //    public DefaultPointcutAdvisor druidStatAdvisor(DruidStatInterceptor druidStatInterceptor,
 | ||||
| //                                                   JdkRegexpMethodPointcut druidStatPointcut) {
 | ||||
| //        DefaultPointcutAdvisor defaultPointAdvisor = new DefaultPointcutAdvisor();
 | ||||
| //        defaultPointAdvisor.setPointcut(druidStatPointcut);
 | ||||
| //        defaultPointAdvisor.setAdvice(druidStatInterceptor);
 | ||||
| //        return defaultPointAdvisor;
 | ||||
| //    }
 | ||||
| //
 | ||||
| //}
 | ||||
| @ -0,0 +1,88 @@ | ||||
| package com.zhengqing.common.db.config.mybatis; | ||||
| 
 | ||||
| import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | ||||
| import com.zhengqing.common.base.constant.BaseConstant; | ||||
| import com.zhengqing.common.base.context.JwtCustomUserContext; | ||||
| import com.zhengqing.common.base.enums.AuthSourceEnum; | ||||
| import com.zhengqing.common.base.model.bo.JwtCustomUserBO; | ||||
| import com.zhengqing.common.db.constant.MybatisConstant; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.ibatis.reflection.MetaObject; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * MyBatisPlus自定义字段自动填充处理类 - 实体类中使用 @TableField注解 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description 注意前端传值时要为null | ||||
|  * @date 2019/8/18 1:46 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Component | ||||
| public class MyMetaObjectHandler implements MetaObjectHandler { | ||||
| 
 | ||||
|     /** | ||||
|      * 创建 | ||||
|      */ | ||||
|     @Override | ||||
|     public void insertFill(MetaObject metaObject) { | ||||
|         // 用户id
 | ||||
|         Long userId = this.getUserId(); | ||||
|         // 当前时间
 | ||||
|         Date nowDate = new Date(); | ||||
| 
 | ||||
|         // 判断对象中是否存在该参数,如果存在则插入数据
 | ||||
|         if (metaObject.hasGetter(MybatisConstant.IS_DELETED)) { | ||||
|             this.setFieldValByName(MybatisConstant.IS_DELETED, false, metaObject); | ||||
|         } | ||||
|         if (metaObject.hasGetter(MybatisConstant.CREATE_BY)) { | ||||
|             this.setFieldValByName(MybatisConstant.CREATE_BY, userId, metaObject); | ||||
|         } | ||||
|         if (metaObject.hasGetter(MybatisConstant.CREATE_TIME)) { | ||||
|             this.setFieldValByName(MybatisConstant.CREATE_TIME, nowDate, metaObject); | ||||
|         } | ||||
|         if (metaObject.hasGetter(MybatisConstant.UPDATE_BY)) { | ||||
|             this.setFieldValByName(MybatisConstant.UPDATE_BY, userId, metaObject); | ||||
|         } | ||||
|         if (metaObject.hasGetter(MybatisConstant.UPDATE_TIME)) { | ||||
|             this.setFieldValByName(MybatisConstant.UPDATE_TIME, nowDate, metaObject); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 更新 | ||||
|      */ | ||||
|     @Override | ||||
|     public void updateFill(MetaObject metaObject) { | ||||
|         if (metaObject.hasGetter(MybatisConstant.UPDATE_BY)) { | ||||
|             this.setFieldValByName(MybatisConstant.UPDATE_BY, this.getUserId(), metaObject); | ||||
|         } | ||||
|         if (metaObject.hasGetter(MybatisConstant.UPDATE_TIME)) { | ||||
|             this.setFieldValByName(MybatisConstant.UPDATE_TIME, new Date(), metaObject); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取上下文中的用户id | ||||
|      * | ||||
|      * @return 用户id | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/7/8 18:15 | ||||
|      */ | ||||
|     private Long getUserId() { | ||||
|         JwtCustomUserBO jwtCustomUserBO = JwtCustomUserContext.get(); | ||||
|         if (jwtCustomUserBO != null) { | ||||
|             if (AuthSourceEnum.B.getValue().equals(jwtCustomUserBO.getAuthSource())) { | ||||
|                 return Long.valueOf(jwtCustomUserBO.getSysUserId()); | ||||
|             } else { | ||||
|                 return Long.valueOf(jwtCustomUserBO.getUmsUserId()); | ||||
|             } | ||||
|         } | ||||
|         return Long.valueOf(BaseConstant.DEFAULT_CONTEXT_KEY_USER_ID); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,157 @@ | ||||
| package com.zhengqing.common.db.config.mybatis; | ||||
| 
 | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import com.baomidou.mybatisplus.annotation.DbType; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import com.zhengqing.common.base.context.TenantIdContext; | ||||
| import com.zhengqing.common.db.config.mybatis.data.permission.first.DataPermissionInterceptor; | ||||
| import com.zhengqing.common.db.config.mybatis.data.permission.second.MyDataPermissionHandler; | ||||
| import com.zhengqing.common.db.config.mybatis.data.permission.second.MyDataPermissionInterceptor; | ||||
| import com.zhengqing.common.db.config.mybatis.plugins.LogicDeleteInterceptor; | ||||
| import com.zhengqing.common.db.config.mybatis.plugins.SqlLogInterceptor; | ||||
| import net.sf.jsqlparser.expression.Expression; | ||||
| import net.sf.jsqlparser.expression.LongValue; | ||||
| import org.mybatis.spring.annotation.MapperScan; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
| 
 | ||||
| import javax.sql.DataSource; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * MybatisPlus配置类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2019/8/23 9:46 | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableTransactionManagement | ||||
| @MapperScan({"com.zhengqing.*.mapper", "com.zhengqing.*.*.mapper"}) | ||||
| public class MybatisPlusConfig { | ||||
| 
 | ||||
|     /** | ||||
|      * 需要设置租户ID的表 | ||||
|      */ | ||||
|     public static Set<String> TENANT_ID_TABLE = new HashSet<>(); | ||||
| 
 | ||||
|     /** | ||||
|      * 需要逻辑删除的表 | ||||
|      */ | ||||
|     public static Set<String> LOGIC_DELETE_TABLE = new HashSet<>(); | ||||
| 
 | ||||
|     static { | ||||
| //        TENANT_ID_TABLE.add("t_demo");
 | ||||
|         LOGIC_DELETE_TABLE.add("t_sys_menu"); | ||||
|         LOGIC_DELETE_TABLE.add("t_sys_user"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * mybatis-plus分页插件 | ||||
|      * 文档:https://baomidou.com/pages/2976a3/#spring-boot
 | ||||
|      */ | ||||
|     @Bean | ||||
|     public MybatisPlusInterceptor mybatisPlusInterceptor() { | ||||
|         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | ||||
|         /** | ||||
|          * 1、mybatis-plus多租户插件 | ||||
|          * 文档:https://baomidou.com/pages/aef2f2/#tenantlineinnerinterceptor
 | ||||
|          */ | ||||
|         interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { | ||||
|             @Override | ||||
|             public Expression getTenantId() { | ||||
|                 return new LongValue(TenantIdContext.getTenantId()); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public String getTenantIdColumn() { | ||||
|                 return "tenant_id"; | ||||
|             } | ||||
| 
 | ||||
|             // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
 | ||||
|             @Override | ||||
|             public boolean ignoreTable(String tableName) { | ||||
|                 if (!TENANT_ID_TABLE.contains(tableName)) { | ||||
|                     // 不需要租户id
 | ||||
|                     return true; | ||||
|                 } | ||||
|                 Boolean tenantIdFlag = TenantIdContext.getFlag(); | ||||
|                 Assert.notNull(tenantIdFlag, "租户id不能为空!"); | ||||
|                 return !tenantIdFlag; | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|         // tips: 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
 | ||||
| 
 | ||||
| 
 | ||||
|         /** | ||||
|          * 2、添加数据权限插件 | ||||
|          */ | ||||
|         MyDataPermissionInterceptor dataPermissionInterceptor = new MyDataPermissionInterceptor(); | ||||
|         // 添加自定义的数据权限处理器
 | ||||
|         dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler()); | ||||
|         interceptor.addInnerInterceptor(dataPermissionInterceptor); | ||||
| 
 | ||||
|         /** | ||||
|          * 3、mybatis-plus分页插件 | ||||
|          * 文档:https://baomidou.com/pages/2976a3/#spring-boot
 | ||||
|          */ | ||||
|         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); | ||||
|         return interceptor; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * mybatis-plus SQL性能分析插件【生产环境可以关闭】 性能分析拦截器,用于输出每条 SQL 语句及其执行时间 【注:3.2.0+ 已移除`PerformanceInterceptor`】 | ||||
|      */ | ||||
|     // @Bean
 | ||||
|     // @Profile({"dev", "test"}) // 设置 dev test 环境开启
 | ||||
|     // public PerformanceInterceptor performanceInterceptor() {
 | ||||
|     // SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
 | ||||
|     // performanceInterceptor.setMaxTime(3000);
 | ||||
|     // SQL是否格式化 默认false
 | ||||
|     // performanceInterceptor.setFormat(true);
 | ||||
|     // return new PerformanceInterceptor();
 | ||||
|     // }
 | ||||
| 
 | ||||
|     /** | ||||
|      * sql 日志 | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnProperty( | ||||
|             value = "smallboot.mybatis-plus-sql-log", | ||||
|             havingValue = "true", | ||||
|             // true表示缺少此配置属性时也会加载该bean
 | ||||
|             matchIfMissing = true) | ||||
|     public SqlLogInterceptor sqlLogInterceptor() { | ||||
|         return new SqlLogInterceptor(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 数据权限插件 | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public DataPermissionInterceptor dataScopeInterceptor(DataSource dataSource) { | ||||
|         return new DataPermissionInterceptor(dataSource); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 逻辑删除插件 | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public LogicDeleteInterceptor logicDeleteInterceptor() { | ||||
|         return new LogicDeleteInterceptor(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,160 @@ | ||||
| package com.zhengqing.common.db.config.mybatis.data.permission.first; | ||||
| 
 | ||||
| import com.baomidou.mybatisplus.core.toolkit.PluginUtils; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.SneakyThrows; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import net.sf.jsqlparser.JSQLParserException; | ||||
| import net.sf.jsqlparser.expression.Alias; | ||||
| import net.sf.jsqlparser.expression.operators.conditional.AndExpression; | ||||
| import net.sf.jsqlparser.parser.CCJSqlParserManager; | ||||
| import net.sf.jsqlparser.parser.CCJSqlParserUtil; | ||||
| import net.sf.jsqlparser.schema.Table; | ||||
| import net.sf.jsqlparser.statement.select.PlainSelect; | ||||
| import net.sf.jsqlparser.statement.select.Select; | ||||
| import net.sf.jsqlparser.statement.select.SelectBody; | ||||
| import net.sf.jsqlparser.statement.select.SetOperationList; | ||||
| import org.apache.ibatis.executor.statement.StatementHandler; | ||||
| import org.apache.ibatis.mapping.BoundSql; | ||||
| import org.apache.ibatis.mapping.MappedStatement; | ||||
| import org.apache.ibatis.mapping.SqlCommandType; | ||||
| import org.apache.ibatis.plugin.*; | ||||
| import org.apache.ibatis.reflection.MetaObject; | ||||
| import org.apache.ibatis.reflection.SystemMetaObject; | ||||
| 
 | ||||
| import javax.sql.DataSource; | ||||
| import java.io.StringReader; | ||||
| import java.sql.Connection; | ||||
| import java.util.List; | ||||
| import java.util.Properties; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * <p> mybatis-plus 数据权限插件 </p> | ||||
|  * | ||||
|  * @author zhengqingya | ||||
|  * @description | ||||
|  * @date 2022/1/12 14:36 | ||||
|  */ | ||||
| @Slf4j | ||||
| @AllArgsConstructor | ||||
| @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) | ||||
| public class DataPermissionInterceptor implements Interceptor { | ||||
| 
 | ||||
|     /** | ||||
|      * 数据源 | ||||
|      */ | ||||
|     private DataSource dataSource; | ||||
| 
 | ||||
|     @Override | ||||
|     public Object intercept(Invocation invocation) throws Throwable { | ||||
|         StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); | ||||
|         MetaObject metaObject = SystemMetaObject.forObject(statementHandler); | ||||
|         // 先判断是不是SELECT操作 不是直接过滤
 | ||||
|         MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); | ||||
|         if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) { | ||||
|             return invocation.proceed(); | ||||
|         } | ||||
|         BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); | ||||
|         // 执行的SQL语句
 | ||||
|         String originalSql = boundSql.getSql(); | ||||
|         // SQL语句的参数
 | ||||
|         Object parameterObject = boundSql.getParameterObject(); | ||||
| 
 | ||||
|         // TODO 这里对执行SQL进行自定义处理...
 | ||||
| //        String finalSql = this.handleSql(originalSql);
 | ||||
| //        System.err.println("数据权限处理过后的SQL: " + finalSql);
 | ||||
| 
 | ||||
|         metaObject.setValue("delegate.boundSql.sql", originalSql); | ||||
|         return invocation.proceed(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 改写SQL | ||||
|      * {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor} | ||||
|      * | ||||
|      * @param originalSql 执行的SQL语句 | ||||
|      * @return 处理后的SQL | ||||
|      * @author zhengqingya | ||||
|      * @date 2022/1/13 10:43 | ||||
|      */ | ||||
|     private String handleSql(String originalSql) throws JSQLParserException { | ||||
|         CCJSqlParserManager parserManager = new CCJSqlParserManager(); | ||||
|         Select select = (Select) parserManager.parse(new StringReader(originalSql)); | ||||
|         SelectBody selectBody = select.getSelectBody(); | ||||
|         if (selectBody instanceof PlainSelect) { | ||||
|             this.setWhere((PlainSelect) selectBody); | ||||
|         } else if (selectBody instanceof SetOperationList) { | ||||
|             SetOperationList setOperationList = (SetOperationList) selectBody; | ||||
|             List<SelectBody> selectBodyList = setOperationList.getSelects(); | ||||
|             selectBodyList.forEach(s -> this.setWhere((PlainSelect) s)); | ||||
|         } | ||||
|         return select.toString(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 设置 where 条件  --  使用CCJSqlParser将原SQL进行解析并改写 | ||||
|      * | ||||
|      * @param plainSelect 查询对象 | ||||
|      */ | ||||
|     @SneakyThrows(Exception.class) | ||||
|     protected void setWhere(PlainSelect plainSelect) { | ||||
|         Table fromItem = (Table) plainSelect.getFromItem(); | ||||
|         // 有别名用别名,无别名用表名,防止字段冲突报错
 | ||||
|         Alias fromItemAlias = fromItem.getAlias(); | ||||
|         String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName(); | ||||
|         // 构建子查询 -- 数据权限过滤SQL
 | ||||
|         String dataPermissionSql = mainTableName + ".create_by in ( 1, 2, 3 )"; | ||||
|         if (plainSelect.getWhere() == null) { | ||||
|             plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(dataPermissionSql)); | ||||
|         } else { | ||||
|             plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), CCJSqlParserUtil.parseCondExpression(dataPermissionSql))); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 生成拦截对象的代理 | ||||
|      * | ||||
|      * @param target 目标对象 | ||||
|      * @return 代理对象 | ||||
|      */ | ||||
|     @Override | ||||
|     public Object plugin(Object target) { | ||||
|         if (target instanceof StatementHandler) { | ||||
|             return Plugin.wrap(target, this); | ||||
|         } | ||||
|         return target; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * mybatis配置的属性 | ||||
|      * | ||||
|      * @param properties mybatis配置的属性 | ||||
|      */ | ||||
|     @Override | ||||
|     public void setProperties(Properties properties) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 查找参数是否包括DataScope对象 | ||||
|      * | ||||
|      * @param parameterObj 参数列表 | ||||
|      * @return DataScope | ||||
|      */ | ||||
|     //    private DataScope findDataScopeObject(Object parameterObj) {
 | ||||
|     //        if (parameterObj instanceof DataScope) {
 | ||||
|     //            return (DataScope) parameterObj;
 | ||||
|     //        } else if (parameterObj instanceof Map) {
 | ||||
|     //            for (Object val : ((Map<?, ?>) parameterObj).values()) {
 | ||||
|     //                if (val instanceof DataScope) {
 | ||||
|     //                    return (DataScope) val;
 | ||||
|     //                }
 | ||||
|     //            }
 | ||||
|     //        }
 | ||||
|     //        return null;
 | ||||
|     //    }
 | ||||
| 
 | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue
	
	 zhengqingya
						zhengqingya