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