feat: initial iShare project code

This commit is contained in:
purovps
2026-02-16 23:20:59 +08:00
parent 8c83a6fd46
commit 6f270a972e
1910 changed files with 218015 additions and 0 deletions

View 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>

View File

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

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
com.pig4cloud.pigx.common.audit.AuditAutoConfiguration

View 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>

View 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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

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

View File

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

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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 "*";
}

View File

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

View File

@@ -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
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -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 {
}
}

View File

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

View File

@@ -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

View File

@@ -0,0 +1,16 @@
${AnsiColor.BRIGHT_YELLOW}
::::::::: ::::::::::: :::::::: ::: :::
:+: :+: :+: :+: :+: :+: :+:
+:+ +:+ +:+ +:+ +:+ +:+
+#++:++#+ +#+ :#: +#++:+
+#+ +#+ +#+ +#+# +#+ +#+
#+# #+# #+# #+# #+# #+#
### ########### ######## ### ###
www.pig4cloud.com
Pig Microservice Architecture
${AnsiColor.DEFAULT}

View File

@@ -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

View 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>

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

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

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View 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>

View File

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

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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'";
}

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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]);
}
}

View File

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

View File

@@ -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;
}

View File

@@ -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