feat: initial iShare project code
This commit is contained in:
38
pigx-common/pigx-common-audit/pom.xml
Normal file
38
pigx-common/pigx-common-audit/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pigx-common-audit</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>pigx 数据审计相关工具类</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 数据对比实现 -->
|
||||
<dependency>
|
||||
<groupId>org.javers</groupId>
|
||||
<artifactId>javers-core</artifactId>
|
||||
</dependency>
|
||||
<!-- 切面依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<!-- 提供日志插入 API -->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>as-upms-api</artifactId>
|
||||
</dependency>
|
||||
<!-- 获取上下文的操作用户 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.pig4cloud.pigx.common.audit;
|
||||
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteAuditLogService;
|
||||
import com.pig4cloud.pigx.common.audit.aop.AuditAspect;
|
||||
import com.pig4cloud.pigx.common.audit.handle.DefaultAuditLogHandle;
|
||||
import com.pig4cloud.pigx.common.audit.handle.IAuditLogHandle;
|
||||
import com.pig4cloud.pigx.common.audit.handle.ICompareHandle;
|
||||
import com.pig4cloud.pigx.common.audit.handle.JavesCompareHandle;
|
||||
import com.pig4cloud.pigx.common.audit.support.SpelParser;
|
||||
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 审计自动配置类
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*/
|
||||
@EnableAsync
|
||||
@AutoConfiguration
|
||||
@Import({ AuditAspect.class, SpelParser.class })
|
||||
public class AuditAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 默认注入 javers 的比较器实现
|
||||
* @param auditNameHandleOptional 注入审计用户来源
|
||||
* @return ICompareHandle
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ICompareHandle compareHandle(Optional<IAuditLogHandle> auditNameHandleOptional) {
|
||||
return new JavesCompareHandle(auditNameHandleOptional);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的审计日志存储策略
|
||||
* @return DefaultAuditLogHandle
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public IAuditLogHandle auditLogHandle(RemoteAuditLogService logService, KeyStrResolver tenantKeyStrResolver) {
|
||||
return new DefaultAuditLogHandle(logService, tenantKeyStrResolver);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.pig4cloud.pigx.common.audit.annotation;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
*
|
||||
* 记需要进行审计的方法
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Audit {
|
||||
|
||||
/**
|
||||
* 此审计的名称
|
||||
* @return 审计名称
|
||||
*/
|
||||
String name();
|
||||
|
||||
@AliasFor("spel")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 默认查询的表达式
|
||||
* @return string
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String spel() default "";
|
||||
|
||||
/**
|
||||
* 查询原有结果的表达式,如果为空取 spel()
|
||||
* @return string
|
||||
*/
|
||||
String oldVal() default "";
|
||||
|
||||
/**
|
||||
* 查询编辑后的表达式,如果为空取 spel()
|
||||
* @return string
|
||||
*/
|
||||
String newVal() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.common.audit.aop;
|
||||
|
||||
import com.pig4cloud.pigx.common.audit.annotation.Audit;
|
||||
import com.pig4cloud.pigx.common.audit.handle.ICompareHandle;
|
||||
import com.pig4cloud.pigx.common.audit.support.SpelParser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*
|
||||
* 拦截被@Audit注解标记的方法,并记录前后变化值
|
||||
*/
|
||||
|
||||
@Aspect
|
||||
@RequiredArgsConstructor
|
||||
public class AuditAspect {
|
||||
|
||||
private final ICompareHandle compareHandle;
|
||||
|
||||
@Around("@annotation(audit)")
|
||||
public Object auditLog(ProceedingJoinPoint joinPoint, Audit audit) throws Throwable {
|
||||
// 获取变更之前的结果
|
||||
Object oldVal = SpelParser.parser(joinPoint,
|
||||
StringUtils.hasText(audit.oldVal()) ? audit.oldVal() : audit.spel());
|
||||
Object result = joinPoint.proceed();
|
||||
|
||||
compareHandle.compare(oldVal, joinPoint, audit);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.pig4cloud.pigx.common.audit.handle;
|
||||
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysAuditLog;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteAuditLogService;
|
||||
import com.pig4cloud.pigx.common.audit.annotation.Audit;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.javers.core.Changes;
|
||||
import org.javers.core.diff.Change;
|
||||
import org.javers.core.diff.changetype.ValueChange;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 默认的审计日志存储
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2023/2/27
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultAuditLogHandle implements IAuditLogHandle {
|
||||
|
||||
private final RemoteAuditLogService remoteLogService;
|
||||
|
||||
private final KeyStrResolver tenantKeyStrResolver;
|
||||
|
||||
@Override
|
||||
public void handle(Audit audit, Changes changes) {
|
||||
// 如果变更项为空则不进行审计
|
||||
if (changes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前操作人
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
// 如果获取不到授权信息、或者没有身份信息的接口 直接跳过处理
|
||||
if (Objects.isNull(authentication) || !authentication.isAuthenticated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SysAuditLog> auditLogList = new ArrayList<>();
|
||||
for (Change change : changes) {
|
||||
ValueChange valueChange = (ValueChange) change;
|
||||
|
||||
SysAuditLog auditLog = new SysAuditLog();
|
||||
auditLog.setAuditName(audit.name());
|
||||
auditLog.setAuditField(valueChange.getPropertyName()); // 修改的字段名称
|
||||
|
||||
if (Objects.nonNull(valueChange.getLeft())) {
|
||||
auditLog.setBeforeVal(valueChange.getLeft().toString()); // 更改前的值
|
||||
}
|
||||
if (Objects.nonNull(valueChange.getRight())) {
|
||||
auditLog.setAfterVal(valueChange.getRight().toString()); // getRight
|
||||
}
|
||||
|
||||
auditLog.setCreateBy(authentication.getName()); // 操作人
|
||||
auditLog.setCreateTime(LocalDateTime.now()); // 操作时间
|
||||
auditLog.setTenantId(Long.parseLong(tenantKeyStrResolver.key())); // 设置操作所属租户
|
||||
|
||||
auditLogList.add(auditLog);
|
||||
}
|
||||
|
||||
// 异步保存日志,提升性能
|
||||
IAuditLogHandle auditLogHandle = SpringContextHolder.getBean(IAuditLogHandle.class);
|
||||
auditLogHandle.asyncSend(auditLogList);
|
||||
}
|
||||
|
||||
@Async
|
||||
public void asyncSend(List<SysAuditLog> auditLogList) {
|
||||
remoteLogService.saveLog(auditLogList, SecurityConstants.FROM_IN);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.pig4cloud.pigx.common.audit.handle;
|
||||
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysAuditLog;
|
||||
import com.pig4cloud.pigx.common.audit.annotation.Audit;
|
||||
import org.javers.core.Changes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*
|
||||
* 审计日志处理器
|
||||
*/
|
||||
public interface IAuditLogHandle {
|
||||
|
||||
void handle(Audit audit, Changes changes);
|
||||
|
||||
void asyncSend(List<SysAuditLog> auditLogList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.pig4cloud.pigx.common.audit.handle;
|
||||
|
||||
import com.pig4cloud.pigx.common.audit.annotation.Audit;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.javers.core.Changes;
|
||||
|
||||
/**
|
||||
* 比较器抽象类
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*/
|
||||
public interface ICompareHandle {
|
||||
|
||||
/**
|
||||
* 比较两个对象是否变更,及其变更后如何审计
|
||||
* @param oldVal 原有值
|
||||
* @param newVal 变更后值
|
||||
*/
|
||||
Changes compare(Object oldVal, ProceedingJoinPoint joinPoint, Audit audit);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.pig4cloud.pigx.common.audit.handle;
|
||||
|
||||
import com.pig4cloud.pigx.common.audit.annotation.Audit;
|
||||
import com.pig4cloud.pigx.common.audit.support.DataAuditor;
|
||||
import com.pig4cloud.pigx.common.audit.support.SpelParser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.javers.core.Changes;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* javers 比较实现
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JavesCompareHandle implements ICompareHandle {
|
||||
|
||||
private final Optional<IAuditLogHandle> auditLogHandleOptional;
|
||||
|
||||
/**
|
||||
* 比较两个对象是否变更,及其变更后如何审计
|
||||
* @param oldVal 原有值
|
||||
*/
|
||||
@Override
|
||||
public Changes compare(Object oldVal, ProceedingJoinPoint joinPoint, Audit audit) {
|
||||
Object newVal = SpelParser.parser(joinPoint,
|
||||
StringUtils.hasText(audit.newVal()) ? audit.newVal() : audit.spel());
|
||||
Changes compare = DataAuditor.compare(oldVal, newVal);
|
||||
auditLogHandleOptional.ifPresent(handle -> handle.handle(audit, compare));
|
||||
return compare;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.pig4cloud.pigx.common.audit.support;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.javers.core.Changes;
|
||||
import org.javers.core.Javers;
|
||||
import org.javers.core.JaversBuilder;
|
||||
import org.javers.core.diff.Diff;
|
||||
|
||||
import static org.javers.core.diff.ListCompareAlgorithm.LEVENSHTEIN_DISTANCE;
|
||||
|
||||
/**
|
||||
* javers 审计工具
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*/
|
||||
@UtilityClass
|
||||
public class DataAuditor {
|
||||
|
||||
private final Javers javers = JaversBuilder.javers().withListCompareAlgorithm(LEVENSHTEIN_DISTANCE).build();
|
||||
|
||||
public Changes compare(Object newObj, Object oldObj) {
|
||||
Diff compare = javers.compare(newObj, oldObj);
|
||||
return compare.getChanges();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.pig4cloud.pigx.common.audit.support;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
* 表达式处理器
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2023/2/26
|
||||
*/
|
||||
public class SpelParser implements ApplicationContextAware {
|
||||
|
||||
private final static SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
public static Object parser(ProceedingJoinPoint joinPoint, String spel) {
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
|
||||
for (int i = 0; i < joinPoint.getArgs().length; i++) {
|
||||
String paramName = ((MethodSignature) joinPoint.getSignature()).getParameterNames()[i];
|
||||
context.setVariable(paramName, joinPoint.getArgs()[i]);
|
||||
}
|
||||
return parser.parseExpression(spel).getValue(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpelParser.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.pig4cloud.pigx.common.audit.AuditAutoConfiguration
|
||||
446
pigx-common/pigx-common-bom/pom.xml
Normal file
446
pigx-common/pigx-common-bom/pom.xml
Normal file
@@ -0,0 +1,446 @@
|
||||
<?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>
|
||||
<groupId>group.springframework</groupId>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<version>2021.0.7</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-bom</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>${pigx.version}</version>
|
||||
<description>pigx 公共版本控制</description>
|
||||
|
||||
<properties>
|
||||
<pigx.version>5.2.0</pigx.version>
|
||||
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
|
||||
<mybatis-plus-join.version>1.4.5</mybatis-plus-join.version>
|
||||
<dynamic-ds.version>4.1.3</dynamic-ds.version>
|
||||
<druid.version>1.2.19</druid.version>
|
||||
<hutool.version>5.8.21</hutool.version>
|
||||
<mysql.connector.version>8.0.32</mysql.connector.version>
|
||||
<oracle.version>21.3.0.0</oracle.version>
|
||||
<sqlserver.version>8.4.1.jre8</sqlserver.version>
|
||||
<dm.version>8.1.2.192</dm.version>
|
||||
<highgo.version>6.2.0</highgo.version>
|
||||
<knife4j.version>3.0.3</knife4j.version>
|
||||
<swagger.core.version>2.2.8</swagger.core.version>
|
||||
<springdoc.version>1.6.15</springdoc.version>
|
||||
<mp.weixin.version>4.4.0</mp.weixin.version>
|
||||
<ijpay.version>2.8.0</ijpay.version>
|
||||
<groovy.version>3.0.3</groovy.version>
|
||||
<jsoup.version>1.13.1</jsoup.version>
|
||||
<aviator.version>5.3.3</aviator.version>
|
||||
<flowable.version>6.8.0</flowable.version>
|
||||
<security.oauth.version>2.3.6.RELEASE</security.oauth.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<xxl.job.version>2.3.0</xxl.job.version>
|
||||
<aliyun.version>3.0.52.ALL</aliyun.version>
|
||||
<aws.version>1.12.261</aws.version>
|
||||
<javers.version>6.10.0</javers.version>
|
||||
<seata.version>1.6.1</seata.version>
|
||||
<asm.version>7.1</asm.version>
|
||||
<log4j2.version>2.17.1</log4j2.version>
|
||||
<javaformat.plugin.version>0.0.23</javaformat.plugin.version>
|
||||
<docker.plugin.version>0.33.0</docker.plugin.version>
|
||||
<cloud.plugin.version>1.0.0</cloud.plugin.version>
|
||||
<sentinel.version>1.8.4</sentinel.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-audit</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-data</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-gateway</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-gray</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-datasource</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-idempotent</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-job</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-log</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-oss</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-security</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-sentinel</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-feign</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-sequence</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-swagger</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-seata</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-xss</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-websocket</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-encrypt-api</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-excel</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>as-upms-api</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>as-app-server-api</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-flow-task-api</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-flow-engine-api</artifactId>
|
||||
<version>${pigx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>${asm.version}</version>
|
||||
</dependency>
|
||||
<!-- seata kryo 序列化-->
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-serializer-kryo</artifactId>
|
||||
<version>${seata.version}</version>
|
||||
</dependency>
|
||||
<!--mybatis plus extension,包含了mybatis plus core-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-extension</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||
<version>${dynamic-ds.version}</version>
|
||||
</dependency>
|
||||
<!-- 连表查询依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-boot-starter</artifactId>
|
||||
<version>${mybatis-plus-join.version}</version>
|
||||
</dependency>
|
||||
<!-- 连表查询依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-annotation</artifactId>
|
||||
<version>${mybatis-plus-join.version}</version>
|
||||
</dependency>
|
||||
<!-- druid 连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<!--mysql 驱动-->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>${mysql.connector.version}</version>
|
||||
</dependency>
|
||||
<!--oracle 驱动-->
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>${oracle.version}</version>
|
||||
</dependency>
|
||||
<!-- mssql -->
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>${sqlserver.version}</version>
|
||||
</dependency>
|
||||
<!--DM8-->
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>DmJdbcDriver18</artifactId>
|
||||
<version>${dm.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>DmDialect-for-hibernate5.3</artifactId>
|
||||
<version>${dm.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.highgo</groupId>
|
||||
<artifactId>HgdbJdbc</artifactId>
|
||||
<version>${highgo.version}</version>
|
||||
</dependency>
|
||||
<!--fastjson-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<!-- 对象对比工具-->
|
||||
<dependency>
|
||||
<groupId>org.javers</groupId>
|
||||
<artifactId>javers-core</artifactId>
|
||||
<version>${javers.version}</version>
|
||||
</dependency>
|
||||
<!--springdoc -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webmvc-core</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springboot</groupId>
|
||||
<artifactId>knife4j-openapi3-ui</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
<version>${swagger.core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger.core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webflux-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webflux-core</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-common</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-security</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<!--微信依赖-->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-mp</artifactId>
|
||||
<version>${mp.weixin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-cp</artifactId>
|
||||
<version>${mp.weixin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-common</artifactId>
|
||||
<version>${mp.weixin.version}</version>
|
||||
</dependency>
|
||||
<!--计算引擎-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.aviator</groupId>
|
||||
<artifactId>aviator</artifactId>
|
||||
<version>${aviator.version}</version>
|
||||
</dependency>
|
||||
<!--工作流依赖-->
|
||||
<dependency>
|
||||
<groupId>org.flowable</groupId>
|
||||
<artifactId>flowable-spring-boot-starter-process</artifactId>
|
||||
<version>${flowable.version}</version>
|
||||
</dependency>
|
||||
<!--支付相关SDK-->
|
||||
<dependency>
|
||||
<groupId>com.github.javen205</groupId>
|
||||
<artifactId>IJPay-WxPay</artifactId>
|
||||
<version>${ijpay.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.javen205</groupId>
|
||||
<artifactId>IJPay-AliPay</artifactId>
|
||||
<version>${ijpay.version}</version>
|
||||
</dependency>
|
||||
<!--定义groovy 版本-->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<version>${groovy.version}</version>
|
||||
</dependency>
|
||||
<!--稳定版本,替代spring security bom内置-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
<version>${security.oauth.version}</version>
|
||||
</dependency>
|
||||
<!--jsoup html 解析组件-->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>${jsoup.version}</version>
|
||||
</dependency>
|
||||
<!-- 指定 log4j 版本-->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-to-slf4j</artifactId>
|
||||
<version>${log4j2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-bom</artifactId>
|
||||
<version>${log4j2.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!--hutool bom-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-web-servlet</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<!-- 增加云效nexus示例仓库 (演示使用,可自行删除) -->
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>rdc-releases</id>
|
||||
<url>https://packages.aliyun.com/maven/repository/2161442-release-DcBZC1/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>rdc-snapshots</id>
|
||||
<url>https://packages.aliyun.com/maven/repository/2161442-snapshot-FzKqZK/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
56
pigx-common/pigx-common-core/pom.xml
Normal file
56
pigx-common/pigx-common-core/pom.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>pigx 公共工具类核心包</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
</dependency>
|
||||
<!--mvc 相关配置-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--server-api-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<!--hibernate-validator-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!--json模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
</dependency>
|
||||
<!--TTL-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>${ttl.version}</version>
|
||||
</dependency>
|
||||
<!--swagger 依赖-->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.pig4cloud.pigx.common.core.config;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.pig4cloud.pigx.common.core.jackson.PigxJavaTimeModule;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* JacksonConfig 配置时间转换规则
|
||||
* {@link com.pig4cloud.pigx.common.core.jackson.PigxJavaTimeModule}、默认时区等
|
||||
*
|
||||
* @author L.cm
|
||||
* @author lengleng
|
||||
* @author lishangbu
|
||||
* @date 2020-06-15
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(ObjectMapper.class)
|
||||
@AutoConfigureBefore(JacksonAutoConfiguration.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class JacksonConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final String ASIA_SHANGHAI = "Asia/Shanghai";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||
return builder -> {
|
||||
builder.locale(Locale.CHINA);
|
||||
builder.timeZone(TimeZone.getTimeZone(ASIA_SHANGHAI));
|
||||
builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
|
||||
builder.serializerByType(Long.class, ToStringSerializer.instance);
|
||||
builder.modules(new PigxJavaTimeModule());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加GET请求参数中时间类型转换 {@link com.pig4cloud.pigx.common.core.jackson.PigxJavaTimeModule}
|
||||
* <ul>
|
||||
* <li>HH:mm:ss -> LocalTime</li>
|
||||
* <li>yyyy-MM-dd -> LocalDate</li>
|
||||
* <li>yyyy-MM-dd HH:mm:ss -> LocalDateTime</li>
|
||||
* </ul>
|
||||
* @param registry
|
||||
*/
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN));
|
||||
registrar.setDateFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN));
|
||||
registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 避免form 提交 context-type 不规范中文乱码
|
||||
* @return Filter
|
||||
*/
|
||||
@Bean
|
||||
public OrderedCharacterEncodingFilter characterEncodingFilter() {
|
||||
OrderedCharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
|
||||
filter.setEncoding(StandardCharsets.UTF_8.name());
|
||||
filter.setForceEncoding(true);
|
||||
filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
return filter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.config;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/11/14
|
||||
* <p>
|
||||
* 国际化配置
|
||||
*/
|
||||
@Configuration
|
||||
public class MessageSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public MessageSource pigxMessageSource() {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
messageSource.setBasename("classpath:i18n/messages");
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/8/16 RestTemplate
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfiguration {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.pig4cloud.pigx.common.core.constant;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-04-28
|
||||
* <p>
|
||||
* 缓存的key 常量
|
||||
*/
|
||||
public interface CacheConstants {
|
||||
|
||||
/**
|
||||
* 全局缓存,在缓存名称上加上该前缀表示该缓存不区分租户,比如:
|
||||
* <p/>
|
||||
* {@code @Cacheable(value = CacheConstants.GLOBALLY+CacheConstants.MENU_DETAILS, key = "#roleId + '_menu'", unless = "#result == null")}
|
||||
*/
|
||||
String GLOBALLY = "gl:";
|
||||
|
||||
/**
|
||||
* 验证码前缀
|
||||
*/
|
||||
String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:";
|
||||
|
||||
/**
|
||||
* 菜单信息缓存
|
||||
*/
|
||||
String MENU_DETAILS = "menu_details";
|
||||
|
||||
/**
|
||||
* 用户信息缓存
|
||||
*/
|
||||
String USER_DETAILS = "user_details";
|
||||
|
||||
/**
|
||||
* 移动端用户信息缓存
|
||||
*/
|
||||
String USER_DETAILS_MINI = "user_details_mini";
|
||||
|
||||
/**
|
||||
* 角色信息缓存
|
||||
*/
|
||||
String ROLE_DETAILS = "role_details";
|
||||
|
||||
/**
|
||||
* 字典信息缓存
|
||||
*/
|
||||
String DICT_DETAILS = "dict_details";
|
||||
|
||||
/**
|
||||
* oauth 客户端信息
|
||||
*/
|
||||
String CLIENT_DETAILS_KEY = "pigx_oauth:client:details";
|
||||
|
||||
/**
|
||||
* spring boot admin 事件key
|
||||
*/
|
||||
String EVENT_KEY = GLOBALLY + "event_key";
|
||||
|
||||
/**
|
||||
* 路由存放
|
||||
*/
|
||||
String ROUTE_KEY = GLOBALLY + "gateway_route_key";
|
||||
|
||||
/**
|
||||
* 内存reload 时间
|
||||
*/
|
||||
String ROUTE_JVM_RELOAD_TOPIC = "gateway_jvm_route_reload_topic";
|
||||
|
||||
/**
|
||||
* redis 重新加载 路由信息
|
||||
*/
|
||||
String ROUTE_REDIS_RELOAD_TOPIC = "upms_redis_route_reload_topic";
|
||||
|
||||
/**
|
||||
* redis 重新加载客户端信息
|
||||
*/
|
||||
String CLIENT_REDIS_RELOAD_TOPIC = "upms_redis_client_reload_topic";
|
||||
|
||||
/**
|
||||
* 公众号 reload
|
||||
*/
|
||||
String MP_REDIS_RELOAD_TOPIC = "mp_redis_reload_topic";
|
||||
|
||||
/**
|
||||
* 支付 reload 事件
|
||||
*/
|
||||
String PAY_REDIS_RELOAD_TOPIC = "pay_redis_reload_topic";
|
||||
|
||||
/**
|
||||
* 参数缓存
|
||||
*/
|
||||
String PARAMS_DETAILS = "params_details";
|
||||
|
||||
/**
|
||||
* 租户缓存 (不区分租户)
|
||||
*/
|
||||
String TENANT_DETAILS = GLOBALLY + "tenant_details";
|
||||
|
||||
/**
|
||||
* i18n缓存 (不区分租户)
|
||||
*/
|
||||
String I18N_DETAILS = GLOBALLY + "i18n_details";
|
||||
|
||||
/**
|
||||
* 客户端配置缓存
|
||||
*/
|
||||
String CLIENT_FLAG = "client_config_flag";
|
||||
|
||||
/**
|
||||
* 登录错误次数
|
||||
*/
|
||||
String LOGIN_ERROR_TIMES = "login_error_times";
|
||||
|
||||
/**
|
||||
* oauth 缓存前缀
|
||||
*/
|
||||
String PROJECT_OAUTH_ACCESS = "token::access_token";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2017/10/29
|
||||
*/
|
||||
public interface CommonConstants {
|
||||
|
||||
/**
|
||||
* header 中租户ID
|
||||
*/
|
||||
String TENANT_ID = "TENANT-ID";
|
||||
|
||||
/**
|
||||
* header 中版本信息
|
||||
*/
|
||||
String VERSION = "VERSION";
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
Long TENANT_ID_1 = 1L;
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
String STATUS_DEL = "1";
|
||||
|
||||
/**
|
||||
* 正常
|
||||
*/
|
||||
String STATUS_NORMAL = "0";
|
||||
|
||||
/**
|
||||
* 锁定
|
||||
*/
|
||||
String STATUS_LOCK = "9";
|
||||
|
||||
/**
|
||||
* 菜单树根节点
|
||||
*/
|
||||
Long MENU_TREE_ROOT_ID = -1L;
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
String UTF8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* 前端工程名
|
||||
*/
|
||||
String FRONT_END_PROJECT = "pigx-ui";
|
||||
|
||||
/**
|
||||
* 移动端工程名
|
||||
*/
|
||||
String UNI_END_PROJECT = "pigx-app";
|
||||
|
||||
/**
|
||||
* 后端工程名
|
||||
*/
|
||||
String BACK_END_PROJECT = "pigx";
|
||||
|
||||
/**
|
||||
* 公共参数
|
||||
*/
|
||||
String PIG_PUBLIC_PARAM_KEY = "PIG_PUBLIC_PARAM_KEY";
|
||||
|
||||
/**
|
||||
* 成功标记
|
||||
*/
|
||||
Integer SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* 失败标记
|
||||
*/
|
||||
Integer FAIL = 1;
|
||||
|
||||
/**
|
||||
* 默认存储bucket
|
||||
*/
|
||||
String BUCKET_NAME = "lengleng";
|
||||
|
||||
/**
|
||||
* 滑块验证码
|
||||
*/
|
||||
String IMAGE_CODE_TYPE = "blockPuzzle";
|
||||
|
||||
/**
|
||||
* 验证码开关
|
||||
*/
|
||||
String CAPTCHA_FLAG = "captcha_flag";
|
||||
|
||||
/**
|
||||
* 密码传输是否加密
|
||||
*/
|
||||
String ENC_FLAG = "enc_flag";
|
||||
|
||||
/**
|
||||
* 客户端允许同时在线数量
|
||||
*/
|
||||
String ONLINE_QUANTITY = "online_quantity";
|
||||
|
||||
/**
|
||||
* 请求开始时间
|
||||
*/
|
||||
String REQUEST_START_TIME = "REQUEST-START-TIME";
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
String CURRENT = "current";
|
||||
|
||||
/**
|
||||
* size
|
||||
*/
|
||||
String SIZE = "size";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.pig4cloud.pigx.common.core.constant;
|
||||
|
||||
/**
|
||||
* 分页相关的参数
|
||||
*
|
||||
* @author lishangbu
|
||||
* @date 2018/11/22
|
||||
*/
|
||||
public interface PaginationConstants {
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
String CURRENT = "current";
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
String SIZE = "size";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2017-12-18
|
||||
*/
|
||||
public interface SecurityConstants {
|
||||
|
||||
/**
|
||||
* 启动时是否检查Inner注解安全性
|
||||
*/
|
||||
boolean INNER_CHECK = true;
|
||||
|
||||
/**
|
||||
* 刷新
|
||||
*/
|
||||
String REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
/**
|
||||
* 验证码有效期
|
||||
*/
|
||||
int CODE_TIME = 60;
|
||||
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
String CODE_SIZE = "4";
|
||||
|
||||
/**
|
||||
* 角色前缀
|
||||
*/
|
||||
String ROLE = "ROLE_";
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
String PIGX_PREFIX = "pigx_";
|
||||
|
||||
/**
|
||||
* token 相关前缀
|
||||
*/
|
||||
String TOKEN_PREFIX = "token:";
|
||||
|
||||
/**
|
||||
* oauth 相关前缀
|
||||
*/
|
||||
String OAUTH_PREFIX = "oauth:";
|
||||
|
||||
/**
|
||||
* 授权码模式code key 前缀
|
||||
*/
|
||||
String OAUTH_CODE_PREFIX = "oauth:code:";
|
||||
|
||||
/**
|
||||
* 项目的license
|
||||
*/
|
||||
String PIGX_LICENSE = "https://pig4cloud.com";
|
||||
|
||||
/**
|
||||
* 内部
|
||||
*/
|
||||
String FROM_IN = "Y";
|
||||
|
||||
/**
|
||||
* 标志
|
||||
*/
|
||||
String FROM = "from";
|
||||
|
||||
/**
|
||||
* 请求header
|
||||
*/
|
||||
String HEADER_FROM_IN = FROM + "=" + FROM_IN;
|
||||
|
||||
/**
|
||||
* OAUTH URL
|
||||
*/
|
||||
String OAUTH_TOKEN_URL = "/oauth2/token";
|
||||
|
||||
/**
|
||||
* 移动端授权
|
||||
*/
|
||||
String GRANT_MOBILE = "mobile";
|
||||
|
||||
/**
|
||||
* TOC 客户端
|
||||
*/
|
||||
String HEADER_TOC = "CLIENT-TOC";
|
||||
|
||||
/**
|
||||
* TOC 客户端
|
||||
*/
|
||||
String HEADER_TOC_YES = "Y";
|
||||
|
||||
/**
|
||||
* QQ获取token
|
||||
*/
|
||||
String QQ_AUTHORIZATION_CODE_URL = "https://graph.qq.com/oauth2.0/token?grant_type="
|
||||
+ "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
|
||||
|
||||
/**
|
||||
* 微信获取OPENID
|
||||
*/
|
||||
String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"
|
||||
+ "?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
|
||||
|
||||
/**
|
||||
* 微信小程序OPENID
|
||||
*/
|
||||
String MINI_APP_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/jscode2session"
|
||||
+ "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
|
||||
|
||||
/**
|
||||
* 码云获取token
|
||||
*/
|
||||
String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type="
|
||||
+ "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
|
||||
|
||||
/**
|
||||
* 开源中国获取token
|
||||
*/
|
||||
String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";
|
||||
|
||||
/**
|
||||
* QQ获取用户信息
|
||||
*/
|
||||
String QQ_USER_INFO_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
|
||||
|
||||
/**
|
||||
* 码云获取用户信息
|
||||
*/
|
||||
String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s";
|
||||
|
||||
/**
|
||||
* 开源中国用户信息
|
||||
*/
|
||||
String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json";
|
||||
|
||||
/**
|
||||
* 钉钉获取 token
|
||||
*/
|
||||
String DING_OLD_GET_TOKEN = "https://oapi.dingtalk.com/gettoken";
|
||||
|
||||
/**
|
||||
* 钉钉同步部门列表
|
||||
*/
|
||||
String DING_OLD_DEPT_URL = "https://oapi.dingtalk.com/topapi/v2/department/listsub";
|
||||
|
||||
/**
|
||||
* 钉钉部门用户id列表
|
||||
*/
|
||||
String DING_DEPT_USERIDS_URL = "https://oapi.dingtalk.com/topapi/user/listid";
|
||||
|
||||
/**
|
||||
* 钉钉用户详情
|
||||
*/
|
||||
String DING_USER_INFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/get";
|
||||
|
||||
/**
|
||||
* {bcrypt} 加密的特征码
|
||||
*/
|
||||
String BCRYPT = "{bcrypt}";
|
||||
|
||||
/**
|
||||
* 客户端模式
|
||||
*/
|
||||
String CLIENT_CREDENTIALS = "client_credentials";
|
||||
|
||||
/**
|
||||
* 客户端编号
|
||||
*/
|
||||
String CLIENT_ID = "client_id";
|
||||
|
||||
/**
|
||||
* 客户端唯一令牌
|
||||
*/
|
||||
String CLIENT_RECREATE = "recreate_flag";
|
||||
|
||||
/**
|
||||
* 用户ID字段
|
||||
*/
|
||||
String DETAILS_USER_ID = "user_id";
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
String DETAILS_USERNAME = "username";
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
String NAME = "name";
|
||||
|
||||
/**
|
||||
* 协议字段
|
||||
*/
|
||||
String DETAILS_LICENSE = "license";
|
||||
|
||||
/**
|
||||
* 激活字段 兼容外围系统接入
|
||||
*/
|
||||
String ACTIVE = "active";
|
||||
|
||||
/**
|
||||
* AES 加密
|
||||
*/
|
||||
String AES = "aes";
|
||||
|
||||
/**
|
||||
* 授权码模式confirm
|
||||
*/
|
||||
String CUSTOM_CONSENT_PAGE_URI = "/token/confirm_access";
|
||||
|
||||
/**
|
||||
* {noop} 加密的特征码
|
||||
*/
|
||||
String NOOP = "{noop}";
|
||||
|
||||
/**
|
||||
* 短信登录 参数名称
|
||||
*/
|
||||
String SMS_PARAMETER_NAME = "mobile";
|
||||
|
||||
/**
|
||||
* 手机号登录
|
||||
*/
|
||||
String APP = "mobile";
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
String DETAILS_USER = "user_info";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018年06月22日16:41:01 服务名称
|
||||
*/
|
||||
public interface ServiceNameConstants {
|
||||
|
||||
/**
|
||||
* 认证中心
|
||||
*/
|
||||
String AUTH_SERVICE = "pigx-auth";
|
||||
|
||||
/**
|
||||
* UMPS模块
|
||||
*/
|
||||
// String UPMS_SERVICE = "pigx-upms-biz";
|
||||
String UPMS_SERVICE = "as-upms-biz";
|
||||
|
||||
/**
|
||||
* app服务
|
||||
*/
|
||||
// String APP_SERVER = "pigx-app-server-biz";
|
||||
String APP_SERVER = "as-app-server-biz";
|
||||
|
||||
/**
|
||||
* 流程引擎
|
||||
*/
|
||||
String FLOW_ENGINE_SERVER = "pigx-flow-engine-biz";
|
||||
|
||||
/**
|
||||
* 流程工单
|
||||
*/
|
||||
String FLOW_TASK_SERVER = "pigx-flow-task-biz";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-11-18
|
||||
* <p>
|
||||
* 验证码状态
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CaptchaFlagTypeEnum {
|
||||
|
||||
/**
|
||||
* 开启验证码
|
||||
*/
|
||||
ON("1", "开启验证码"),
|
||||
|
||||
/**
|
||||
* 关闭验证码
|
||||
*/
|
||||
OFF("0", "关闭验证码");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-05-16
|
||||
* <p>
|
||||
* 字典类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DictTypeEnum {
|
||||
|
||||
/**
|
||||
* 字典类型-系统内置(不可修改)
|
||||
*/
|
||||
SYSTEM("1", "系统内置"),
|
||||
|
||||
/**
|
||||
* 字典类型-业务类型
|
||||
*/
|
||||
BIZ("0", "业务类");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-11-18
|
||||
* <p>
|
||||
* 密码是否加密传输
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum EncFlagTypeEnum {
|
||||
|
||||
/**
|
||||
* 是
|
||||
*/
|
||||
YES("1", "是"),
|
||||
|
||||
/**
|
||||
* 否
|
||||
*/
|
||||
NO("0", "否");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/8/15 社交登录类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LoginTypeEnum {
|
||||
|
||||
/**
|
||||
* 账号密码登录
|
||||
*/
|
||||
PWD("PWD", "账号密码登录"),
|
||||
|
||||
/**
|
||||
* 验证码登录
|
||||
*/
|
||||
SMS("SMS", "验证码登录"),
|
||||
|
||||
APPSMS("APP-SMS", "APP验证码登录"),
|
||||
|
||||
/**
|
||||
* QQ登录
|
||||
*/
|
||||
QQ("QQ", "QQ登录"),
|
||||
|
||||
/**
|
||||
* 微信登录
|
||||
*/
|
||||
WECHAT("WX", "微信登录"),
|
||||
|
||||
/**
|
||||
* 微信小程序
|
||||
*/
|
||||
MINI_APP("MINI", "微信小程序"),
|
||||
|
||||
/**
|
||||
* 码云登录
|
||||
*/
|
||||
GITEE("GITEE", "码云登录"),
|
||||
|
||||
/**
|
||||
* 开源中国登录
|
||||
*/
|
||||
OSC("OSC", "开源中国登录"),
|
||||
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
DINGTALK("DINGTALK", "钉钉"),
|
||||
|
||||
/**
|
||||
* 企业微信
|
||||
*/
|
||||
WEIXIN_CP("WEIXIN_CP", "企业微信"),
|
||||
|
||||
/**
|
||||
* CAS 登录
|
||||
*/
|
||||
CAS("CAS", "CAS 登录");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-02-17
|
||||
* <p>
|
||||
* 菜单类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MenuTypeEnum {
|
||||
|
||||
/**
|
||||
* 左侧菜单
|
||||
*/
|
||||
LEFT_MENU("0", "left"),
|
||||
|
||||
/**
|
||||
* 顶部菜单
|
||||
*/
|
||||
TOP_MENU("2", "top"),
|
||||
|
||||
/**
|
||||
* 按钮
|
||||
*/
|
||||
BUTTON("1", "button");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/9/30 流程状态
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ProcessStatusEnum {
|
||||
|
||||
/**
|
||||
* 激活
|
||||
*/
|
||||
ACTIVE("active"),
|
||||
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
SUSPEND("suspend");
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final String status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/9/30 资源类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ResourceTypeEnum {
|
||||
|
||||
/**
|
||||
* 图片资源
|
||||
*/
|
||||
IMAGE("image", "图片资源"),
|
||||
|
||||
/**
|
||||
* xml资源
|
||||
*/
|
||||
XML("xml", "xml资源");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-01-19
|
||||
* <p>
|
||||
* 前端类型类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum StyleTypeEnum {
|
||||
|
||||
/**
|
||||
* 前端类型-avue 风格
|
||||
*/
|
||||
AVUE("0", "avue"),
|
||||
|
||||
/**
|
||||
* 前端类型-element 风格
|
||||
*/
|
||||
ELEMENT("1", "element"),
|
||||
|
||||
/**
|
||||
* 前端风格-uview 风格
|
||||
*/
|
||||
UVIEW("2", "uview"),
|
||||
|
||||
/**
|
||||
* magic
|
||||
*/
|
||||
MAGIC("3", "magic"),
|
||||
/**
|
||||
* element-plus
|
||||
*/
|
||||
PLUS("4", "plus");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String style;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
public static String getDecs(String style) {
|
||||
return Arrays.stream(StyleTypeEnum.values()).filter(styleTypeEnum -> styleTypeEnum.getStyle().equals(style))
|
||||
.findFirst().get().getDescription();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/9/30 流程状态
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TaskStatusEnum {
|
||||
|
||||
/**
|
||||
* 未提交
|
||||
*/
|
||||
UNSUBMIT("0", "未提交"),
|
||||
|
||||
/**
|
||||
* 审核中
|
||||
*/
|
||||
CHECK("1", "审核中"),
|
||||
|
||||
/**
|
||||
* 已完成
|
||||
*/
|
||||
COMPLETED("2", "已完成"),
|
||||
|
||||
/**
|
||||
* 驳回
|
||||
*/
|
||||
OVERRULE("9", "驳回");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final String status;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.pig4cloud.pigx.common.core.constant.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum UserTypeEnum {
|
||||
|
||||
TOB("0", "面向后台应用"), TOC("1", "面向小程序");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final String status;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.exception;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 😴2018年06月22日16:21:57
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class CheckedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CheckedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CheckedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public CheckedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.pig4cloud.pigx.common.core.exception;
|
||||
|
||||
/**
|
||||
* 错误编码
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/3/30
|
||||
*/
|
||||
public interface ErrorCodes {
|
||||
|
||||
/**
|
||||
* 系统编码错误
|
||||
*/
|
||||
String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error";
|
||||
|
||||
/**
|
||||
* 系统内置参数不能删除
|
||||
*/
|
||||
String SYS_PARAM_DELETE_SYSTEM = "sys.param.delete.system";
|
||||
|
||||
/**
|
||||
* 用户已存在
|
||||
*/
|
||||
String SYS_USER_USERNAME_EXISTING = "sys.user.username.existing";
|
||||
|
||||
/**
|
||||
* 用户已存在
|
||||
*/
|
||||
String SYS_USER_PHONE_EXISTING = "sys.user.phone.existing";
|
||||
|
||||
/**
|
||||
* 用户原密码错误,修改失败
|
||||
*/
|
||||
String SYS_USER_UPDATE_PASSWORDERROR = "sys.user.update.passwordError";
|
||||
|
||||
/**
|
||||
* 用户信息为空
|
||||
*/
|
||||
String SYS_USER_USERINFO_EMPTY = "sys.user.userInfo.empty";
|
||||
|
||||
/**
|
||||
* 获取当前用户信息失败
|
||||
*/
|
||||
String SYS_USER_QUERY_ERROR = "sys.user.query.error";
|
||||
|
||||
String SYS_USER_IMPORT_SUCCEED = "sys.user.import.succeed";
|
||||
|
||||
/**
|
||||
* 部门名称不存在
|
||||
*/
|
||||
String SYS_DEPT_DEPTNAME_INEXISTENCE = "sys.dept.deptName.inexistence";
|
||||
|
||||
/**
|
||||
* 岗位名称不存在
|
||||
*/
|
||||
String SYS_POST_POSTNAME_INEXISTENCE = "sys.post.postName.inexistence";
|
||||
|
||||
/**
|
||||
* 岗位名称或编码已经存在
|
||||
*/
|
||||
String SYS_POST_NAMEORCODE_EXISTING = "sys.post.nameOrCode.existing";
|
||||
|
||||
/**
|
||||
* 角色名称不存在
|
||||
*/
|
||||
String SYS_ROLE_ROLENAME_INEXISTENCE = "sys.role.roleName.inexistence";
|
||||
|
||||
/**
|
||||
* 角色名或角色编码已经存在
|
||||
*/
|
||||
String SYS_ROLE_NAMEORCODE_EXISTING = "sys.role.nameOrCode.existing";
|
||||
|
||||
/**
|
||||
* 菜单存在下级节点 删除失败
|
||||
*/
|
||||
String SYS_MENU_DELETE_EXISTING = "sys.menu.delete.existing";
|
||||
|
||||
/**
|
||||
* 系统内置字典不允许删除
|
||||
*/
|
||||
String SYS_DICT_DELETE_SYSTEM = "sys.dict.delete.system";
|
||||
|
||||
/**
|
||||
* 系统内置字典不能修改
|
||||
*/
|
||||
String SYS_DICT_UPDATE_SYSTEM = "sys.dict.update.system";
|
||||
|
||||
/**
|
||||
* 验证码发送频繁
|
||||
*/
|
||||
String SYS_APP_SMS_OFTEN = "sys.app.sms.often";
|
||||
|
||||
/**
|
||||
* 手机号未注册
|
||||
*/
|
||||
String SYS_APP_PHONE_UNREGISTERED = "sys.app.phone.unregistered";
|
||||
|
||||
/**
|
||||
* 企微调用接口错误
|
||||
*/
|
||||
String SYS_CONNECT_CP_DEPT_SYNC_ERROR = "sys.connect.cp.dept.sync.error";
|
||||
|
||||
/**
|
||||
* 企微调用接口错误
|
||||
*/
|
||||
String SYS_CONNECT_CP_USER_SYNC_ERROR = "sys.connect.cp.user.sync.error";
|
||||
|
||||
/**
|
||||
* 用户信息为空
|
||||
*/
|
||||
String APP_USER_USERINFO_EMPTY = "app.user.userInfo.empty";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.exception;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018年06月22日16:22:15
|
||||
*/
|
||||
public class ValidateCodeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -7285211528095468156L;
|
||||
|
||||
public ValidateCodeException() {
|
||||
}
|
||||
|
||||
public ValidateCodeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.pig4cloud.pigx.common.core.factory;
|
||||
|
||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.core.io.support.PropertySourceFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2022/3/29
|
||||
* <p>
|
||||
* 读取自定义 yaml 文件工厂类
|
||||
*/
|
||||
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||
|
||||
@Override
|
||||
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
|
||||
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
|
||||
String sourceName = name != null ? name : resource.getResource().getFilename();
|
||||
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
|
||||
}
|
||||
|
||||
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
|
||||
try {
|
||||
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
||||
factory.setResources(resource.getResource());
|
||||
factory.afterPropertiesSet();
|
||||
return factory.getObject();
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof FileNotFoundException)
|
||||
throw (FileNotFoundException) e.getCause();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.pig4cloud.pigx.common.core.jackson;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* java 8 时间默认序列化
|
||||
*
|
||||
* @author L.cm
|
||||
* @author lishanbu
|
||||
*/
|
||||
public class PigxJavaTimeModule extends SimpleModule {
|
||||
|
||||
/**
|
||||
* 指定序列化规则
|
||||
*/
|
||||
public PigxJavaTimeModule() {
|
||||
super(PackageVersion.VERSION);
|
||||
|
||||
// ======================= 时间序列化规则 ===============================
|
||||
// yyyy-MM-dd HH:mm:ss
|
||||
this.addSerializer(LocalDateTime.class,
|
||||
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
|
||||
// yyyy-MM-dd
|
||||
this.addSerializer(LocalDate.class,
|
||||
new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
|
||||
// HH:mm:ss
|
||||
this.addSerializer(LocalTime.class,
|
||||
new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
|
||||
|
||||
// Instant 类型序列化
|
||||
this.addSerializer(Instant.class, InstantSerializer.INSTANCE);
|
||||
|
||||
// ======================= 时间反序列化规则 ==============================
|
||||
// yyyy-MM-dd HH:mm:ss
|
||||
this.addDeserializer(LocalDateTime.class,
|
||||
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
|
||||
// yyyy-MM-dd
|
||||
this.addDeserializer(LocalDate.class,
|
||||
new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
|
||||
// HH:mm:ss
|
||||
this.addDeserializer(LocalTime.class,
|
||||
new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
|
||||
// Instant 反序列化
|
||||
this.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.sensitive;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 对象脱敏注解
|
||||
*
|
||||
* @author mayee
|
||||
* @version v1.0
|
||||
**/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = SensitiveSerialize.class)
|
||||
public @interface Sensitive {
|
||||
|
||||
/**
|
||||
* 脱敏数据类型, 非Customer时, 将忽略 refixNoMaskLen 和 suffixNoMaskLen 和 maskStr
|
||||
*/
|
||||
SensitiveTypeEnum type() default SensitiveTypeEnum.CUSTOMER;
|
||||
|
||||
/**
|
||||
* 前置不需要打码的长度
|
||||
*/
|
||||
int prefixNoMaskLen() default 0;
|
||||
|
||||
/**
|
||||
* 后置不需要打码的长度
|
||||
*/
|
||||
int suffixNoMaskLen() default 0;
|
||||
|
||||
/**
|
||||
* 用什么打码
|
||||
*/
|
||||
String maskStr() default "*";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.sensitive;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import com.pig4cloud.pigx.common.core.util.DesensitizedUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-08-13
|
||||
* <p>
|
||||
* 脱敏序列化
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
|
||||
|
||||
private SensitiveTypeEnum type;
|
||||
|
||||
private Integer prefixNoMaskLen;
|
||||
|
||||
private Integer suffixNoMaskLen;
|
||||
|
||||
private String maskStr;
|
||||
|
||||
@Override
|
||||
public void serialize(final String origin, final JsonGenerator jsonGenerator,
|
||||
final SerializerProvider serializerProvider) throws IOException {
|
||||
switch (type) {
|
||||
case CHINESE_NAME:
|
||||
jsonGenerator.writeString(DesensitizedUtils.chineseName(origin));
|
||||
break;
|
||||
case ID_CARD:
|
||||
jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin));
|
||||
break;
|
||||
case FIXED_PHONE:
|
||||
jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin));
|
||||
break;
|
||||
case MOBILE_PHONE:
|
||||
jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin));
|
||||
break;
|
||||
case ADDRESS:
|
||||
jsonGenerator.writeString(DesensitizedUtils.address(origin));
|
||||
break;
|
||||
case EMAIL:
|
||||
jsonGenerator.writeString(DesensitizedUtils.email(origin));
|
||||
break;
|
||||
case BANK_CARD:
|
||||
jsonGenerator.writeString(DesensitizedUtils.bankCard(origin));
|
||||
break;
|
||||
case PASSWORD:
|
||||
jsonGenerator.writeString(DesensitizedUtils.password(origin));
|
||||
break;
|
||||
case KEY:
|
||||
jsonGenerator.writeString(DesensitizedUtils.key(origin));
|
||||
break;
|
||||
case CUSTOMER:
|
||||
jsonGenerator
|
||||
.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknow sensitive type enum " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
|
||||
final BeanProperty beanProperty) throws JsonMappingException {
|
||||
if (beanProperty != null) {
|
||||
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
|
||||
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
|
||||
if (sensitive == null) {
|
||||
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
|
||||
}
|
||||
if (sensitive != null) {
|
||||
return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(),
|
||||
sensitive.suffixNoMaskLen(), sensitive.maskStr());
|
||||
}
|
||||
}
|
||||
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
|
||||
}
|
||||
return serializerProvider.findNullValueSerializer(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.sensitive;
|
||||
|
||||
/**
|
||||
* 敏感信息枚举类
|
||||
*
|
||||
* @author mayee
|
||||
* @version v1.0
|
||||
**/
|
||||
public enum SensitiveTypeEnum {
|
||||
|
||||
/**
|
||||
* 自定义
|
||||
*/
|
||||
CUSTOMER,
|
||||
/**
|
||||
* 用户名, 刘*华, 徐*
|
||||
*/
|
||||
CHINESE_NAME,
|
||||
/**
|
||||
* 身份证号, 110110********1234
|
||||
*/
|
||||
ID_CARD,
|
||||
/**
|
||||
* 座机号, ****1234
|
||||
*/
|
||||
FIXED_PHONE,
|
||||
/**
|
||||
* 手机号, 176****1234
|
||||
*/
|
||||
MOBILE_PHONE,
|
||||
/**
|
||||
* 地址, 北京********
|
||||
*/
|
||||
ADDRESS,
|
||||
/**
|
||||
* 电子邮件, s*****o@xx.com
|
||||
*/
|
||||
EMAIL,
|
||||
/**
|
||||
* 银行卡, 622202************1234
|
||||
*/
|
||||
BANK_CARD,
|
||||
/**
|
||||
* 密码, 永远是 ******, 与长度无关
|
||||
*/
|
||||
PASSWORD,
|
||||
/**
|
||||
* 密钥, 【密钥】密钥除了最后三位其他都是***, 与长度无关
|
||||
*/
|
||||
KEY
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 类工具类
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@UtilityClass
|
||||
public class ClassUtils extends org.springframework.util.ClassUtils {
|
||||
|
||||
private final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer();
|
||||
|
||||
/**
|
||||
* 获取方法参数信息
|
||||
* @param constructor 构造器
|
||||
* @param parameterIndex 参数序号
|
||||
* @return {MethodParameter}
|
||||
*/
|
||||
public MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
|
||||
MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);
|
||||
methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法参数信息
|
||||
* @param method 方法
|
||||
* @param parameterIndex 参数序号
|
||||
* @return {MethodParameter}
|
||||
*/
|
||||
public MethodParameter getMethodParameter(Method method, int parameterIndex) {
|
||||
MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
|
||||
methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Annotation
|
||||
* @param method Method
|
||||
* @param annotationType 注解类
|
||||
* @param <A> 泛型标记
|
||||
* @return {Annotation}
|
||||
*/
|
||||
public <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
|
||||
Class<?> targetClass = method.getDeclaringClass();
|
||||
// The method may be on an interface, but we need attributes from the target
|
||||
// class.
|
||||
// If the target class is null, the method will be unchanged.
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
|
||||
// If we are dealing with method with generic parameters, find the original
|
||||
// method.
|
||||
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||
// 先找方法,再找方法上的类
|
||||
A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);
|
||||
|
||||
if (null != annotation) {
|
||||
return annotation;
|
||||
}
|
||||
// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
|
||||
return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Annotation
|
||||
* @param handlerMethod HandlerMethod
|
||||
* @param annotationType 注解类
|
||||
* @param <A> 泛型标记
|
||||
* @return {Annotation}
|
||||
*/
|
||||
public <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
|
||||
// 先找方法,再找方法上的类
|
||||
A annotation = handlerMethod.getMethodAnnotation(annotationType);
|
||||
if (null != annotation) {
|
||||
return annotation;
|
||||
}
|
||||
// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
|
||||
Class<?> beanType = handlerMethod.getBeanType();
|
||||
return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 脱敏工具类
|
||||
*
|
||||
* @author mayee
|
||||
* @version v1.0
|
||||
**/
|
||||
public class DesensitizedUtils {
|
||||
|
||||
/**
|
||||
* 对字符串进行脱敏操作
|
||||
* @param origin 原始字符串
|
||||
* @param prefixNoMaskLen 左侧需要保留几位明文字段
|
||||
* @param suffixNoMaskLen 右侧需要保留几位明文字段
|
||||
* @param maskStr 用于遮罩的字符串, 如'*'
|
||||
* @return 脱敏后结果
|
||||
*/
|
||||
public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
|
||||
if (origin == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0, n = origin.length(); i < n; i++) {
|
||||
if (i < prefixNoMaskLen) {
|
||||
sb.append(origin.charAt(i));
|
||||
continue;
|
||||
}
|
||||
if (i > (n - suffixNoMaskLen - 1)) {
|
||||
sb.append(origin.charAt(i));
|
||||
continue;
|
||||
}
|
||||
sb.append(maskStr);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 【中文姓名】只显示最后一个汉字,其他隐藏为星号,比如:**梦
|
||||
* @param fullName 姓名
|
||||
* @return 结果
|
||||
*/
|
||||
public static String chineseName(String fullName) {
|
||||
if (fullName == null) {
|
||||
return null;
|
||||
}
|
||||
return desValue(fullName, 0, 1, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【身份证号】显示前六位, 四位,其他隐藏。共计18位或者15位,比如:340304*******1234
|
||||
* @param id 身份证号码
|
||||
* @return 结果
|
||||
*/
|
||||
public static String idCardNum(String id) {
|
||||
return desValue(id, 6, 4, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【固定电话】后四位,其他隐藏,比如 ****1234
|
||||
* @param num 固定电话
|
||||
* @return 结果
|
||||
*/
|
||||
public static String fixedPhone(String num) {
|
||||
return desValue(num, 0, 4, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【手机号码】前三位,后四位,其他隐藏,比如135****6810
|
||||
* @param num 手机号码
|
||||
* @return 结果
|
||||
*/
|
||||
public static String mobilePhone(String num) {
|
||||
return desValue(num, 3, 4, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
|
||||
* @param address 地址
|
||||
* @return 结果
|
||||
*/
|
||||
public static String address(String address) {
|
||||
return desValue(address, 6, 0, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
|
||||
* @param email 电子邮箱
|
||||
* @return 结果
|
||||
*/
|
||||
public static String email(String email) {
|
||||
if (email == null) {
|
||||
return null;
|
||||
}
|
||||
int index = StrUtil.indexOf(email, '@');
|
||||
if (index <= 1) {
|
||||
return email;
|
||||
}
|
||||
String preEmail = desValue(email.substring(0, index), 1, 0, "*");
|
||||
return preEmail + email.substring(index);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:622260**********1234
|
||||
* @param cardNum 银行卡号
|
||||
* @return 结果
|
||||
*/
|
||||
public static String bankCard(String cardNum) {
|
||||
return desValue(cardNum, 6, 4, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【密码】密码的全部字符都用*代替,比如:******
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
public static String password(String password) {
|
||||
if (password == null) {
|
||||
return null;
|
||||
}
|
||||
return "******";
|
||||
}
|
||||
|
||||
/**
|
||||
* 【密钥】密钥除了最后三位,全部都用*代替,比如:***xdS 脱敏后长度为6,如果明文长度不足三位,则按实际长度显示,剩余位置补*
|
||||
* @param key 密钥
|
||||
* @return 结果
|
||||
*/
|
||||
public static String key(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
int viewLength = 6;
|
||||
StringBuilder tmpKey = new StringBuilder(desValue(key, 0, 3, "*"));
|
||||
if (tmpKey.length() > viewLength) {
|
||||
return tmpKey.substring(tmpKey.length() - viewLength);
|
||||
}
|
||||
else if (tmpKey.length() < viewLength) {
|
||||
int buffLength = viewLength - tmpKey.length();
|
||||
for (int i = 0; i < buffLength; i++) {
|
||||
tmpKey.insert(0, "*");
|
||||
}
|
||||
return tmpKey.toString();
|
||||
}
|
||||
else {
|
||||
return tmpKey.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/9/29
|
||||
* <p>
|
||||
* 字符串处理,方便其他模块解耦处理
|
||||
*/
|
||||
public interface KeyStrResolver {
|
||||
|
||||
/**
|
||||
* 字符串加工
|
||||
* @param in 输入字符串
|
||||
* @param split 分割符
|
||||
* @return 输出字符串
|
||||
*/
|
||||
String extract(String in, String split);
|
||||
|
||||
/**
|
||||
* 字符串获取
|
||||
* @return 模块返回字符串
|
||||
*/
|
||||
String key();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.context.MessageSource;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* i18n 工具类
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/3/30
|
||||
*/
|
||||
@UtilityClass
|
||||
public class MsgUtils {
|
||||
|
||||
/**
|
||||
* 通过code 获取中文错误信息
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code) {
|
||||
MessageSource messageSource = SpringContextHolder.getBean("pigxMessageSource");
|
||||
return messageSource.getMessage(code, null, Locale.CHINA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过code 和参数获取中文错误信息
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code, Object... objects) {
|
||||
MessageSource messageSource = SpringContextHolder.getBean("pigxMessageSource");
|
||||
return messageSource.getMessage(code, objects, Locale.CHINA);
|
||||
}
|
||||
|
||||
/**
|
||||
* security 通过code 和参数获取中文错误信息
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public String getSecurityMessage(String code, Object... objects) {
|
||||
MessageSource messageSource = SpringContextHolder.getBean("securityMessageSource");
|
||||
return messageSource.getMessage(code, objects, Locale.CHINA);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 响应信息主体
|
||||
*
|
||||
* @param <T>
|
||||
* @author lengleng
|
||||
*/
|
||||
@Builder
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "响应信息主体")
|
||||
public class R<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "返回标记:成功标记=0,失败标记=1")
|
||||
private int code;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "返回信息")
|
||||
private String msg;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "数据")
|
||||
private T data;
|
||||
|
||||
public static <T> R<T> ok() {
|
||||
return restResult(null, CommonConstants.SUCCESS, null);
|
||||
}
|
||||
|
||||
public static <T> R<T> ok(T data) {
|
||||
return restResult(data, CommonConstants.SUCCESS, null);
|
||||
}
|
||||
|
||||
public static <T> R<T> ok(T data, String msg) {
|
||||
return restResult(data, CommonConstants.SUCCESS, msg);
|
||||
}
|
||||
|
||||
public static <T> R<T> failed() {
|
||||
return restResult(null, CommonConstants.FAIL, null);
|
||||
}
|
||||
|
||||
public static <T> R<T> failed(String msg) {
|
||||
return restResult(null, CommonConstants.FAIL, msg);
|
||||
}
|
||||
|
||||
public static <T> R<T> failed(T data) {
|
||||
return restResult(data, CommonConstants.FAIL, null);
|
||||
}
|
||||
|
||||
public static <T> R<T> failed(T data, String msg) {
|
||||
return restResult(data, CommonConstants.FAIL, msg);
|
||||
}
|
||||
|
||||
static <T> R<T> restResult(T data, int code, String msg) {
|
||||
R<T> apiResult = new R<>();
|
||||
apiResult.setCode(code);
|
||||
apiResult.setData(data);
|
||||
apiResult.setMsg(msg);
|
||||
return apiResult;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return this.code == CommonConstants.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 简化{@code R<T>} 的访问操作,例子 <pre>
|
||||
* R<Integer> result = R.ok(0);
|
||||
* // 使用场景1: 链式操作: 断言然后消费
|
||||
* RetOps.of(result)
|
||||
* .assertCode(-1,r -> new RuntimeException("error "+r.getCode()))
|
||||
* .assertDataNotEmpty(r -> new IllegalStateException("oops!"))
|
||||
* .useData(System.out::println);
|
||||
*
|
||||
* // 使用场景2: 读取原始值(data),这里返回的是Optional
|
||||
* RetOps.of(result).getData().orElse(null);
|
||||
*
|
||||
* // 使用场景3: 类型转换
|
||||
* R<String> s = RetOps.of(result)
|
||||
* .assertDataNotNull(r -> new IllegalStateException("nani??"))
|
||||
* .map(i -> Integer.toHexString(i))
|
||||
* .peek();
|
||||
* </pre>
|
||||
*
|
||||
* @author CJ (power4j@outlook.com)
|
||||
* @date 2022/5/12
|
||||
* @since 4.4
|
||||
*/
|
||||
public class RetOps<T> {
|
||||
|
||||
/** 状态码为成功 */
|
||||
public static final Predicate<R<?>> CODE_SUCCESS = r -> CommonConstants.SUCCESS == r.getCode();
|
||||
|
||||
/** 数据有值 */
|
||||
public static final Predicate<R<?>> HAS_DATA = r -> ObjectUtil.isNotEmpty(r.getData());
|
||||
|
||||
/** 数据有值,并且包含元素 */
|
||||
public static final Predicate<R<?>> HAS_ELEMENT = r -> ObjectUtil.isNotEmpty(r.getData());
|
||||
|
||||
/** 状态码为成功并且有值 */
|
||||
public static final Predicate<R<?>> DATA_AVAILABLE = CODE_SUCCESS.and(HAS_DATA);
|
||||
|
||||
private final R<T> original;
|
||||
|
||||
// ~ 初始化
|
||||
// ===================================================================================================
|
||||
|
||||
RetOps(R<T> original) {
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
public static <T> RetOps<T> of(R<T> original) {
|
||||
return new RetOps<>(Objects.requireNonNull(original));
|
||||
}
|
||||
|
||||
// ~ 杂项方法
|
||||
// ===================================================================================================
|
||||
|
||||
/**
|
||||
* 观察原始值
|
||||
* @return R
|
||||
*/
|
||||
public R<T> peek() {
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取{@code code}的值
|
||||
* @return 返回code的值
|
||||
*/
|
||||
public int getCode() {
|
||||
return original.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取{@code data}的值
|
||||
* @return 返回 Optional 包装的data
|
||||
*/
|
||||
public Optional<T> getData() {
|
||||
return Optional.ofNullable(original.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 有条件地读取{@code data}的值
|
||||
* @param predicate 断言函数
|
||||
* @return 返回 Optional 包装的data,如果断言失败返回empty
|
||||
*/
|
||||
public Optional<T> getDataIf(Predicate<? super R<?>> predicate) {
|
||||
return predicate.test(original) ? getData() : Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取{@code msg}的值
|
||||
* @return 返回Optional包装的 msg
|
||||
*/
|
||||
public Optional<String> getMsg() {
|
||||
return Optional.ofNullable(original.getMsg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对{@code code}的值进行相等性测试
|
||||
* @param value 基准值
|
||||
* @return 返回ture表示相等
|
||||
*/
|
||||
public boolean codeEquals(int value) {
|
||||
return original.getCode() == value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对{@code code}的值进行相等性测试
|
||||
* @param value 基准值
|
||||
* @return 返回ture表示不相等
|
||||
*/
|
||||
public boolean codeNotEquals(int value) {
|
||||
return !codeEquals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
* @return 返回ture表示成功
|
||||
* @see CommonConstants#SUCCESS
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return codeEquals(CommonConstants.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否失败
|
||||
* @return 返回ture表示失败
|
||||
*/
|
||||
public boolean notSuccess() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
// ~ 链式操作
|
||||
// ===================================================================================================
|
||||
|
||||
/**
|
||||
* 断言{@code code}的值
|
||||
* @param expect 预期的值
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertCode(int expect, Function<? super R<T>, ? extends Ex> func)
|
||||
throws Ex {
|
||||
if (codeNotEquals(expect)) {
|
||||
throw func.apply(original);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言成功
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertSuccess(Function<? super R<T>, ? extends Ex> func) throws Ex {
|
||||
return assertCode(CommonConstants.SUCCESS, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言业务数据有值
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertDataNotNull(Function<? super R<T>, ? extends Ex> func) throws Ex {
|
||||
if (Objects.isNull(original.getData())) {
|
||||
throw func.apply(original);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言业务数据有值,并且包含元素
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertDataNotEmpty(Function<? super R<T>, ? extends Ex> func) throws Ex {
|
||||
if (ObjectUtil.isEmpty(original.getData())) {
|
||||
throw func.apply(original);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对业务数据(data)转换
|
||||
* @param mapper 业务数据转换函数
|
||||
* @param <U> 数据类型
|
||||
* @return 返回新实例,以便于继续进行链式操作
|
||||
*/
|
||||
public <U> RetOps<U> map(Function<? super T, ? extends U> mapper) {
|
||||
R<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
|
||||
return of(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对业务数据(data)转换
|
||||
* @param predicate 断言函数
|
||||
* @param mapper 业务数据转换函数
|
||||
* @param <U> 数据类型
|
||||
* @return 返回新实例,以便于继续进行链式操作
|
||||
* @see RetOps#CODE_SUCCESS
|
||||
* @see RetOps#HAS_DATA
|
||||
* @see RetOps#HAS_ELEMENT
|
||||
* @see RetOps#DATA_AVAILABLE
|
||||
*/
|
||||
public <U> RetOps<U> mapIf(Predicate<? super R<T>> predicate, Function<? super T, ? extends U> mapper) {
|
||||
R<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
|
||||
return of(result);
|
||||
}
|
||||
|
||||
// ~ 数据消费
|
||||
// ===================================================================================================
|
||||
|
||||
/**
|
||||
* 消费数据,注意此方法保证数据可用
|
||||
* @param consumer 消费函数
|
||||
*/
|
||||
public void useData(Consumer<? super T> consumer) {
|
||||
consumer.accept(original.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件消费(错误代码匹配某个值)
|
||||
* @param consumer 消费函数
|
||||
* @param codes 错误代码集合,匹配任意一个则调用消费函数
|
||||
*/
|
||||
public void useDataOnCode(Consumer<? super T> consumer, int... codes) {
|
||||
useDataIf(o -> Arrays.stream(codes).filter(c -> original.getCode() == c).findFirst().isPresent(), consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件消费(错误代码表示成功)
|
||||
* @param consumer 消费函数
|
||||
*/
|
||||
public void useDataIfSuccess(Consumer<? super T> consumer) {
|
||||
useDataIf(CODE_SUCCESS, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件消费
|
||||
* @param predicate 断言函数
|
||||
* @param consumer 消费函数,断言函数返回{@code true}时被调用
|
||||
* @see RetOps#CODE_SUCCESS
|
||||
* @see RetOps#HAS_DATA
|
||||
* @see RetOps#HAS_ELEMENT
|
||||
* @see RetOps#DATA_AVAILABLE
|
||||
*/
|
||||
public void useDataIf(Predicate<? super R<T>> predicate, Consumer<? super T> consumer) {
|
||||
if (predicate.test(original)) {
|
||||
consumer.accept(original.getData());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/6/27 Spring 工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@Lazy(false)
|
||||
public class SpringContextHolder implements BeanFactoryPostProcessor, ApplicationContextAware, DisposableBean {
|
||||
|
||||
private static ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private static ApplicationContext applicationContext = null;
|
||||
|
||||
/**
|
||||
* 取得存储在静态变量中的ApplicationContext.
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* BeanFactoryPostProcessor, 注入Context到静态变量中.
|
||||
*/
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
|
||||
SpringContextHolder.beanFactory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
SpringContextHolder.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ListableBeanFactory getBeanFactory() {
|
||||
return null == beanFactory ? applicationContext : beanFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) {
|
||||
return (T) getBeanFactory().getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, Map<Bean名称,实现类></>
|
||||
*/
|
||||
public static <T> Map<String, T> getBeansOfType(Class<T> type) {
|
||||
return getBeanFactory().getBeansOfType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
public static <T> T getBean(Class<T> requiredType) {
|
||||
return getBeanFactory().getBean(requiredType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除SpringContextHolder中的ApplicationContext为Null.
|
||||
*/
|
||||
public static void clearHolder() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
|
||||
}
|
||||
applicationContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布事件
|
||||
* @param event
|
||||
*/
|
||||
public static void publishEvent(ApplicationEvent event) {
|
||||
if (applicationContext == null) {
|
||||
return;
|
||||
}
|
||||
applicationContext.publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
SpringContextHolder.clearHolder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
/**
|
||||
* 校验类型
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/4/26
|
||||
*/
|
||||
public class ValidGroup {
|
||||
|
||||
/**
|
||||
* 插入组
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/4/26
|
||||
*/
|
||||
public static interface Insert {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑组
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/4/26
|
||||
*/
|
||||
public static interface Update {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.core.util;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Miscellaneous utilities for web applications.
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class WebUtils extends org.springframework.web.util.WebUtils {
|
||||
|
||||
private final String BASIC_ = "Basic ";
|
||||
|
||||
private final String UNKNOWN = "unknown";
|
||||
|
||||
/**
|
||||
* 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解
|
||||
* @param handlerMethod HandlerMethod
|
||||
* @return 是否ajax请求
|
||||
*/
|
||||
public boolean isBody(HandlerMethod handlerMethod) {
|
||||
ResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class);
|
||||
return responseBody != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取cookie
|
||||
* @param name cookie name
|
||||
* @return cookie value
|
||||
*/
|
||||
public String getCookieVal(String name) {
|
||||
HttpServletRequest request = WebUtils.getRequest();
|
||||
Assert.notNull(request, "request from RequestContextHolder is null");
|
||||
return getCookieVal(request, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取cookie
|
||||
* @param request HttpServletRequest
|
||||
* @param name cookie name
|
||||
* @return cookie value
|
||||
*/
|
||||
public String getCookieVal(HttpServletRequest request, String name) {
|
||||
Cookie cookie = getCookie(request, name);
|
||||
return cookie != null ? cookie.getValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除 某个指定的cookie
|
||||
* @param response HttpServletResponse
|
||||
* @param key cookie key
|
||||
*/
|
||||
public void removeCookie(HttpServletResponse response, String key) {
|
||||
setCookie(response, key, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置cookie
|
||||
* @param response HttpServletResponse
|
||||
* @param name cookie name
|
||||
* @param value cookie value
|
||||
* @param maxAgeInSeconds maxage
|
||||
*/
|
||||
public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(maxAgeInSeconds);
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 HttpServletRequest
|
||||
* @return {HttpServletRequest}
|
||||
*/
|
||||
public HttpServletRequest getRequest() {
|
||||
try {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 HttpServletResponse
|
||||
* @return {HttpServletResponse}
|
||||
*/
|
||||
public HttpServletResponse getResponse() {
|
||||
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回json
|
||||
* @param response HttpServletResponse
|
||||
* @param result 结果对象
|
||||
*/
|
||||
public void renderJson(HttpServletResponse response, Object result) {
|
||||
renderJson(response, result, MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回json
|
||||
* @param response HttpServletResponse
|
||||
* @param result 结果对象
|
||||
* @param contentType contentType
|
||||
*/
|
||||
public void renderJson(HttpServletResponse response, Object result, String contentType) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType(contentType);
|
||||
try (PrintWriter out = response.getWriter()) {
|
||||
out.append(JSONUtil.toJsonStr(result));
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ip
|
||||
* @return {String}
|
||||
*/
|
||||
public String getIP() {
|
||||
return getIP(WebUtils.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ip
|
||||
* @param request HttpServletRequest
|
||||
* @return {String}
|
||||
*/
|
||||
public String getIP(HttpServletRequest request) {
|
||||
Assert.notNull(request, "HttpServletRequest is null");
|
||||
String ip = request.getHeader("X-Requested-For");
|
||||
if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
}
|
||||
if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return StrUtil.isBlank(ip) ? null : ip.split(",")[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 client id
|
||||
* @param header
|
||||
* @param defVal
|
||||
* @return 如果解析失败返回默认值
|
||||
*/
|
||||
public String extractClientId(String header, final String defVal) {
|
||||
|
||||
if (header == null || !header.startsWith(BASIC_)) {
|
||||
log.debug("请求头中client信息为空: {}", header);
|
||||
return defVal;
|
||||
}
|
||||
byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] decoded;
|
||||
try {
|
||||
decoded = Base64.decode(base64Token);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
log.debug("Failed to decode basic authentication token: {}", header);
|
||||
return defVal;
|
||||
}
|
||||
|
||||
String token = new String(decoded, StandardCharsets.UTF_8);
|
||||
|
||||
int delim = token.indexOf(":");
|
||||
|
||||
if (delim == -1) {
|
||||
log.debug("Invalid basic authentication token: {}", header);
|
||||
return defVal;
|
||||
}
|
||||
return token.substring(0, delim);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中解析 client id
|
||||
* @param header
|
||||
* @return
|
||||
*/
|
||||
public Optional<String> extractClientId(String header) {
|
||||
return Optional.ofNullable(extractClientId(header, null));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
com.pig4cloud.pigx.common.core.config.JacksonConfiguration
|
||||
com.pig4cloud.pigx.common.core.config.MessageSourceConfiguration
|
||||
com.pig4cloud.pigx.common.core.config.RestTemplateConfiguration
|
||||
com.pig4cloud.pigx.common.core.util.SpringContextHolder
|
||||
16
pigx-common/pigx-common-core/src/main/resources/banner.txt
Normal file
16
pigx-common/pigx-common-core/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
${AnsiColor.BRIGHT_YELLOW}
|
||||
|
||||
::::::::: ::::::::::: :::::::: ::: :::
|
||||
:+: :+: :+: :+: :+: :+: :+:
|
||||
+:+ +:+ +:+ +:+ +:+ +:+
|
||||
+#++:++#+ +#+ :#: +#++:+
|
||||
+#+ +#+ +#+ +#+# +#+ +#+
|
||||
#+# #+# #+# #+# #+# #+#
|
||||
### ########### ######## ### ###
|
||||
|
||||
www.pig4cloud.com
|
||||
|
||||
Pig Microservice Architecture
|
||||
${AnsiColor.DEFAULT}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
sys.user.update.passwordError=\u539F\u5BC6\u7801\u9519\u8BEF\uFF0C\u4FEE\u6539\u5931\u8D25
|
||||
sys.user.query.error=\u83B7\u53D6\u5F53\u524D\u7528\u6237\u4FE1\u606F\u5931\u8D25
|
||||
sys.user.import.succeed=\u7528\u6237\u5BFC\u5165\u6210\u529F\uFF0C\u9ED8\u8BA4\u5BC6\u7801\u4E3A\u624B\u673A\u53F7
|
||||
sys.user.username.existing={0} \u7528\u6237\u540D\u5DF2\u5B58\u5728
|
||||
sys.user.phone.existing={0} \u624b\u673a\u53f7\u5df2\u5b58\u5728
|
||||
sys.user.userInfo.empty={0} \u7528\u6237\u4FE1\u606F\u4E3A\u7A7A
|
||||
sys.dept.deptName.inexistence={0} \u90E8\u95E8\u540D\u79F0\u4E0D\u5B58\u5728
|
||||
sys.post.postName.inexistence={0} \u5C97\u4F4D\u540D\u79F0\u4E0D\u5B58\u5728
|
||||
sys.post.nameOrCode.existing={0} {1} \u5C97\u4F4D\u540D\u6216\u5C97\u4F4D\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728
|
||||
sys.role.roleName.inexistence={0} \u89D2\u8272\u540D\u79F0\u4E0D\u5B58\u5728
|
||||
sys.role.nameOrCode.existing={0} {1} \u89D2\u8272\u540D\u6216\u89D2\u8272\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728
|
||||
sys.param.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u53C2\u6570\u4E0D\u80FD\u5220\u9664
|
||||
sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF
|
||||
sys.menu.delete.existing=\u83DC\u5355\u542B\u6709\u4E0B\u7EA7\u4E0D\u80FD\u5220\u9664
|
||||
sys.app.sms.often=\u9A8C\u8BC1\u7801\u53D1\u9001\u8FC7\u9891\u7E41
|
||||
sys.app.phone.unregistered={0} \u624B\u673A\u53F7\u672A\u6CE8\u518C
|
||||
sys.dict.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u5220\u9664
|
||||
sys.dict.update.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u4FEE\u6539
|
||||
sys.connect.cp.dept.sync.error=\u83B7\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u90E8\u95E8\u5217\u8868\u5931\u8D25
|
||||
sys.connect.cp.user.sync.error=\u83B7\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u7528\u6237\u5217\u8868\u5931\u8D25
|
||||
app.user.userInfo.empty={0} \u7528\u6237\u4FE1\u606F\u4E3A\u7A7A
|
||||
56
pigx-common/pigx-common-data/pom.xml
Normal file
56
pigx-common/pigx-common-data/pom.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pigx-common-data</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>pigx 数据操作相关</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!--工具类核心包-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
</dependency>
|
||||
<!--mybatis plus-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-extension</artifactId>
|
||||
</dependency>
|
||||
<!-- 连表查询依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- druid 连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--安全依赖获取上下文信息-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-security</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--feign client-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>as-upms-api</artifactId>
|
||||
</dependency>
|
||||
<!--缓存依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.dao.PessimisticLockingFailureException;
|
||||
import org.springframework.data.redis.cache.CacheStatistics;
|
||||
import org.springframework.data.redis.cache.CacheStatisticsCollector;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link RedisCacheWriter} implementation capable of reading/writing binary data from/to
|
||||
* Redis in {@literal standalone} and {@literal cluster} environments. Works upon a given
|
||||
* {@link RedisConnectionFactory} to obtain the actual {@link RedisConnection}. <br />
|
||||
* {@link DefaultRedisCacheWriter} can be used in
|
||||
* {@link RedisCacheWriter#lockingRedisCacheWriter(RedisConnectionFactory) locking} or
|
||||
* {@link RedisCacheWriter#nonLockingRedisCacheWriter(RedisConnectionFactory) non-locking}
|
||||
* mode. While {@literal non-locking} aims for maximum performance it may result in
|
||||
* overlapping, non atomic, command execution for operations spanning multiple Redis
|
||||
* interactions like {@code putIfAbsent}. The {@literal locking} counterpart prevents
|
||||
* command overlap by setting an explicit lock key and checking against presence of this
|
||||
* key which leads to additional requests and potential command wait times.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
class DefaultRedisCacheWriter implements RedisCacheWriter {
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
|
||||
private final Duration sleepTime;
|
||||
|
||||
/**
|
||||
* @param connectionFactory must not be {@literal null}.
|
||||
*/
|
||||
DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory) {
|
||||
this(connectionFactory, Duration.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param connectionFactory must not be {@literal null}.
|
||||
* @param sleepTime sleep time between lock request attempts. Must not be
|
||||
* {@literal null}. Use {@link Duration#ZERO} to disable locking.
|
||||
*/
|
||||
private DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
|
||||
|
||||
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
|
||||
Assert.notNull(sleepTime, "SleepTime must not be null!");
|
||||
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.sleepTime = sleepTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
execute(name, connection -> {
|
||||
|
||||
if (shouldExpireWithin(ttl)) {
|
||||
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
|
||||
}
|
||||
else {
|
||||
connection.set(key, value);
|
||||
}
|
||||
|
||||
return "OK";
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] get(String name, byte[] key) {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
|
||||
return execute(name, connection -> connection.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
return execute(name, connection -> {
|
||||
|
||||
if (isLockingCacheWriter()) {
|
||||
doLock(name, connection);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Boolean.TRUE.equals(connection.setNX(key, value))) {
|
||||
|
||||
if (shouldExpireWithin(ttl)) {
|
||||
connection.pExpire(key, ttl.toMillis());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return connection.get(key);
|
||||
}
|
||||
finally {
|
||||
|
||||
if (isLockingCacheWriter()) {
|
||||
doUnlock(name, connection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除,原来是删除指定的键,目前修改为既可以删除指定键的数据,也是可以删除某个前缀开始的所有数据
|
||||
* @param name
|
||||
* @param key
|
||||
*/
|
||||
@Override
|
||||
public void remove(String name, byte[] key) {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
|
||||
execute(name, connection -> {
|
||||
// 获取某个前缀所拥有的所有的键,某个前缀开头,后面肯定是*
|
||||
Set<byte[]> keys = connection.keys(key);
|
||||
int delNum = 0;
|
||||
Assert.notNull(keys, "keys must not be null!");
|
||||
for (byte[] keyByte : keys) {
|
||||
delNum += connection.del(keyByte);
|
||||
}
|
||||
return delNum;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean(String name, byte[] pattern) {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
Assert.notNull(pattern, "Pattern must not be null!");
|
||||
|
||||
execute(name, connection -> {
|
||||
|
||||
boolean wasLocked = false;
|
||||
|
||||
try {
|
||||
|
||||
if (isLockingCacheWriter()) {
|
||||
doLock(name, connection);
|
||||
wasLocked = true;
|
||||
}
|
||||
|
||||
byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
|
||||
.toArray(new byte[0][]);
|
||||
|
||||
if (keys.length > 0) {
|
||||
connection.del(keys);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
||||
if (wasLocked && isLockingCacheWriter()) {
|
||||
doUnlock(name, connection);
|
||||
}
|
||||
}
|
||||
|
||||
return "OK";
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearStatistics(String s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RedisCacheWriter withStatisticsCollector(CacheStatisticsCollector cacheStatisticsCollector) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set a write lock on a cache.
|
||||
* @param name the name of the cache to lock.
|
||||
*/
|
||||
void lock(String name) {
|
||||
execute(name, connection -> doLock(name, connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly remove a write lock from a cache.
|
||||
* @param name the name of the cache to unlock.
|
||||
*/
|
||||
void unlock(String name) {
|
||||
executeLockFree(connection -> doUnlock(name, connection));
|
||||
}
|
||||
|
||||
private Boolean doLock(String name, RedisConnection connection) {
|
||||
return connection.setNX(createCacheLockKey(name), new byte[0]);
|
||||
}
|
||||
|
||||
private Long doUnlock(String name, RedisConnection connection) {
|
||||
return connection.del(createCacheLockKey(name));
|
||||
}
|
||||
|
||||
boolean doCheckLock(String name, RedisConnection connection) {
|
||||
return connection.exists(createCacheLockKey(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if {@link RedisCacheWriter} uses locks.
|
||||
*/
|
||||
private boolean isLockingCacheWriter() {
|
||||
return !sleepTime.isZero() && !sleepTime.isNegative();
|
||||
}
|
||||
|
||||
private <T> T execute(String name, Function<RedisConnection, T> callback) {
|
||||
|
||||
try (RedisConnection connection = connectionFactory.getConnection()) {
|
||||
checkAndPotentiallyWaitUntilUnlocked(name, connection);
|
||||
return callback.apply(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeLockFree(Consumer<RedisConnection> callback) {
|
||||
try (RedisConnection connection = connectionFactory.getConnection()) {
|
||||
callback.accept(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
|
||||
|
||||
if (!isLockingCacheWriter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
while (doCheckLock(name, connection)) {
|
||||
Thread.sleep(sleepTime.toMillis());
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
|
||||
// Re-interrupt current thread, to allow other participants to react.
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
throw new PessimisticLockingFailureException(
|
||||
String.format("Interrupted while waiting to unlock cache %s", name), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldExpireWithin(@Nullable Duration ttl) {
|
||||
return ttl != null && !ttl.isZero() && !ttl.isNegative();
|
||||
}
|
||||
|
||||
private static byte[] createCacheLockKey(String name) {
|
||||
return (name + "~lock").getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheStatistics getCacheStatistics(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.convert.DurationStyle;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* redis cache 扩展cache name自动化配置
|
||||
*
|
||||
* @author L.cm
|
||||
* @author lengleng
|
||||
* <p>
|
||||
* cachename = xx#ttl
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisAutoCacheManager extends RedisCacheManager {
|
||||
|
||||
private static final String SPLIT_FLAG = "#";
|
||||
|
||||
private static final int CACHE_LENGTH = 2;
|
||||
|
||||
RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
|
||||
Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
|
||||
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
|
||||
if (StrUtil.isBlank(name) || !name.contains(SPLIT_FLAG)) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
|
||||
String[] cacheArray = name.split(SPLIT_FLAG);
|
||||
if (cacheArray.length < CACHE_LENGTH) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
|
||||
if (cacheConfig != null) {
|
||||
Duration duration = DurationStyle.detectAndParse(cacheArray[1], ChronoUnit.SECONDS);
|
||||
cacheConfig = cacheConfig.entryTtl(duration);
|
||||
}
|
||||
return super.createRedisCache(cacheArray[0], cacheConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从上下文中获取租户ID,重写@Cacheable value 值
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Cache getCache(String name) {
|
||||
// see https://gitee.wang/pig/pigx/issues/613
|
||||
if (name.startsWith(CacheConstants.GLOBALLY)) {
|
||||
return super.getCache(name);
|
||||
}
|
||||
return super.getCache(TenantContextHolder.getTenantId() + StrUtil.COLON + name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 扩展redis-cache支持注解cacheName添加超时时间
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureAfter({ RedisAutoConfiguration.class })
|
||||
@ConditionalOnBean({ RedisConnectionFactory.class })
|
||||
@EnableConfigurationProperties(CacheProperties.class)
|
||||
public class RedisCacheAutoConfiguration {
|
||||
|
||||
private final CacheProperties cacheProperties;
|
||||
|
||||
private final CacheManagerCustomizers customizerInvoker;
|
||||
|
||||
@Nullable
|
||||
private final RedisCacheConfiguration redisCacheConfiguration;
|
||||
|
||||
RedisCacheAutoConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker,
|
||||
ObjectProvider<RedisCacheConfiguration> redisCacheConfiguration) {
|
||||
this.cacheProperties = cacheProperties;
|
||||
this.customizerInvoker = customizerInvoker;
|
||||
this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ResourceLoader resourceLoader) {
|
||||
DefaultRedisCacheWriter redisCacheWriter = new DefaultRedisCacheWriter(connectionFactory);
|
||||
RedisCacheConfiguration cacheConfiguration = this.determineConfiguration(resourceLoader.getClassLoader());
|
||||
List<String> cacheNames = this.cacheProperties.getCacheNames();
|
||||
Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
|
||||
if (!cacheNames.isEmpty()) {
|
||||
Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
|
||||
cacheNames.forEach(it -> cacheConfigMap.put(it, cacheConfiguration));
|
||||
initialCaches.putAll(cacheConfigMap);
|
||||
}
|
||||
RedisAutoCacheManager cacheManager = new RedisAutoCacheManager(redisCacheWriter, cacheConfiguration,
|
||||
initialCaches, true);
|
||||
cacheManager.setTransactionAware(false);
|
||||
return this.customizerInvoker.customize(cacheManager);
|
||||
}
|
||||
|
||||
private RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) {
|
||||
if (this.redisCacheConfiguration != null) {
|
||||
return this.redisCacheConfiguration;
|
||||
}
|
||||
else {
|
||||
CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||
.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
|
||||
if (redisProperties.getTimeToLive() != null) {
|
||||
config = config.entryTtl(redisProperties.getTimeToLive());
|
||||
}
|
||||
|
||||
if (redisProperties.getKeyPrefix() != null) {
|
||||
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
|
||||
}
|
||||
|
||||
if (!redisProperties.isCacheNullValues()) {
|
||||
config = config.disableCachingNullValues();
|
||||
}
|
||||
|
||||
if (!redisProperties.isUseKeyPrefix()) {
|
||||
config = config.disableKeyPrefix();
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CacheManagerCustomizers配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(CacheManagerCustomizers.class)
|
||||
public class RedisCacheManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
public CacheManagerCustomizers cacheManagerCustomizers(
|
||||
ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
|
||||
return new CacheManagerCustomizers(customizers.getIfAvailable());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2022/2/4
|
||||
*
|
||||
* redis message 信道相关配置
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RedisMessageConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(redisConnectionFactory);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* RedisTemplate 配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@EnableCaching
|
||||
@Configuration
|
||||
@AutoConfigureBefore(name = { "org.redisson.spring.starter.RedissonAutoConfiguration",
|
||||
"org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration" })
|
||||
public class RedisTemplateConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/8/30 数据权限查询参数
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DataScope extends HashMap {
|
||||
|
||||
/**
|
||||
* 限制范围的字段名称
|
||||
*/
|
||||
private String scopeDeptName = "dept_id";
|
||||
|
||||
/**
|
||||
* 本人权限范围字段
|
||||
*/
|
||||
private String scopeUserName = "create_by";
|
||||
|
||||
/**
|
||||
* 具体的数据范围
|
||||
*/
|
||||
private List<Long> deptList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 具体查询的用户数据权限范围
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 是否只查询本部门
|
||||
*/
|
||||
private Boolean isOnly = false;
|
||||
|
||||
/**
|
||||
* 函数名称,默认 SELECT * ;
|
||||
*
|
||||
* <ul>
|
||||
* <li>COUNT(1)</li>
|
||||
* </ul>
|
||||
*/
|
||||
private DataScopeFuncEnum func = DataScopeFuncEnum.ALL;
|
||||
|
||||
/**
|
||||
* of 获取实例
|
||||
*/
|
||||
public static DataScope of() {
|
||||
return new DataScope();
|
||||
}
|
||||
|
||||
public DataScope deptIds(List<Long> deptIds) {
|
||||
this.deptList = deptIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataScope only(boolean isOnly) {
|
||||
this.isOnly = isOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataScope func(DataScopeFuncEnum func) {
|
||||
this.func = func;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据权限函数类型
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020-06-17
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DataScopeFuncEnum {
|
||||
|
||||
/**
|
||||
* 查询全部数据 SELECT * FROM (originSql) temp_data_scope WHERE temp_data_scope.dept_id IN
|
||||
* (1)
|
||||
*/
|
||||
ALL("*", "全部"),
|
||||
|
||||
/**
|
||||
* 查询函数COUNT SELECT COUNT(1) FROM (originSql) temp_data_scope WHERE
|
||||
* temp_data_scope.dept_id IN (1)
|
||||
*/
|
||||
COUNT("COUNT(1)", "自定义");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-09-07
|
||||
* <p>
|
||||
* data scope 判断处理器,抽象服务扩展
|
||||
*/
|
||||
public interface DataScopeHandle {
|
||||
|
||||
/**
|
||||
* 计算用户数据权限
|
||||
* @param dataScope 数据权限设置
|
||||
* @return 返回true表示无需进行数据过滤处理,返回false表示需要进行数据过滤
|
||||
*/
|
||||
Boolean calcScope(DataScope dataScope);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/11/29
|
||||
*/
|
||||
public class DataScopeInnerInterceptor implements DataScopeInterceptor {
|
||||
|
||||
@Setter
|
||||
private DataScopeHandle dataScopeHandle;
|
||||
|
||||
@Override
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
|
||||
ResultHandler resultHandler, BoundSql boundSql) {
|
||||
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
|
||||
|
||||
String originalSql = boundSql.getSql();
|
||||
Object parameterObject = boundSql.getParameterObject();
|
||||
|
||||
// 查找参数中包含DataScope类型的参数
|
||||
DataScope dataScope = findDataScopeObject(parameterObject);
|
||||
if (dataScope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 返回true 不拦截直接返回原始 SQL (只针对 * 查询)
|
||||
if (dataScopeHandle.calcScope(dataScope) && DataScopeFuncEnum.ALL.equals(dataScope.getFunc())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 返回true 不拦截直接返回原始 SQL (只针对 COUNT 查询)
|
||||
if (dataScopeHandle.calcScope(dataScope) && DataScopeFuncEnum.COUNT.equals(dataScope.getFunc())) {
|
||||
mpBs.sql(String.format("SELECT %s FROM (%s) temp_data_scope", dataScope.getFunc().getType(), originalSql));
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> deptIds = dataScope.getDeptList();
|
||||
|
||||
// 1.无数据权限限制,则直接返回 0 条数据
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(dataScope.getUsername())) {
|
||||
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2",
|
||||
dataScope.getFunc().getType(), originalSql);
|
||||
}
|
||||
// 2.如果为本人权限则走下面
|
||||
else if (StrUtil.isNotBlank(dataScope.getUsername())) {
|
||||
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername());
|
||||
}
|
||||
// 3.都没有,则是其他权限,走下面
|
||||
else {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeDeptName(), join);
|
||||
}
|
||||
|
||||
mpBs.sql(originalSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找参数是否包括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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
|
||||
/**
|
||||
* 数据权限抽象
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/8/9
|
||||
*/
|
||||
public interface DataScopeInterceptor extends InnerInterceptor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.github.yulichang.injector.MPJSqlInjector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支持自定义数据权限方法注入
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020-06-17
|
||||
*/
|
||||
public class DataScopeSqlInjector extends MPJSqlInjector {
|
||||
|
||||
@Override
|
||||
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
|
||||
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
|
||||
methodList.add(new SelectListByScope());
|
||||
methodList.add(new SelectPageByScope());
|
||||
methodList.add(new SelectCountByScope());
|
||||
return methodList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/12/26
|
||||
* <p>
|
||||
* 数据权限类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DataScopeTypeEnum {
|
||||
|
||||
/**
|
||||
* 查询全部数据
|
||||
*/
|
||||
ALL(0, "全部"),
|
||||
|
||||
/**
|
||||
* 自定义
|
||||
*/
|
||||
CUSTOM(1, "自定义"),
|
||||
|
||||
/**
|
||||
* 本级及子级
|
||||
*/
|
||||
OWN_CHILD_LEVEL(2, "本级及子级"),
|
||||
|
||||
/**
|
||||
* 本级
|
||||
*/
|
||||
OWN_LEVEL(3, "本级"),
|
||||
|
||||
/**
|
||||
* 本人
|
||||
*/
|
||||
SELF_LEVEL(4, "本人");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final int type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 扩展通用 Mapper,支持数据权限 和批量插入
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020-06-17
|
||||
*/
|
||||
public interface PigxBaseMapper<T> extends MPJBaseMapper<T> {
|
||||
|
||||
/**
|
||||
* 根据 entity 条件,查询全部记录
|
||||
* @param queryWrapper 实体对象封装操作类(可以为 null)
|
||||
* @param scope 数据权限范围
|
||||
* @return List<T>
|
||||
*/
|
||||
List<T> selectListByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope scope);
|
||||
|
||||
/**
|
||||
* 根据 entity 条件,查询全部记录(并翻页)
|
||||
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
|
||||
* @param queryWrapper 实体对象封装操作类(可以为 null)
|
||||
* @param scope 数据权限范围
|
||||
* @return Page
|
||||
*/
|
||||
<E extends IPage<T>> E selectPageByScope(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper,
|
||||
DataScope scope);
|
||||
|
||||
/**
|
||||
* 根据 Wrapper 条件,查询总记录数
|
||||
* @param queryWrapper 实体对象封装操作类(可以为 null)
|
||||
* @param scope 数据权限范围
|
||||
* @return Integer
|
||||
*/
|
||||
Long selectCountByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope scope);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysDept;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysRole;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteDataScopeService;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.enums.UserTypeEnum;
|
||||
import com.pig4cloud.pigx.common.core.util.RetOps;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-09-07
|
||||
* <p>
|
||||
* 默认data scope 判断处理器
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class PigxDefaultDatascopeHandle implements DataScopeHandle {
|
||||
|
||||
private final RemoteDataScopeService dataScopeService;
|
||||
|
||||
/**
|
||||
* 计算用户数据权限
|
||||
* @param dataScope 数据权限范围
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Boolean calcScope(DataScope dataScope) {
|
||||
PigxUser user = SecurityUtils.getUser();
|
||||
// toc 客户端不进行数据权限
|
||||
if (UserTypeEnum.TOC.getStatus().equals(user.getUserType())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<String> roleIdList = user.getAuthorities().stream().map(GrantedAuthority::getAuthority)
|
||||
.filter(authority -> authority.startsWith(SecurityConstants.ROLE))
|
||||
.map(authority -> authority.split(StrUtil.UNDERLINE)[1]).collect(Collectors.toList());
|
||||
|
||||
List<Long> deptList = dataScope.getDeptList();
|
||||
|
||||
// 当前用户的角色为空 , 返回false
|
||||
if (CollectionUtil.isEmpty(roleIdList)) {
|
||||
return false;
|
||||
}
|
||||
// @formatter:off
|
||||
SysRole role = RetOps.of(dataScopeService.getRoleList(roleIdList))
|
||||
.getData()
|
||||
.orElseGet(Collections::emptyList)
|
||||
.stream()
|
||||
.min(Comparator.comparingInt(SysRole::getDsType)).orElse(null);
|
||||
// @formatter:on
|
||||
// 角色有可能已经删除了
|
||||
if (role == null) {
|
||||
return false;
|
||||
}
|
||||
Integer dsType = role.getDsType();
|
||||
// 查询全部
|
||||
if (DataScopeTypeEnum.ALL.getType() == dsType) {
|
||||
return true;
|
||||
}
|
||||
// 自定义
|
||||
if (DataScopeTypeEnum.CUSTOM.getType() == dsType && StrUtil.isNotBlank(role.getDsScope())) {
|
||||
String dsScope = role.getDsScope();
|
||||
deptList.addAll(
|
||||
Arrays.stream(dsScope.split(StrUtil.COMMA)).map(Long::parseLong).collect(Collectors.toList()));
|
||||
}
|
||||
// 查询本级及其下级
|
||||
if (DataScopeTypeEnum.OWN_CHILD_LEVEL.getType() == dsType) {
|
||||
// @formatter:off
|
||||
List<Long> deptIdList = RetOps.of(dataScopeService.getDescendantList(user.getDeptId()))
|
||||
.getData()
|
||||
.orElseGet(Collections::emptyList)
|
||||
.stream()
|
||||
.map(SysDept::getDeptId).collect(Collectors.toList());
|
||||
// @formatter:on
|
||||
deptList.addAll(deptIdList);
|
||||
}
|
||||
// 只查询本级
|
||||
if (DataScopeTypeEnum.OWN_LEVEL.getType() == dsType) {
|
||||
deptList.add(user.getDeptId());
|
||||
}
|
||||
|
||||
// 只查询本人
|
||||
if (DataScopeTypeEnum.SELF_LEVEL.getType() == dsType) {
|
||||
dataScope.setUsername(user.getUsername());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlSource;
|
||||
|
||||
/**
|
||||
* 扩展支持COUNT查询数量
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020/6/17
|
||||
*/
|
||||
public class SelectCountByScope extends AbstractMethod {
|
||||
|
||||
public SelectCountByScope() {
|
||||
super("selectCountByScope");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
||||
SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
|
||||
|
||||
String sql = String.format(sqlMethod.getSql(), this.sqlFirst(), this.sqlSelectColumns(tableInfo, true),
|
||||
tableInfo.getTableName(), this.sqlWhereEntityWrapper(true, tableInfo), this.sqlOrderBy(tableInfo),
|
||||
this.sqlComment());
|
||||
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
|
||||
|
||||
return this.addSelectMappedStatementForOther(mapperClass, sqlSource, Long.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlSource;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/26
|
||||
*/
|
||||
public class SelectListByScope extends AbstractMethod {
|
||||
|
||||
public SelectListByScope() {
|
||||
super("selectListByScope");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
||||
SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
|
||||
String sql = String.format(sqlMethod.getSql(), this.sqlFirst(), this.sqlSelectColumns(tableInfo, true),
|
||||
tableInfo.getTableName(), this.sqlWhereEntityWrapper(true, tableInfo), this.sqlOrderBy(tableInfo),
|
||||
this.sqlComment());
|
||||
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
|
||||
return this.addSelectMappedStatementForTable(mapperClass, sqlSource, tableInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.pig4cloud.pigx.common.data.datascope;
|
||||
|
||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlSource;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/26
|
||||
*/
|
||||
public class SelectPageByScope extends AbstractMethod {
|
||||
|
||||
public SelectPageByScope() {
|
||||
super("selectPageByScope");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
||||
SqlMethod sqlMethod = SqlMethod.SELECT_PAGE;
|
||||
String sql = String.format(sqlMethod.getSql(), this.sqlFirst(), this.sqlSelectColumns(tableInfo, true),
|
||||
tableInfo.getTableName(), this.sqlWhereEntityWrapper(true, tableInfo), this.sqlOrderBy(tableInfo),
|
||||
this.sqlComment());
|
||||
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
|
||||
return this.addSelectMappedStatementForTable(mapperClass, sqlSource, tableInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.pig4cloud.pigx.common.data.handler;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Mybatis数组,符串互转
|
||||
* <p>
|
||||
* MappedJdbcTypes 数据库中的数据类型 MappedTypes java中的的数据类型
|
||||
*
|
||||
* @author xuzihui
|
||||
* @date 2019-11-20
|
||||
*/
|
||||
@MappedTypes(value = { Long[].class })
|
||||
@MappedJdbcTypes(value = JdbcType.VARCHAR)
|
||||
public class JsonLongArrayTypeHandler extends BaseTypeHandler<Long[]> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Long[] parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
ps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Long[] getNullableResult(ResultSet rs, String columnName) {
|
||||
String reString = rs.getString(columnName);
|
||||
return Convert.toLongArray(reString);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Long[] getNullableResult(ResultSet rs, int columnIndex) {
|
||||
String reString = rs.getString(columnIndex);
|
||||
return Convert.toLongArray(reString);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Long[] getNullableResult(CallableStatement cs, int columnIndex) {
|
||||
String reString = cs.getString(columnIndex);
|
||||
return Convert.toLongArray(reString);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.pig4cloud.pigx.common.data.handler;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Mybatis数组,符串互转
|
||||
* <p>
|
||||
* MappedJdbcTypes 数据库中的数据类型 MappedTypes java中的的数据类型
|
||||
*
|
||||
* @author xuzihui
|
||||
* @date 2019-11-20
|
||||
*/
|
||||
@MappedTypes(value = { String[].class })
|
||||
@MappedJdbcTypes(value = JdbcType.VARCHAR)
|
||||
public class JsonStringArrayTypeHandler extends BaseTypeHandler<String[]> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
ps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String[] getNullableResult(ResultSet rs, String columnName) {
|
||||
String reString = rs.getString(columnName);
|
||||
return Convert.toStrArray(reString);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String[] getNullableResult(ResultSet rs, int columnIndex) {
|
||||
String reString = rs.getString(columnIndex);
|
||||
return Convert.toStrArray(reString);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String[] getNullableResult(CallableStatement cs, int columnIndex) {
|
||||
String reString = cs.getString(columnIndex);
|
||||
return Convert.toStrArray(reString);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.mybatis;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.druid.DbType;
|
||||
import com.alibaba.druid.filter.FilterChain;
|
||||
import com.alibaba.druid.filter.FilterEventAdapter;
|
||||
import com.alibaba.druid.proxy.jdbc.JdbcParameter;
|
||||
import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
|
||||
import com.alibaba.druid.proxy.jdbc.StatementProxy;
|
||||
import com.alibaba.druid.sql.SQLUtils;
|
||||
import com.alibaba.druid.sql.ast.SQLStatement;
|
||||
import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
|
||||
import com.alibaba.druid.stat.TableStat;
|
||||
import com.alibaba.druid.util.StringUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 打印可执行的 sql 日志
|
||||
*
|
||||
* <p>
|
||||
* 参考:https://jfinal.com/share/2204
|
||||
* </p>
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DruidSqlLogFilter extends FilterEventAdapter {
|
||||
|
||||
private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
|
||||
|
||||
private final PigxMybatisProperties properties;
|
||||
|
||||
@Override
|
||||
protected void statementExecuteBefore(StatementProxy statement, String sql) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteBatchBefore(StatementProxy statement) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException {
|
||||
// 先调用父类关闭 statement
|
||||
super.statement_close(chain, statement);
|
||||
// 支持动态开启
|
||||
if (!properties.isShowSql()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 是否开启调试
|
||||
if (!log.isInfoEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 打印可执行的 sql
|
||||
String sql = statement.getBatchSql();
|
||||
// sql 为空直接返回
|
||||
if (StringUtils.isEmpty(sql)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
|
||||
|
||||
// 判断表名是配置了匹配过滤
|
||||
if (CollUtil.isNotEmpty(properties.getSkipTable())) {
|
||||
List<String> skipTableList = properties.getSkipTable();
|
||||
|
||||
List<String> tableNameList = getTablesBydruid(sql, dbType);
|
||||
if (tableNameList.stream().anyMatch(tableName -> StrUtil.containsAnyIgnoreCase(tableName,
|
||||
ArrayUtil.toArray(skipTableList, String.class)))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int parametersSize = statement.getParametersSize();
|
||||
List<Object> parameters = new ArrayList<>(parametersSize);
|
||||
for (int i = 0; i < parametersSize; ++i) {
|
||||
// 转换参数,处理 java8 时间
|
||||
parameters.add(getJdbcParameter(statement.getParameter(i)));
|
||||
}
|
||||
String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
|
||||
printSql(formattedSql, statement);
|
||||
}
|
||||
|
||||
private static Object getJdbcParameter(JdbcParameter jdbcParam) {
|
||||
if (jdbcParam == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = jdbcParam.getValue();
|
||||
// 处理 java8 时间
|
||||
if (value instanceof TemporalAccessor) {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void printSql(String sql, StatementProxy statement) {
|
||||
// 打印 sql
|
||||
String sqlLogger = "\n\n======= Sql Logger ======================" + "\n{}"
|
||||
+ "\n======= Sql Execute Time: {} =======\n";
|
||||
log.info(sqlLogger, sql.trim(), format(statement.getLastExecuteTimeNano()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化执行时间,单位为 ms 和 s,保留三位小数
|
||||
* @param nanos 纳秒
|
||||
* @return 格式化后的时间
|
||||
*/
|
||||
private static String format(long nanos) {
|
||||
if (nanos < 1) {
|
||||
return "0ms";
|
||||
}
|
||||
double millis = (double) nanos / (1000 * 1000);
|
||||
// 不够 1 ms,最小单位为 ms
|
||||
if (millis > 1000) {
|
||||
return String.format("%.3fs", millis / 1000);
|
||||
}
|
||||
else {
|
||||
return String.format("%.3fms", millis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SQL中提取表名(sql中出现的所有表)
|
||||
* @param sql sql语句
|
||||
* @param dbType dbType
|
||||
* @return List<String>
|
||||
*/
|
||||
public static List<String> getTablesBydruid(String sql, String dbType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
|
||||
for (SQLStatement stmt : stmtList) {
|
||||
// 也可以用更精确的解析器,如MySqlSchemaStatVisitor
|
||||
SchemaStatVisitor visitor = new SchemaStatVisitor();
|
||||
stmt.accept(visitor);
|
||||
Map<TableStat.Name, TableStat> tables = visitor.getTables();
|
||||
for (TableStat.Name name : tables.keySet()) {
|
||||
result.add(name.getName());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteDataScopeService;
|
||||
import com.pig4cloud.pigx.common.data.datascope.DataScopeInnerInterceptor;
|
||||
import com.pig4cloud.pigx.common.data.datascope.DataScopeInterceptor;
|
||||
import com.pig4cloud.pigx.common.data.datascope.DataScopeSqlInjector;
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxDefaultDatascopeHandle;
|
||||
import com.pig4cloud.pigx.common.data.resolver.SqlFilterArgumentResolver;
|
||||
import com.pig4cloud.pigx.common.data.tenant.PigxTenantConfigProperties;
|
||||
import com.pig4cloud.pigx.common.data.tenant.PigxTenantHandler;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-02-08
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnBean(DataSource.class)
|
||||
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(PigxMybatisProperties.class)
|
||||
public class MybatisPlusConfiguration implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 增加请求参数解析器,对请求中的参数注入SQL 检查
|
||||
* @param resolverList
|
||||
*/
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolverList) {
|
||||
resolverList.add(new SqlFilterArgumentResolver());
|
||||
}
|
||||
|
||||
/**
|
||||
* mybatis plus 拦截器配置
|
||||
* @return PigxDefaultDatascopeHandle
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantLineInnerInterceptor tenantLineInnerInterceptor,
|
||||
DataScopeInterceptor dataScopeInterceptor) {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 注入多租户支持
|
||||
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
|
||||
// 数据权限
|
||||
interceptor.addInnerInterceptor(dataScopeInterceptor);
|
||||
// 分页支持
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
|
||||
paginationInnerInterceptor.setMaxLimit(1000L);
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建租户维护处理器对象
|
||||
* @return 处理后的租户维护处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineInnerInterceptor tenantLineInnerInterceptor(PigxTenantConfigProperties tenantConfigProperties) {
|
||||
TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor();
|
||||
tenantLineInnerInterceptor.setTenantLineHandler(new PigxTenantHandler(tenantConfigProperties));
|
||||
return tenantLineInnerInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限拦截器
|
||||
* @return DataScopeInterceptor
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(PigxUser.class)
|
||||
public DataScopeInterceptor dataScopeInterceptor(RemoteDataScopeService dataScopeService) {
|
||||
DataScopeInnerInterceptor dataScopeInnerInterceptor = new DataScopeInnerInterceptor();
|
||||
dataScopeInnerInterceptor.setDataScopeHandle(new PigxDefaultDatascopeHandle(dataScopeService));
|
||||
return dataScopeInnerInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展 mybatis-plus baseMapper 支持数据权限
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnBean(DataScopeInterceptor.class)
|
||||
public DataScopeSqlInjector dataScopeSqlInjector() {
|
||||
return new DataScopeSqlInjector();
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 日志格式化
|
||||
* @return DruidSqlLogFilter
|
||||
*/
|
||||
@Bean
|
||||
public DruidSqlLogFilter sqlLogFilter(PigxMybatisProperties properties) {
|
||||
return new DruidSqlLogFilter(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审计字段自动填充
|
||||
* @return {@link MetaObjectHandler}
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() {
|
||||
return new MybatisPlusMetaObjectHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库方言配置
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public DatabaseIdProvider databaseIdProvider() {
|
||||
VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("SQL Server", "mssql");
|
||||
databaseIdProvider.setProperties(properties);
|
||||
return databaseIdProvider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.pig4cloud.pigx.common.data.mybatis;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* MybatisPlus 自动填充配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Slf4j
|
||||
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
log.debug("mybatis plus start insert fill ....");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 审计字段自动填充,覆盖用户输入
|
||||
fillValIfNullByName("createTime", now, metaObject, true);
|
||||
fillValIfNullByName("updateTime", now, metaObject, true);
|
||||
fillValIfNullByName("createBy", getUserName(), metaObject, true);
|
||||
fillValIfNullByName("updateBy", getUserName(), metaObject, true);
|
||||
|
||||
// 删除标记自动填充
|
||||
fillValIfNullByName("delFlag", CommonConstants.STATUS_NORMAL, metaObject, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
log.debug("mybatis plus start update fill ....");
|
||||
fillValIfNullByName("updateTime", LocalDateTime.now(), metaObject, true);
|
||||
fillValIfNullByName("updateBy", getUserName(), metaObject, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充值,先判断是否有手动设置,优先手动设置的值,例如:job必须手动设置
|
||||
* @param fieldName 属性名
|
||||
* @param fieldVal 属性值
|
||||
* @param metaObject MetaObject
|
||||
* @param isCover 是否覆盖原有值,避免更新操作手动入参
|
||||
*/
|
||||
private static void fillValIfNullByName(String fieldName, Object fieldVal, MetaObject metaObject, boolean isCover) {
|
||||
// 0. 如果填充值为空
|
||||
if (fieldVal == null) {
|
||||
return;
|
||||
}
|
||||
// 1. 没有 get 方法
|
||||
if (!metaObject.hasSetter(fieldName)) {
|
||||
return;
|
||||
}
|
||||
// 2. 如果用户有手动设置的值
|
||||
Object userSetValue = metaObject.getValue(fieldName);
|
||||
String setValueStr = StrUtil.str(userSetValue, Charset.defaultCharset());
|
||||
if (StrUtil.isNotBlank(setValueStr) && !isCover) {
|
||||
return;
|
||||
}
|
||||
// 3. field 类型相同时设置
|
||||
Class<?> getterType = metaObject.getGetterType(fieldName);
|
||||
if (ClassUtils.isAssignableValue(getterType, fieldVal)) {
|
||||
metaObject.setValue(fieldName, fieldVal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 spring security 当前的用户名
|
||||
* @return 当前用户名
|
||||
*/
|
||||
private String getUserName() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
// 匿名接口直接返回
|
||||
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Optional.ofNullable(authentication).isPresent()) {
|
||||
return authentication.getName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.pig4cloud.pigx.common.data.mybatis;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Mybatis 配置
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2021/6/3
|
||||
*/
|
||||
@Data
|
||||
@RefreshScope
|
||||
@ConfigurationProperties("pigx.mybatis")
|
||||
public class PigxMybatisProperties {
|
||||
|
||||
/**
|
||||
* 是否打印可执行 sql
|
||||
*/
|
||||
private boolean showSql = true;
|
||||
|
||||
/**
|
||||
* 跳过表
|
||||
*/
|
||||
private List<String> skipTable = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.pig4cloud.pigx.common.data.resolver;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysDictItem;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteDictService;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author fxz
|
||||
* @date 2022/3/24 字典解析器
|
||||
*/
|
||||
@UtilityClass
|
||||
public class DictResolver {
|
||||
|
||||
/**
|
||||
* 根据字典类型获取所有字典项
|
||||
* @param type 字典类型
|
||||
* @return 字典数据项集合
|
||||
*/
|
||||
public List<SysDictItem> getDictItemsByType(String type) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(type), "参数不合法");
|
||||
|
||||
RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);
|
||||
|
||||
return remoteDictService.getDictByType(type).getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型以及字典项字典值获取字典标签
|
||||
* @param type 字典类型
|
||||
* @param itemValue 字典项字典值
|
||||
* @return 字典项标签值
|
||||
*/
|
||||
public String getDictItemLabel(String type, String itemValue) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法");
|
||||
|
||||
SysDictItem sysDictItem = getDictItemByItemValue(type, itemValue);
|
||||
|
||||
return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getLabel() : StringPool.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型以及字典标签获取字典值
|
||||
* @param type 字典类型
|
||||
* @param itemLabel 字典数据标签
|
||||
* @return 字典数据项值
|
||||
*/
|
||||
public String getDictItemValue(String type, String itemLabel) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法");
|
||||
|
||||
SysDictItem sysDictItem = getDictItemByItemLabel(type, itemLabel);
|
||||
|
||||
return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getItemValue() : StringPool.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型以及字典值获取字典项
|
||||
* @param type 字典类型
|
||||
* @param itemValue 字典数据值
|
||||
* @return 字典数据项
|
||||
*/
|
||||
public SysDictItem getDictItemByItemValue(String type, String itemValue) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法");
|
||||
|
||||
List<SysDictItem> dictItemList = getDictItemsByType(type);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(dictItemList)) {
|
||||
return dictItemList.stream().filter(item -> itemValue.equals(item.getItemValue())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型以及字典标签获取字典项
|
||||
* @param type 字典类型
|
||||
* @param itemLabel 字典数据项标签
|
||||
* @return 字典数据项
|
||||
*/
|
||||
public SysDictItem getDictItemByItemLabel(String type, String itemLabel) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法");
|
||||
|
||||
List<SysDictItem> dictItemList = getDictItemsByType(type);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(dictItemList)) {
|
||||
return dictItemList.stream().filter(item -> itemLabel.equals(item.getLabel())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.pig4cloud.pigx.common.data.resolver;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteParamService;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/5/12
|
||||
* <p>
|
||||
* 系统参数配置解析器
|
||||
*/
|
||||
@UtilityClass
|
||||
public class ParamResolver {
|
||||
|
||||
/**
|
||||
* 根据多个key 查询value 配置 结果使用hutool 的maputil 进行包装处理 MapUtil.getBool(result,key)
|
||||
* @param key key
|
||||
* @return Map<String,Object>
|
||||
*/
|
||||
public Map<String, Object> getMap(String... key) {
|
||||
// 校验入参是否合法
|
||||
if (Objects.isNull(key)) {
|
||||
throw new IllegalArgumentException("参数不合法");
|
||||
}
|
||||
|
||||
RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
|
||||
return remoteParamService.getByKeys(key, SecurityConstants.FROM_IN).getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key 查询value 配置
|
||||
* @param key key
|
||||
* @param defaultVal 默认值
|
||||
* @return value
|
||||
*/
|
||||
public Long getLong(String key, Long... defaultVal) {
|
||||
return checkAndGet(key, Long.class, defaultVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key 查询value 配置
|
||||
* @param key key
|
||||
* @param defaultVal 默认值
|
||||
* @return value
|
||||
*/
|
||||
public String getStr(String key, String... defaultVal) {
|
||||
return checkAndGet(key, String.class, defaultVal);
|
||||
}
|
||||
|
||||
private <T> T checkAndGet(String key, Class<T> clazz, T... defaultVal) {
|
||||
// 校验入参是否合法
|
||||
if (StrUtil.isBlank(key) || defaultVal.length > 1) {
|
||||
throw new IllegalArgumentException("参数不合法");
|
||||
}
|
||||
|
||||
RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
|
||||
|
||||
String result = remoteParamService.getByKey(key, SecurityConstants.FROM_IN).getData();
|
||||
|
||||
if (StrUtil.isNotBlank(result)) {
|
||||
return Convert.convert(clazz, result);
|
||||
}
|
||||
|
||||
if (defaultVal.length == 1) {
|
||||
return Convert.convert(clazz, defaultVal.clone()[0]);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.resolver;
|
||||
|
||||
import cn.hutool.core.comparator.CompareUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.exception.CheckedException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-06-24
|
||||
* <p>
|
||||
* 解决Mybatis Plus Order By SQL注入问题
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final static String[] KEYWORDS = { "master", "truncate", "insert", "select", "delete", "update", "declare",
|
||||
"alter", "drop", "sleep", "extractvalue", "concat" };
|
||||
|
||||
/**
|
||||
* 判断Controller是否包含page 参数
|
||||
* @param parameter 参数
|
||||
* @return 是否过滤
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.getParameterType().equals(Page.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parameter 入参集合
|
||||
* @param mavContainer model 和 view
|
||||
* @param webRequest web相关
|
||||
* @param binderFactory 入参解析
|
||||
* @return 检查后新的page对象
|
||||
* <p>
|
||||
* page 只支持查询 GET .如需解析POST获取请求报文体处理
|
||||
*/
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
|
||||
|
||||
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
|
||||
String ascs = request.getParameter("ascs");
|
||||
String descs = request.getParameter("descs");
|
||||
|
||||
String current = request.getParameter("current");
|
||||
String size = request.getParameter("size");
|
||||
|
||||
Page page = new Page();
|
||||
if (StrUtil.isNotBlank(current)) {
|
||||
// 如果current page 小于零 视为不合法数据
|
||||
if (CompareUtil.compare(Long.parseLong(current), 0L) < 0) {
|
||||
throw new CheckedException("current page error");
|
||||
}
|
||||
page.setCurrent(Long.parseLong(current));
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(size)) {
|
||||
page.setSize(Long.parseLong(size));
|
||||
}
|
||||
|
||||
List<OrderItem> orderItemList = new ArrayList<>();
|
||||
Optional.ofNullable(ascs).ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
|
||||
.filter(sqlInjectPredicate()).map(OrderItem::asc).collect(Collectors.toList())));
|
||||
Optional.ofNullable(descs).ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
|
||||
.filter(sqlInjectPredicate()).map(OrderItem::desc).collect(Collectors.toList())));
|
||||
page.addOrder(orderItemList);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户输入里面有没有关键字
|
||||
* @return Predicate
|
||||
*/
|
||||
private Predicate<String> sqlInjectPredicate() {
|
||||
return sql -> Arrays.stream(KEYWORDS).noneMatch(keyword -> StrUtil.containsIgnoreCase(sql, keyword));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.pig4cloud.pigx.common.data.resolver;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/9/29
|
||||
* <p>
|
||||
* 租户字符串处理(方便其他模块获取)
|
||||
*/
|
||||
public class TenantKeyStrResolver implements KeyStrResolver {
|
||||
|
||||
/**
|
||||
* 传入字符串增加 租户编号:in
|
||||
* @param in 输入字符串
|
||||
* @param split 分割符
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String extract(String in, String split) {
|
||||
return TenantContextHolder.getTenantId() + split + in;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前租户ID
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String key() {
|
||||
return String.valueOf(TenantContextHolder.getTenantId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/9/14
|
||||
*/
|
||||
@Slf4j
|
||||
public class PigxFeignTenantInterceptor implements RequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
if (TenantContextHolder.getTenantId() == null) {
|
||||
log.debug("TTL 中的 租户ID为空,feign拦截器 >> 跳过");
|
||||
return;
|
||||
}
|
||||
requestTemplate.header(CommonConstants.TENANT_ID, TenantContextHolder.getTenantId().toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 多租户配置
|
||||
*
|
||||
* @author oathsign
|
||||
*/
|
||||
@Data
|
||||
@RefreshScope
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "pigx.tenant")
|
||||
public class PigxTenantConfigProperties {
|
||||
|
||||
/**
|
||||
* 维护租户列名称
|
||||
*/
|
||||
private String column = "tenant_id";
|
||||
|
||||
/**
|
||||
* 多租户的数据表集合
|
||||
*/
|
||||
private List<String> tables = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/29
|
||||
* <p>
|
||||
* 租户信息拦截
|
||||
*/
|
||||
@Configuration
|
||||
public class PigxTenantConfiguration {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor pigxFeignTenantInterceptor() {
|
||||
return new PigxFeignTenantInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientHttpRequestInterceptor pigxTenantRequestInterceptor() {
|
||||
return new TenantRequestInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.NullValue;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018-12-26
|
||||
* <p>
|
||||
* 租户维护处理器
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PigxTenantHandler implements TenantLineHandler {
|
||||
|
||||
private final PigxTenantConfigProperties properties;
|
||||
|
||||
/**
|
||||
* 获取租户 ID 值表达式,只支持单个 ID 值
|
||||
* <p>
|
||||
* @return 租户 ID 值表达式
|
||||
*/
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
log.debug("当前租户为 >> {}", tenantId);
|
||||
|
||||
if (tenantId == null) {
|
||||
return new NullValue();
|
||||
}
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户字段名
|
||||
* @return 租户字段名
|
||||
*/
|
||||
@Override
|
||||
public String getTenantIdColumn() {
|
||||
return properties.getColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名判断是否忽略拼接多租户条件
|
||||
* <p>
|
||||
* 默认都要进行解析并拼接多租户条件
|
||||
* @param tableName 表名
|
||||
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
|
||||
*/
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
// 判断是否跳过当前查询的租户过滤
|
||||
if (TenantContextHolder.getTenantSkip()) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
// 租户中ID 为空,查询全部,不进行过滤
|
||||
if (tenantId == null) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
return !properties.getTables().contains(tableName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 租户运行时代理<br/>
|
||||
* 这是一个工具类,用于切换租户运行时,保护租户ID上下文<br/>
|
||||
* 下面这段代码演示问题所在 <pre>
|
||||
* void methodA(){
|
||||
* // 因为某些特殊原因,需要手动指定租户
|
||||
* TenantContextHolder.setTenantId(1);
|
||||
* // do something ...
|
||||
* }
|
||||
* void methodB(){
|
||||
* // 因为某些特殊原因,需要手动指定租户
|
||||
* TenantContextHolder.setTenantId(2);
|
||||
* methodA();
|
||||
* // 此时租户ID已经变成 1
|
||||
* // do something ...
|
||||
* }
|
||||
* </pre> 嵌套设置租户ID会导致租户上下文难以维护,并且很难察觉,容易导致数据错乱。 推荐的写法: <pre>
|
||||
* void methodA(){
|
||||
* TenantBroker.RunAs(1,() -> {
|
||||
* // do something ...
|
||||
* });
|
||||
* }
|
||||
* void methodB(){
|
||||
* TenantBroker.RunAs(2,() -> {
|
||||
* methodA();
|
||||
* // do something ...
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author CJ (jclazz@outlook.com)
|
||||
* @date 2020/6/12
|
||||
* @since 3.9
|
||||
*/
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class TenantBroker {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RunAs<T> {
|
||||
|
||||
/**
|
||||
* 执行业务逻辑
|
||||
* @param tenantId
|
||||
* @throws Exception
|
||||
*/
|
||||
void run(T tenantId) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ApplyAs<T, R> {
|
||||
|
||||
/**
|
||||
* 执行业务逻辑,返回一个值
|
||||
* @param tenantId
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
R apply(T tenantId) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 以某个租户的身份运行
|
||||
* @param tenant 租户ID
|
||||
* @param func
|
||||
*/
|
||||
public void runAs(Long tenant, RunAs<Long> func) {
|
||||
final Long pre = TenantContextHolder.getTenantId();
|
||||
try {
|
||||
log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
|
||||
TenantContextHolder.setTenantId(tenant);
|
||||
func.run(tenant);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
|
||||
}
|
||||
finally {
|
||||
log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
|
||||
TenantContextHolder.setTenantId(pre);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以某个租户的身份运行
|
||||
* @param tenant 租户ID
|
||||
* @param func
|
||||
* @param <T> 返回数据类型
|
||||
* @return
|
||||
*/
|
||||
public <T> T applyAs(Long tenant, ApplyAs<Long, T> func) {
|
||||
final Long pre = TenantContextHolder.getTenantId();
|
||||
try {
|
||||
log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
|
||||
TenantContextHolder.setTenantId(tenant);
|
||||
return func.apply(tenant);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
|
||||
}
|
||||
finally {
|
||||
log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
|
||||
TenantContextHolder.setTenantId(pre);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以某个租户的身份运行
|
||||
* @param supplier
|
||||
* @param func
|
||||
*/
|
||||
public void runAs(Supplier<Long> supplier, RunAs<Long> func) {
|
||||
runAs(supplier.get(), func);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以某个租户的身份运行
|
||||
* @param supplier
|
||||
* @param func
|
||||
* @param <T> 返回数据类型
|
||||
* @return
|
||||
*/
|
||||
public <T> T applyAs(Supplier<Long> supplier, ApplyAs<Long, T> func) {
|
||||
return applyAs(supplier.get(), func);
|
||||
}
|
||||
|
||||
public static class TenantBrokerExceptionWrapper extends RuntimeException {
|
||||
|
||||
public TenantBrokerExceptionWrapper(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public TenantBrokerExceptionWrapper(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/10/4 租户工具类
|
||||
*/
|
||||
@UtilityClass
|
||||
public class TenantContextHolder {
|
||||
|
||||
private final ThreadLocal<Long> THREAD_LOCAL_TENANT = new TransmittableThreadLocal<>();
|
||||
|
||||
private final ThreadLocal<Boolean> THREAD_LOCAL_TENANT_SKIP_FLAG = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* TTL 设置租户ID<br/>
|
||||
* <b>谨慎使用此方法,避免嵌套调用。尽量使用 {@code TenantBroker} </b>
|
||||
* @param tenantId
|
||||
* @see TenantBroker
|
||||
*/
|
||||
public void setTenantId(Long tenantId) {
|
||||
THREAD_LOCAL_TENANT.set(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否过滤的标识
|
||||
*/
|
||||
public void setTenantSkip() {
|
||||
THREAD_LOCAL_TENANT_SKIP_FLAG.set(Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TTL中的租户ID
|
||||
* @return
|
||||
*/
|
||||
public Long getTenantId() {
|
||||
return THREAD_LOCAL_TENANT.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否跳过租户过滤的标识
|
||||
* @return
|
||||
*/
|
||||
public Boolean getTenantSkip() {
|
||||
return THREAD_LOCAL_TENANT_SKIP_FLAG.get() != null && THREAD_LOCAL_TENANT_SKIP_FLAG.get();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
THREAD_LOCAL_TENANT.remove();
|
||||
THREAD_LOCAL_TENANT_SKIP_FLAG.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/9/13
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class TenantContextHolderFilter extends GenericFilterBean {
|
||||
|
||||
private final static String UNDEFINED_STR = "undefined";
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
String headerTenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String paramTenantId = request.getParameter(CommonConstants.TENANT_ID);
|
||||
|
||||
log.debug("获取header中的租户ID为:{}", headerTenantId);
|
||||
|
||||
if (StrUtil.isNotBlank(headerTenantId) && !StrUtil.equals(UNDEFINED_STR, headerTenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(headerTenantId));
|
||||
}
|
||||
else if (StrUtil.isNotBlank(paramTenantId) && !StrUtil.equals(UNDEFINED_STR, paramTenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(paramTenantId));
|
||||
}
|
||||
else {
|
||||
TenantContextHolder.setTenantId(CommonConstants.TENANT_ID_1);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.pig4cloud.pigx.common.data.tenant;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/29
|
||||
* <p>
|
||||
* 传递 RestTemplate 请求的租户ID
|
||||
*/
|
||||
public class TenantRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
|
||||
if (TenantContextHolder.getTenantId() != null) {
|
||||
request.getHeaders().set(CommonConstants.TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
|
||||
}
|
||||
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
com.pig4cloud.pigx.common.data.cache.RedisTemplateConfiguration
|
||||
com.pig4cloud.pigx.common.data.cache.RedisMessageConfiguration
|
||||
com.pig4cloud.pigx.common.data.cache.RedisCacheManagerConfiguration
|
||||
com.pig4cloud.pigx.common.data.cache.RedisCacheAutoConfiguration
|
||||
com.pig4cloud.pigx.common.data.tenant.PigxTenantConfigProperties
|
||||
com.pig4cloud.pigx.common.data.tenant.TenantContextHolderFilter
|
||||
com.pig4cloud.pigx.common.data.tenant.PigxTenantConfiguration
|
||||
com.pig4cloud.pigx.common.data.mybatis.MybatisPlusConfiguration
|
||||
com.pig4cloud.pigx.common.data.resolver.TenantKeyStrResolver
|
||||
41
pigx-common/pigx-common-datasource/pom.xml
Normal file
41
pigx-common/pigx-common-datasource/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-datasource</artifactId>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>pigx 动态切换数据源</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
</dependency>
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- druid 连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--拦截器依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.datasource;
|
||||
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.creator.druid.DruidDataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.processor.DsHeaderProcessor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsProcessor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsSessionProcessor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
|
||||
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
|
||||
import com.pig4cloud.pigx.common.datasource.config.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jasypt.encryption.StringEncryptor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-02-06
|
||||
* <p>
|
||||
* 动态数据源切换配置
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@Import(DynamicLogConfiguration.class)
|
||||
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(DruidDataSourceProperties.class)
|
||||
public class DynamicDataSourceAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 获取动态数据源提供者
|
||||
* @param defaultDataSourceCreator 默认数据源创建器
|
||||
* @param stringEncryptor 字符串加密器
|
||||
* @param properties 数据源属性
|
||||
* @return 动态数据源提供者
|
||||
*/
|
||||
@Bean
|
||||
public DynamicDataSourceProvider dynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,
|
||||
StringEncryptor stringEncryptor, DruidDataSourceProperties properties) {
|
||||
return new JdbcDynamicDataSourceProvider(defaultDataSourceCreator, stringEncryptor, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认数据源创建器
|
||||
* @param druidDataSourceCreator Druid数据源创建器
|
||||
* @return 默认数据源创建器
|
||||
*/
|
||||
@Bean
|
||||
public DefaultDataSourceCreator defaultDataSourceCreator(DruidDataSourceCreator druidDataSourceCreator) {
|
||||
DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();
|
||||
List<DataSourceCreator> creators = new ArrayList<>();
|
||||
creators.add(druidDataSourceCreator);
|
||||
defaultDataSourceCreator.setCreators(creators);
|
||||
return defaultDataSourceCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源处理器
|
||||
* @return 数据源处理器
|
||||
*/
|
||||
@Bean
|
||||
public DsProcessor dsProcessor(BeanFactory beanFactory) {
|
||||
DsProcessor lastParamDsProcessor = new LastParamDsProcessor();
|
||||
DsProcessor headerProcessor = new DsHeaderProcessor();
|
||||
DsProcessor sessionProcessor = new DsSessionProcessor();
|
||||
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
|
||||
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
lastParamDsProcessor.setNextProcessor(headerProcessor);
|
||||
headerProcessor.setNextProcessor(sessionProcessor);
|
||||
sessionProcessor.setNextProcessor(spelExpressionProcessor);
|
||||
return lastParamDsProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取清除TTL数据源过滤器
|
||||
* @return 清除TTL数据源过滤器
|
||||
*/
|
||||
@Bean
|
||||
public ClearTtlDataSourceFilter clearTtlDsFilter() {
|
||||
return new ClearTtlDataSourceFilter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.pig4cloud.pigx.common.datasource.annotation;
|
||||
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.pig4cloud.pigx.common.datasource.DynamicDataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Lucky
|
||||
* @date 2019-05-18
|
||||
* <p>
|
||||
* 开启动态数据源
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@EnableAutoConfiguration(exclude = { DruidDataSourceAutoConfigure.class })
|
||||
@Import(DynamicDataSourceAutoConfiguration.class)
|
||||
public @interface EnableDynamicDataSource {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.pig4cloud.pigx.common.datasource.config;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/12/11
|
||||
* <p>
|
||||
* 清空上文的DS 设置避免污染当前线程
|
||||
*/
|
||||
public class ClearTtlDataSourceFilter extends GenericFilterBean implements Ordered {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.pig4cloud.pigx.common.datasource.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-05-14
|
||||
* <p>
|
||||
* 参考DruidDataSourceWrapper
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("spring.datasource.druid")
|
||||
public class DruidDataSourceProperties {
|
||||
|
||||
/**
|
||||
* 数据源用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据源密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* jdbcurl
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 数据源驱动
|
||||
*/
|
||||
private String driverClassName;
|
||||
|
||||
/**
|
||||
* 查询数据源的SQL
|
||||
*/
|
||||
private String queryDsSql = "select * from gen_datasource_conf where del_flag = '0'";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.pig4cloud.pigx.common.datasource.config;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.factory.YamlPropertySourceFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2022/8/8
|
||||
*
|
||||
* 注入SQL 格式化的插件
|
||||
*/
|
||||
@ConditionalOnClass(name = "com.pig4cloud.pigx.common.data.mybatis.DruidSqlLogFilter")
|
||||
@PropertySource(value = "classpath:dynamic-ds-log.yaml", factory = YamlPropertySourceFactory.class)
|
||||
public class DynamicLogConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.datasource.config;
|
||||
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.creator.druid.DruidConfig;
|
||||
import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
|
||||
import com.pig4cloud.pigx.common.datasource.support.DataSourceConstants;
|
||||
import com.pig4cloud.pigx.common.datasource.util.DsConfTypeEnum;
|
||||
import com.pig4cloud.pigx.common.datasource.util.DsJdbcUrlEnum;
|
||||
import org.jasypt.encryption.StringEncryptor;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/2/6
|
||||
* <p>
|
||||
* 从数据源中获取 配置信息
|
||||
*/
|
||||
public class JdbcDynamicDataSourceProvider extends AbstractJdbcDataSourceProvider {
|
||||
|
||||
private final DruidDataSourceProperties properties;
|
||||
|
||||
private final StringEncryptor stringEncryptor;
|
||||
|
||||
public JdbcDynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,
|
||||
StringEncryptor stringEncryptor, DruidDataSourceProperties properties) {
|
||||
super(defaultDataSourceCreator, properties.getDriverClassName(), properties.getUrl(), properties.getUsername(),
|
||||
properties.getPassword());
|
||||
this.stringEncryptor = stringEncryptor;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行语句获得数据源参数
|
||||
* @param statement 语句
|
||||
* @return 数据源参数
|
||||
* @throws SQLException sql异常
|
||||
*/
|
||||
@Override
|
||||
protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
|
||||
ResultSet rs = statement.executeQuery(properties.getQueryDsSql());
|
||||
|
||||
Map<String, DataSourceProperty> map = new HashMap<>(8);
|
||||
while (rs.next()) {
|
||||
String name = rs.getString(DataSourceConstants.NAME);
|
||||
String username = rs.getString(DataSourceConstants.DS_USER_NAME);
|
||||
String password = rs.getString(DataSourceConstants.DS_USER_PWD);
|
||||
Integer confType = rs.getInt(DataSourceConstants.DS_CONFIG_TYPE);
|
||||
String dsType = rs.getString(DataSourceConstants.DS_TYPE);
|
||||
|
||||
DataSourceProperty property = new DataSourceProperty();
|
||||
property.setUsername(username);
|
||||
property.setPassword(stringEncryptor.decrypt(password));
|
||||
|
||||
String url;
|
||||
// JDBC 配置形式
|
||||
DsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(dsType);
|
||||
if (DsConfTypeEnum.JDBC.getType().equals(confType)) {
|
||||
url = rs.getString(DataSourceConstants.DS_JDBC_URL);
|
||||
}
|
||||
else {
|
||||
String host = rs.getString(DataSourceConstants.DS_HOST);
|
||||
String port = rs.getString(DataSourceConstants.DS_PORT);
|
||||
String dsName = rs.getString(DataSourceConstants.DS_NAME);
|
||||
url = String.format(urlEnum.getUrl(), host, port, dsName);
|
||||
}
|
||||
|
||||
// Druid Config
|
||||
DruidConfig druidConfig = new DruidConfig();
|
||||
druidConfig.setValidationQuery(urlEnum.getValidationQuery());
|
||||
property.setDruid(druidConfig);
|
||||
property.setUrl(url);
|
||||
|
||||
map.put(name, property);
|
||||
}
|
||||
|
||||
// 添加默认主数据源
|
||||
DataSourceProperty property = new DataSourceProperty();
|
||||
property.setUsername(properties.getUsername());
|
||||
property.setPassword(properties.getPassword());
|
||||
property.setUrl(properties.getUrl());
|
||||
map.put(DataSourceConstants.DS_MASTER, property);
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.datasource.config;
|
||||
|
||||
import com.baomidou.dynamic.datasource.processor.DsProcessor;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/2/6
|
||||
* <p>
|
||||
* 参数数据源解析 @DS("#last)
|
||||
*/
|
||||
public class LastParamDsProcessor extends DsProcessor {
|
||||
|
||||
private static final String LAST_PREFIX = "#last";
|
||||
|
||||
/**
|
||||
* 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器
|
||||
* @param key DS注解里的内容
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(String key) {
|
||||
if (key.startsWith(LAST_PREFIX)) {
|
||||
// https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/213
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽象最终决定数据源
|
||||
* @param invocation 方法执行信息
|
||||
* @param key DS注解里的内容
|
||||
* @return 数据源名称
|
||||
*/
|
||||
@Override
|
||||
public String doDetermineDatasource(MethodInvocation invocation, String key) {
|
||||
Object[] arguments = invocation.getArguments();
|
||||
return String.valueOf(arguments[arguments.length - 1]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.pig4cloud.pigx.common.datasource.support;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-04-01
|
||||
* <p>
|
||||
* 数据源相关常量
|
||||
*/
|
||||
public interface DataSourceConstants {
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
*/
|
||||
String NAME = "name";
|
||||
|
||||
/**
|
||||
* 默认数据源(master)
|
||||
*/
|
||||
String DS_MASTER = "master";
|
||||
|
||||
/**
|
||||
* jdbcurl
|
||||
*/
|
||||
String DS_JDBC_URL = "url";
|
||||
|
||||
/**
|
||||
* 配置类型
|
||||
*/
|
||||
String DS_CONFIG_TYPE = "conf_type";
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
String DS_USER_NAME = "username";
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
String DS_USER_PWD = "password";
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
*/
|
||||
String DS_TYPE = "ds_type";
|
||||
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
String DS_NAME = "ds_name";
|
||||
|
||||
/**
|
||||
* 主机类型
|
||||
*/
|
||||
String DS_HOST = "host";
|
||||
|
||||
/**
|
||||
* 端口
|
||||
*/
|
||||
String DS_PORT = "port";
|
||||
|
||||
/**
|
||||
* 实例名称
|
||||
*/
|
||||
String DS_INSTANCE = "instance";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.pig4cloud.pigx.common.datasource.util;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/12/11
|
||||
* <p>
|
||||
* 数据源配置类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DsConfTypeEnum {
|
||||
|
||||
/**
|
||||
* 主机链接
|
||||
*/
|
||||
HOST(0, "主机链接"),
|
||||
|
||||
/**
|
||||
* JDBC链接
|
||||
*/
|
||||
JDBC(1, "JDBC链接");
|
||||
|
||||
private final Integer type;
|
||||
|
||||
private final String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.pig4cloud.pigx.common.datasource.util;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/12/11
|
||||
* <p>
|
||||
* jdbc-url
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DsJdbcUrlEnum {
|
||||
|
||||
/**
|
||||
* mysql 数据库
|
||||
*/
|
||||
MYSQL("mysql",
|
||||
"jdbc:mysql://%s:%s/%s?characterEncoding=utf8"
|
||||
+ "&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true"
|
||||
+ "&useLegacyDatetimeCode=false&allowMultiQueries=true&allowPublicKeyRetrieval=true",
|
||||
"select 1", "mysql8 链接"),
|
||||
|
||||
/**
|
||||
* pg 数据库
|
||||
*/
|
||||
PG("pg", "jdbc:postgresql://%s:%s/%s", "select 1", "postgresql 链接"),
|
||||
|
||||
/**
|
||||
* SQL SERVER
|
||||
*/
|
||||
MSSQL("mssql", "jdbc:sqlserver://%s:%s;database=%s;characterEncoding=UTF-8", "select 1", "sqlserver 链接"),
|
||||
|
||||
/**
|
||||
* oracle
|
||||
*/
|
||||
ORACLE("oracle", "jdbc:oracle:thin:@%s:%s:%s", "select 1 from dual", "oracle 链接"),
|
||||
|
||||
/**
|
||||
* db2
|
||||
*/
|
||||
DB2("db2", "jdbc:db2://%s:%s/%s", "select 1 from sysibm.sysdummy1", "DB2 TYPE4 连接"),
|
||||
|
||||
/**
|
||||
* 达梦
|
||||
*/
|
||||
DM("dm", "jdbc:dm://%s:%s/%s", "select 1 from dual", "达梦连接"),
|
||||
|
||||
/**
|
||||
* pg 数据库
|
||||
*/
|
||||
HIGHGO("highgo", "jdbc:highgo://%s:%s/%s", "select 1", "highgo 链接");
|
||||
|
||||
private final String dbName;
|
||||
|
||||
private final String url;
|
||||
|
||||
private final String validationQuery;
|
||||
|
||||
private final String description;
|
||||
|
||||
public static DsJdbcUrlEnum get(String dsType) {
|
||||
return Arrays.stream(DsJdbcUrlEnum.values()).filter(dsJdbcUrlEnum -> dsType.equals(dsJdbcUrlEnum.getDbName()))
|
||||
.findFirst().get();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user