feat:V3.1.1整合remi-comm到remi-cloud中

This commit is contained in:
dianjiu
2024-09-11 17:41:17 +08:00
parent e111c59a69
commit 28d62f6cf4
245 changed files with 20975 additions and 154 deletions

View File

@@ -1,12 +1,12 @@
---------------------------------------------------------------- ----------------------------------------------------------------
Sun Sep 01 21:38:15 CST 2024: Wed Sep 11 17:08:24 CST 2024:
Booting Derby version The Apache Software Foundation - Apache Derby - 10.14.2.0 - (1828579): instance a816c00e-0191-adce-b9d3-000003b04a40 Booting Derby version The Apache Software Foundation - Apache Derby - 10.14.2.0 - (1828579): instance a816c00e-0191-e057-45b8-000002512970
on database directory memory:D:\Codes\remi\remi-cloud\4f82a0bb-9d60-4d6f-b6a3-e6480d5e589c with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@60e53b93 on database directory memory:D:\Codes\remi-top\remi-cloud\1ec76c96-9207-4b71-b497-ffec1d63b532 with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@5e481248
Loaded from file:/D:/Maven/sdt-repository/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar Loaded from file:/D:/Maven/remi-top-repo/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar
java.vendor=Amazon.com Inc. java.vendor=Amazon.com Inc.
java.runtime.version=17.0.11+9-LTS java.runtime.version=17.0.12+7-LTS
user.dir=D:\Codes\remi\remi-cloud user.dir=D:\Codes\remi-top\remi-cloud
os.name=Windows 11 os.name=Windows 10
os.arch=amd64 os.arch=amd64
os.version=10.0 os.version=10.0
derby.system.home=null derby.system.home=null

155
pom.xml
View File

@@ -4,9 +4,8 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>ai.remi.top.cloud</groupId> <groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-cloud</artifactId> <artifactId>remi-cloud</artifactId>
<version>3.1.0</version> <version>3.1.1</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<url>https://github.com/remi/remi-cloud</url>
<name>remi-cloud</name> <name>remi-cloud</name>
<description>Remi Cloud</description> <description>Remi Cloud</description>
@@ -19,7 +18,7 @@
<properties> <properties>
<!--remi--> <!--remi-->
<remi.version>3.1.0</remi.version> <remi.version>3.1.1</remi.version>
<!--jdk--> <!--jdk-->
<java.version>17</java.version> <java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -31,71 +30,59 @@
<spring-boot.version>2.7.18</spring-boot.version> <spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.9</spring-cloud.version> <spring-cloud.version>2021.0.9</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.6.0</spring-cloud-alibaba.version> <spring-cloud-alibaba.version>2021.0.6.0</spring-cloud-alibaba.version>
<!--1.15--> <!--commons-->
<commons-codec.version>1.17.0</commons-codec.version> <commons-text.version>1.12.0</commons-text.version>
<!--3.2.2-->
<commons-collections.version>3.2.2</commons-collections.version>
<!--1.10.0-->
<commons-text.version>1.10.0</commons-text.version>
<!--2.11.0-->
<commons-io.version>2.16.1</commons-io.version> <commons-io.version>2.16.1</commons-io.version>
<!--3.12.0--> <commons-lang3.version>3.17.0</commons-lang3.version>
<commons-lang3.version>3.14.0</commons-lang3.version> <commons-codec.version>1.17.1</commons-codec.version>
<!--1.0.4--> <commons-logging.version>1.3.4</commons-logging.version>
<commons-logging.version>1.3.1</commons-logging.version> <commons-collections.version>3.2.2</commons-collections.version>
<!--3.8.0--> <commons-net.version>3.11.1</commons-net.version>
<commons-net.version>3.10.0</commons-net.version>
<!--1.7-->
<commons-validator.version>1.9.0</commons-validator.version>
<!--1.4-->
<commons-fileupload.version>1.5</commons-fileupload.version> <commons-fileupload.version>1.5</commons-fileupload.version>
<!--1.0.10--> <!--hutool-->
<device.version>1.0.10</device.version>
<!--2.1.3-->
<dom4j.version>2.1.3</dom4j.version>
<!--1.2.9-->
<druid.version>1.2.9</druid.version>
<!--3.1.1-->
<easyexcel.version>3.1.1</easyexcel.version>
<!--6.2.5.Final-->
<hibernate-validator.version>6.2.5.Final</hibernate-validator.version>
<!--1.21-->
<bitwalker.version>1.21</bitwalker.version>
<!--2.7.0-->
<ip2region.version>2.7.0</ip2region.version>
<!--5.8.4-->
<hutool.version>5.8.4</hutool.version> <hutool.version>5.8.4</hutool.version>
<!--31.1-jre--> <!--storage-->
<guava.version>31.1-jre</guava.version> <tika.version>2.7.0</tika.version>
<!--2.9.0--> <qiniu.version>7.13.1</qiniu.version>
<aliyun-sdk-oss.version>3.10.2</aliyun-sdk-oss.version>
<minio.version>8.5.12</minio.version>
<awssdk.version>2.26.3</awssdk.version>
<cos_api.version>5.6.213</cos_api.version>
<!--easyexcel-->
<easyexcel.version>4.0.2</easyexcel.version>
<!--xxl-job-->
<xxl-job.version>2.4.1</xxl-job.version>
<!--user-agent-->
<device.version>1.0.10</device.version>
<bitwalker.version>1.21</bitwalker.version>
<!--xml-dom-->
<dom4j.version>2.1.3</dom4j.version>
<!--validator-->
<commons-validator.version>1.9.0</commons-validator.version>
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
<!--ip2region-->
<ip2region.version>2.7.0</ip2region.version>
<!--json-->
<gson.version>2.9.0</gson.version> <gson.version>2.9.0</gson.version>
<!--2.0.6-->
<fastjson.version>2.0.6</fastjson.version> <fastjson.version>2.0.6</fastjson.version>
<!--2.13.3-->
<jackson.version>2.13.3</jackson.version> <jackson.version>2.13.3</jackson.version>
<!--4.0.0--> <!--java-extend-->
<java-jwt.version>0.12.6</java-jwt.version> <guava.version>33.3.0-jre</guava.version>
<!--4.0.1-->
<javax.version>4.0.1</javax.version> <javax.version>4.0.1</javax.version>
<!--2.3.1-->
<jaxb-api.version>2.3.1</jaxb-api.version> <jaxb-api.version>2.3.1</jaxb-api.version>
<!--3.3.0-->
<cglib.version>3.3.0</cglib.version> <cglib.version>3.3.0</cglib.version>
<!--4.5.0--> <!--knife4j-->
<knife4j.version>4.5.0</knife4j.version> <knife4j.version>4.5.0</knife4j.version>
<!--2.6.0-->
<springdoc.version>2.6.0</springdoc.version> <springdoc.version>2.6.0</springdoc.version>
<!--1.18.34--> <!--mapstruct-->
<lombok.version>1.18.34</lombok.version> <lombok.version>1.18.34</lombok.version>
<!--1.5.2.Final-->
<mapstruct.version>1.5.2.Final</mapstruct.version> <mapstruct.version>1.5.2.Final</mapstruct.version>
<!--0.2.0-->
<lombok-mapstruct.version>0.2.0</lombok-mapstruct.version> <lombok-mapstruct.version>0.2.0</lombok-mapstruct.version>
<!--3.5.2--> <!--jdbc-->
<mybatis-plus.version>3.5.2</mybatis-plus.version> <druid.version>1.2.9</druid.version>
<!--2.2.2-->
<mybatis.version>2.2.2</mybatis.version> <mybatis.version>2.2.2</mybatis.version>
<!--rdb-sql--> <mybatis-plus.version>3.5.2</mybatis-plus.version>
<!--jdbc rom-->
<mysql.version>8.0.33</mysql.version> <mysql.version>8.0.33</mysql.version>
<ojdbc.version>19.7.0.0</ojdbc.version> <ojdbc.version>19.7.0.0</ojdbc.version>
<mssql-jdbc.version>7.4.1.jre8</mssql-jdbc.version> <mssql-jdbc.version>7.4.1.jre8</mssql-jdbc.version>
@@ -103,26 +90,20 @@
<postgresql.version>42.6.0</postgresql.version> <postgresql.version>42.6.0</postgresql.version>
<h2.db.version>2.2.224</h2.db.version> <h2.db.version>2.2.224</h2.db.version>
<mongodb-driver-sync.version>4.10.2</mongodb-driver-sync.version> <mongodb-driver-sync.version>4.10.2</mongodb-driver-sync.version>
<!--4.5.13--> <!--http-->
<httpclient.version>4.5.13</httpclient.version> <httpclient.version>4.5.13</httpclient.version>
<!--8.4.6 4.10.0-->
<minio.version>8.5.12</minio.version>
<!--4.9.3-->
<okhttp3.version>4.12.0</okhttp3.version> <okhttp3.version>4.12.0</okhttp3.version>
<!--velocity--> <!--velocity-->
<velocity.version>1.7</velocity.version> <velocity.version>1.7</velocity.version>
<!--2.4.1--> <!--logger-->
<xxl-job.version>2.4.1</xxl-job.version>
<!--8.13.0-->
<trace.version>8.13.0</trace.version> <trace.version>8.13.0</trace.version>
<!--1.7.36-->
<slf4j.version>1.7.32</slf4j.version> <slf4j.version>1.7.32</slf4j.version>
<!--logback-->
<logback.version>1.3.14</logback.version> <logback.version>1.3.14</logback.version>
</properties> </properties>
<modules> <modules>
<!--基础服务--> <!--基础服务-->
<module>remi-comm</module>
<module>remi-nacos</module> <module>remi-nacos</module>
<module>remi-sys</module> <module>remi-sys</module>
<module>remi-demo</module> <module>remi-demo</module>
@@ -160,82 +141,82 @@
<!--内部服务依赖开始--> <!--内部服务依赖开始-->
<!--remi-comm--> <!--remi-comm-->
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId> <artifactId>remi-comm</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId> <artifactId>remi-comm-core</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-minio</artifactId> <artifactId>remi-comm-minio</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-redis</artifactId> <artifactId>remi-comm-redis</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-jdbc</artifactId> <artifactId>remi-comm-jdbc</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-domain</artifactId> <artifactId>remi-comm-domain</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-exception</artifactId> <artifactId>remi-comm-exception</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-feign</artifactId> <artifactId>remi-comm-feign</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-job</artifactId> <artifactId>remi-comm-job</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-log</artifactId> <artifactId>remi-comm-log</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-swagger</artifactId> <artifactId>remi-comm-swagger</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-thread</artifactId> <artifactId>remi-comm-thread</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId> <artifactId>remi-comm-util</artifactId>
<groupId>ai.remi.top.comm</groupId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.comm</groupId> <groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-auth</artifactId> <artifactId>remi-comm-auth</artifactId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.comm</groupId> <groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-safe</artifactId> <artifactId>remi-comm-safe</artifactId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.remi.top.comm</groupId> <groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-web</artifactId> <artifactId>remi-comm-web</artifactId>
<version>${remi.version}</version> <version>${remi.version}</version>
</dependency> </dependency>
@@ -396,12 +377,12 @@
<groupId>commons-fileupload</groupId> <groupId>commons-fileupload</groupId>
<version>${commons-fileupload.version}</version> <version>${commons-fileupload.version}</version>
</dependency> </dependency>
<!-- validator -->
<dependency> <dependency>
<artifactId>commons-validator</artifactId> <artifactId>commons-validator</artifactId>
<groupId>commons-validator</groupId> <groupId>commons-validator</groupId>
<version>${commons-validator.version}</version> <version>${commons-validator.version}</version>
</dependency> </dependency>
<!-- hibernate-validator -->
<dependency> <dependency>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
<groupId>org.hibernate.validator</groupId> <groupId>org.hibernate.validator</groupId>
@@ -565,22 +546,6 @@
<artifactId>velocity</artifactId> <artifactId>velocity</artifactId>
<version>${velocity.version}</version> <version>${velocity.version}</version>
</dependency> </dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<!-- jaxb-api --> <!-- jaxb-api -->
<dependency> <dependency>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>

60
remi-comm/README.md Normal file
View File

@@ -0,0 +1,60 @@
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Remi Comm</h1>
## 平台简介
Remi Comm 即 Remi Dev Tools 启迪快速开发平台所依赖的公共依赖模块。
## 项目文档
https://remi.yuque.com/r/organizations/homepage
## 体验地址
https://remi.run
RemiAdmin/123456
## 项目源码
### Gitee
https://gitee.com/remi-top
### GitTok
https://gittok.com/remi-top
### Gitlab
https://gitlab.com/remi-top
### Github
https://github.com/remi-top
## 系统模块
~~~
remi-comm
├── remi-comm-auth // 权限控制
├── remi-comm-core // 核心模块
├── remi-comm-domain // 领域模型
├── remi-comm-jdbc // 数据配置
├── remi-comm-exception // 异常模块
├── remi-comm-feign // 通信模块
├── remi-comm-job // 任务模块
├── remi-comm-minio // MinIo 文件存储
├── remi-comm-activemq // ActiveMQ
├── remi-comm-rabbitmq // RabbitMQ
├── remi-comm-rocketmq // RocketMQ
├── remi-comm-redis // 缓存服务
├── remi-comm-util // 工具类
├── remi-comm-thread // 线程池
├── remi-comm-swagger // 系统接口
├── remi-comm-netty // Netty
├── remi-comm-safe // 安全控制
├── remi-comm-web // Web
├── remi-comm-webservice // WebService
├── remi-comm-websocket // WebSocket
├── pom.xml // 公共依赖
~~~

56
remi-comm/pom.xml Normal file
View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>remi-comm</artifactId>
<name>remi-comm</name>
<packaging>pom</packaging>
<description>瑞米平台-公共依赖</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-cloud</artifactId>
<version>3.1.1</version>
</parent>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--子模块管理-->
<modules>
<module>remi-comm-core</module>
<module>remi-comm-util</module>
<module>remi-comm-domain</module>
<module>remi-comm-jdbc</module>
<module>remi-comm-thread</module>
<module>remi-comm-job</module>
<module>remi-comm-log</module>
<module>remi-comm-minio</module>
<module>remi-comm-redis</module>
<module>remi-comm-feign</module>
<module>remi-comm-email</module>
<module>remi-comm-exception</module>
<module>remi-comm-auth</module>
<module>remi-comm-safe</module>
<module>remi-comm-swagger</module>
<module>remi-comm-web</module>
</modules>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project>

View File

@@ -0,0 +1,59 @@
<?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>
<artifactId>remi-comm-auth</artifactId>
<name>remi-comm-auth</name>
<description>remi-comm-auth</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--公共依赖-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-redis</artifactId>
</dependency>
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-exception</artifactId>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,34 @@
package ai.remi.comm.auth.annotation;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc 权限校验
*/
@Inherited
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface RbacPermission {
/**
* 角色
*
* @return
*/
String roles() default "";
/**
* 权限
*
* @return
*/
String permissions() default "";
}

View File

@@ -0,0 +1,224 @@
package ai.remi.comm.auth.aspect;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import ai.remi.comm.auth.annotation.RbacPermission;
import ai.remi.comm.core.constant.Constants;
import ai.remi.comm.core.constant.HeaderConstant;
import ai.remi.comm.exception.custom.BusinessException;
import ai.remi.comm.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc
*/
@Aspect
@Component
@Order(10)
@Slf4j
public class RbacPermissionAspect {
private static final Logger logger = LoggerFactory.getLogger(RbacPermissionAspect.class);
private final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();
private final String ROLE_MENU = "tdcd-cloud-mps:role-menu:{}";
@Resource
private RedisService redisService;
@Pointcut("@annotation(ai.remi.comm.auth.annotation.RbacPermission)")
public void rbacPermissionPointCut() {
}
/**
* 环绕通知
* 方式一:传入注解,获取注解中的参数
*
* @Around("logRecordPointCut()&&@annotation(logRecord)") public void doAround(ProceedingJoinPoint joinPoint, LogRecord logRecord) {}
* 方式二:通过切点获取注解实体
*/
@Around("rbacPermissionPointCut()")
//@Around("rbacPermissionPointCut()&&@annotation(RbacPermission)")
//public void doAround(ProceedingJoinPoint joinPoint, RbacPermission rbacPermission) {
public Object doAround(ProceedingJoinPoint joinPoint) {
// 接收到请求
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 记录请求内容,threadInfo存储所有内容
Map<String, Object> threadInfo = new HashMap<>();
//该方法用于获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等信息,但不包括后面的查询参数部分。
// 注意,getRequestRUL()方法返回的时StringBuffer类型,而不是String类型。
threadInfo.put("url", request.getRequestURL());
//该方法用于获取请求行中资源名称部分,即位于URL的主机和端口之后、参数部分之前的部分
threadInfo.put("uri", request.getRequestURI());
//该方法用于获取请求的协议名,例如http、https或ftp
threadInfo.put("scheme", request.getScheme());
//该方法用于获取HTTP请求消息中的请求方式(如GET、POST等)
threadInfo.put("method", request.getMethod());
logger.info("请求方式:{}", threadInfo.get("method"));
//该方法用于获取请求客户端的IP地址,其格式类似于"192.168.0.1"
threadInfo.put("ip", request.getRemoteAddr());
logger.info("请求ip:{}", threadInfo.get("ip"));
//该方法用于获取Content-Type头字段的值,结果为String类型
threadInfo.put("contentType", request.getContentType());
//包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。
threadInfo.put("userAgent", request.getHeader("User-Agent"));
//threadInfo.put("businessNo", request.getHeader("businessNo"));
threadLocal.set(threadInfo);
//获取方法签名
MethodSignature signature = getSignature(joinPoint);
//获取注解对象
RbacPermission rbacPermission = getAnnotation(joinPoint, RbacPermission.class);
//获取注解方法
Method method = getMethod(joinPoint);
// 获取连接点所在类名
String classname = joinPoint.getTarget().getClass().getSimpleName();
// 获取连接点所在方法名称
String methodName = method.getName();
String functionName = String.format("【%s.%s()】", classname, methodName);
threadInfo.put("functionName", functionName);
logger.info("请求类方法:{}", threadInfo.get("functionName"));
// 下面两个数组中,参数值和参数名的个数和位置是一一对应的。
// 参数名
String[] argNames = signature.getParameterNames();
// 参数值
Object[] args = joinPoint.getArgs();
StringBuilder functionArgs = new StringBuilder();
//不为空时便利组装
if (Objects.nonNull(argNames) && argNames.length > 0 && args.length > 0) {
for (int i = 0; i < args.length; i++) {
functionArgs.append(argNames[i] + ":" + args[i] + ";");
}
}
threadInfo.put("functionArgs", functionArgs.toString());
logger.info("请求参数:{}", threadInfo.get("functionArgs"));
// threadInfo.put("accessToken", request.getHeader("accessToken"));
threadInfo.put("accessToken", request.getHeader(HeaderConstant.X_ACCESS_TOKEN));
logger.info("鉴权令牌:{}", threadInfo.get("accessToken"));
// 权限校验
if (method.isAnnotationPresent(RbacPermission.class)) {
String roles = rbacPermission.roles();
String permissions = rbacPermission.permissions();
// 根据鉴权令牌去redis查询到用户信息、角色信息、权限信息
//Map map = redisService.hmget(RedisDemoEnum.ACCESS_TOKEN.getKey(String.valueOf(threadInfo.get("accessToken"))));
Map map = redisService.hmget(String.valueOf(threadInfo.get("accessToken")));
if (null == map || map.size() == 0) {
throw new BusinessException(Constants.LOGIN_NO_PERMISSION, "accessToken expire! please login again!");
}
String roleCode = String.valueOf(map.get("roleCode"));
List<String> roleCodes = Arrays.asList(roleCode.split(","));
// 校验角色
if (StringUtils.isNotBlank(roles)) {
// 校验用户角色
// JSONObject jsonObject = JSONObject.parseObject(String.valueOf(map.get("user")));
// if (!Arrays.asList(roles.split(",")).contains(jsonObject.get("roleCode"))) {
// throw new BusinessException(Constants.CODE_403, "当前角色权限不足");
// }
for (String authRoleCode : Arrays.asList(roles.split(","))) {
if (!roleCodes.contains(authRoleCode)){
throw new BusinessException(Constants.CODE_403, "the role no Permission!");
}else {
break;
}
}
// if (!Arrays.asList(roles.split(",")).contains(roleCode)) {
// throw new BusinessException(Constants.CODE_403, "the role no Permission!");
// }
}
// 校验权限
if (StringUtils.isNotBlank(permissions)) {
// 组装用户权限
StringBuilder userPermissions = new StringBuilder();
// String menuString = JSONObject.toJSONString(map.get("menus"));
// List<JSONObject> menus = JSONObject.parseArray(menuString);
List<JSONObject> menus = new ArrayList<>();
for (String code : roleCodes) {
String data = null;
try {
data = redisService.getString(StrUtil.format(ROLE_MENU, code));
} catch (Exception e) {
log.error("获取用户权限异常:{}", e.getMessage());
throw new BusinessException(Constants.CODE_403, "get user Permission error!");
}
List<JSONObject> menuList = JSONObject.parseArray(data, JSONObject.class);
menus.addAll(menuList);
}
for (int i = 0; i < menus.size(); i++) {
if ("2".equals(menus.get(i).get("menuType"))) {
userPermissions.append(menus.get(i).get("menuPermission"));
if (i != menus.size() - 1) {
userPermissions.append(",");
}
}
}
// 校验用户权限
if (!Arrays.asList(userPermissions.toString().split(",")).containsAll(Arrays.asList(permissions.split(",")))) {
throw new BusinessException(Constants.CODE_403, "the user no Permission");
}
}
}
//执行到这里开始走进来的方法体(必须声明)
Object returnValue;
try {
returnValue = joinPoint.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new BusinessException(((BusinessException) throwable).getCode(), (throwable).getMessage());
}
return returnValue;
}
private MethodSignature getSignature(JoinPoint joinPoint) {
//通过连接点获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature;
}
private Method getMethod(JoinPoint joinPoint) {
//通过连接点获取方法签名
MethodSignature signature = getSignature(joinPoint);
//获取注解服务的方法
Method method = signature.getMethod();
return method;
}
private <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
Method method = getMethod(joinPoint);
//获取注解类
T annotation = method.getAnnotation(annotationClass);
return annotation;
}
}

View File

@@ -0,0 +1,83 @@
## 方式1: 实现ApplicationContextInitializer接口
实现接口后,将此类注入到Spring容器中,有两种方式。当然也可以直接调用静态方法
第一种: 在此类上加`@Component`注解
第二种:在 `resources/META-INF/spring.factories`文件中添加以下配置: org.springframework.context.ApplicationContextInitializer=CustomerApplicationContextInitializer的路径
实现ApplicationContextInitializer的源码示例:
```java
public class CustomApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
SpringBeanUtils.setApplicationContext(applicationContext);
}
}
```
## 方式2. 实现ApplicationListener接口
实现接口后,将此类注入到Spring容器中,有两种方式。当然也可以直接调用静态方法
第一种: 在此类上加`@Component`注解
第二种:在 `resources/META-INF/spring.factories`文件中添加以下配置: org.springframework.context.ApplicationContextInitializer=CustomerApplicationListener的路径
实现ApplicationListener的源码示例:
```java
public class CustomApplicationListener implements ApplicationListener<ApplicationContextEvent> {
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
SpringBeanUtils.setApplicationContext(event.getApplicationContext());
}
}
```
方式3. 在启动类main方法中设置
```java
@SpringBootApplication
@EnableSwagger2
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(WangMikeSpringApplication.class, args);
SpringBeanUtils.setApplicationContext(applicationContext);
}
}
```
方式4. 实现ApplicationContextAware接口
```java
@Component
public class SpringBeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext){
SpringBeanUtils.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(
return applicationContext;
}
}
```
方式5. 直接通过@Resource注解注入
```java
@Component
@Order(value = 1)
@Slf4j
public class AppStartRunner implements org.springframework.boot.ApplicationRunner {
protected static Logger logger = LoggerFactory.getLogger(AppStartRunner.class);
@Resource
ApplicationContext applicationContext;
}
```

View File

@@ -0,0 +1,55 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>remi-comm-core</artifactId>
<name>remi-comm-core</name>
<description>remi-comm-core</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</dependency>
<!-- 解析user-agent的包 -->
<dependency>
<artifactId>device-detector</artifactId>
<groupId>io.github.mngsk</groupId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,44 @@
package ai.remi.comm.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc AuthFilterConfig
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "filter.config")
public class AuthFilterConfig {
/**
* 公共忽略的url,网关、公共web的AuthFilter 都会忽略---- 配置在application-dev.yaml
*/
private List<String> commonIgnoreUrl= new ArrayList<>();
/**
* 网关忽略的ur----配置在ebg-shop-gateway-dev.yaml
*/
private List<String> gatewayIgnoreUrl=new ArrayList<>();
/**
*各个服务自定义忽略的url---配置在自己的服务
*/
private List<String> customIgnoreUrl= new ArrayList<>();
/**
* 是否验证权限----配置在idaas服务
*/
private Boolean verifyPermission=true;
/**
* 只验证token不验证 权限的
*/
private List<String> onlyVerifyToken=new ArrayList<>();
}

View File

@@ -0,0 +1,34 @@
package ai.remi.comm.core.constant;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 流程审批常量信息
*/
public class ApprovalStatusConstants {
/**
* 待审批
*/
public static final Integer DSP = 0;
/**
* 审批中
*/
public static final Integer SPZ = 1;
/**
* 审批完成
*/
public static final Integer SPWC = 2;
/**
* 已撤销
*/
public static final Integer YCX = 3;
/**
* 已驳回
*/
public static final Integer YBH = 4;
}

View File

@@ -0,0 +1,171 @@
package ai.remi.comm.core.constant;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 通用常量信息
*/
public class Constants {
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
public static final String GBK = "GBK";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi:";
/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap:";
/**
* LDAPS 远程方法调用
*/
public static final String LOOKUP_LDAPS = "ldaps:";
/**
* http请求
*/
public static final String HTTP = "http://";
/**
* https请求
*/
public static final String HTTPS = "https://";
/**
* 成功标记
*/
public static final String SUCCESS = "000000";
/**
* 失败标记
*/
public static final String ERROR = "111111";
/**
* 登录成功状态
*/
public static final String LOGIN_SUCCESS_STATUS = "0";
/**
* 登录失败状态
*/
public static final String LOGIN_FAIL_STATUS = "1";
/**
* 登录成功
*/
public static final String LOGIN_SUCCESS = "Success";
/**
* 登录失败
*/
public static final String LOGIN_FAIL = "Error";
/**
* 注销
*/
public static final String LOGOUT = "Logout";
/**
* 注册
*/
public static final String REGISTER = "Register";
/**
* 当前记录起始索引
*/
public static final String PAGE_NUM = "pageNum";
/**
* 每页显示记录数
*/
public static final String PAGE_SIZE = "pageSize";
/**
* 排序列
*/
public static final String ORDER_BY_COLUMN = "orderByColumn";
/**
* 排序的方向 "desc" 或者 "asc".
*/
public static final String IS_ASC = "isAsc";
/**
* 验证码 redis key
*/
public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
/**
* 验证码有效期(分钟)
*/
public static final long CAPTCHA_EXPIRATION = 2;
/**
* 参数管理 cache key
*/
public static final String SYS_CONFIG_KEY = "sys_config:";
/**
* 字典管理 cache key
*/
public static final String SYS_DICT_KEY = "sys_dict:";
/**
* 资源映射路径 前缀
*/
public static final String RESOURCE_PREFIX = "/profile";
/**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/
public static final String[] JOB_WHITELIST_STR = {"ai.remi"};
/**
* 定时任务违规的字符
*/
public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "ai.remi.comm.core.utils.file"};
/**
* 系统市公司编号
*/
public static final String SYS_COMPANY_ID = "1667075454562709506";
/**
* 登录无权限
*/
public static final String LOGIN_NO_PERMISSION = "401";
/**
* 无权限访问资源
*/
public static final String CODE_401 = "401";
/**
* 当前角色权限不足
*/
public static final String CODE_403 = "403";
/**
* SRS校验数据错误码
*/
public static final String SRS_DATA_CHECK_ERROR = "A02001";
/**
* 功能检验错误码
*/
public static final String COMM_CHECK_ERROR = "222222";
}

View File

@@ -0,0 +1,24 @@
package ai.remi.comm.core.constant;
import com.google.common.collect.Sets;
import java.util.Set;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 过滤器忽略常量
*/
public class FilterIgnoreConstant {
/**
* 默认全部忽略的url 一般用于跟服务无关的
*/
public static final Set<String> FILTER_COMMON_IGNORE_URL = Sets.newHashSet("/**/swagger**/**", "/**/webjars/**",
"/**/v2/api-docs", "/**/v3/api-docs", "/**/error", "/**/doc.html", "/doc.html", "/**/favicon.ico");
/**
* 全局拦截器忽略的服务
*/
public static final Set<String> AUTH_FILTER_IGNORE_SERVICE_NAME = Sets.newHashSet("remi-ids-web","remi-ams-web","remi-mps-web","remi-sso-web","remi-dfe-web","remi-wfe-web","remi-bie-web","remi-wpe-web","remi-job-web","remi-lce-web");
}

View File

@@ -0,0 +1,81 @@
package ai.remi.comm.core.constant;
import ai.remi.comm.core.enums.IdentityTypeEnum;
import ai.remi.comm.core.enums.PermissionScopeEnum;
import ai.remi.comm.core.enums.ServiceTypeEnum;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 全局请求头常量
*/
public class HeaderConstant {
/**
* 部门租户id
*/
public static final String X_DEPT_TENANT_ID = "X-DEPT-TENANT-ID";
/**
* 公司租户id
*/
public static final String X_COMPANY_TENANT_ID = "X-COMPANY-TENANT-ID";
/**
* 当前登陆人唯一id
*/
public static final String X_UNIQUE_ID = "X-UNIQUE-ID";
/**
* 用户系统语言
*/
public static final String X_USER_LANGUAGE = "X-USER-LANGUAGE";
/**
* 用户设备唯一标识
*/
public static final String X_DISTINCT_ID = "X-DISTINCT-ID";
/**
* 登录token
*/
public static final String X_ACCESS_TOKEN = "X-ACCESS-TOKEN";
/**
* 服务类型
*
* @see ServiceTypeEnum
*/
public static final String X_SERVICE_TYPE = "X-SERVICE-TYPE";
/**
* 身份类型 公司用户 访客用户 瑞米用户
*
* @see IdentityTypeEnum
*/
public static final String X_IDENTITY_TYPE = "X-IDENTITY-TYPE";
/**
* 此请求头为用户请求过来的为集团还是公司权限
*
* @see PermissionScopeEnum
*/
public static final String X_PERMISSION_SCOPE = "X-PERMISSION-SCOPE";
/**
* 当权限类型为集团类型时,此请求头包含的为这个用户管理的所有公司id {@link PermissionScopeEnum}
*/
public static final String X_COMPANY_IDS = "X-COMPANY-IDS";
/**
* 当权限类型为公司类型时,此请求头包含的为这个用户管理的所有部门id {@link PermissionScopeEnum}
*/
public static final String X_DEPT_IDS = "X-DEPT-IDS";
/**
* 请求/响应头报文类型
*/
public static final String CONTENT_TYPE = "Content-Type";
}

View File

@@ -0,0 +1,89 @@
package ai.remi.comm.core.constant;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc HTTP状态码
*/
public class HttpStatus {
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 对象创建成功
*/
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;
/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 资源没有被修改
*/
public static final int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少,格式不匹配)
*/
public static final int BAD_REQUEST = 400;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;
/**
* 不支持的数据,媒体类型
*/
public static final int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
/**
* 接口未实现
*/
public static final int NOT_IMPLEMENTED = 501;
}

View File

@@ -0,0 +1,36 @@
package ai.remi.comm.core.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc OAuth2异常常量
*/
public class OAuth2Constants {
/**
* 认证异常
*/
@Getter
@AllArgsConstructor
public enum Error {
USERNAME_NOT_FOUND("username_not_found", "用户名未找到", ""),
BAD_CREDENTIALS("bad_credentials", "错误凭证", "AbstractUserDetailsAuthenticationProvider.badCredentials : Bad credentials"),
USER_LOCKED("user_locked", "用户被锁", "AbstractUserDetailsAuthenticationProvider.locked : User account is locked"),
USER_DISABLE("user_disable", "用户禁用", "AbstractUserDetailsAuthenticationProvider.disabled : User is disabled"),
USER_EXPIRED("user_expired", "用户过期", "AbstractUserDetailsAuthenticationProvider.expired : User account has expired"),
CREDENTIALS_EXPIRED("credentials_expired", "证书过期", "AbstractUserDetailsAuthenticationProvider.credentialsExpired : User credentials have expired"),
INVALID_SCOPE("invalid_scope", "无效作用域", "AbstractAccessDecisionManager.accessDenied : invalid_scope"),
SCOPE_IS_EMPTY("scope_is_empty", "scope 为空异常", ""),
TOKEN_MISSING("token_missing", "令牌不存在", ""),
UN_KNOW_LOGIN_ERROR("un_know_login_error", "未知的登录异常", ""),
INVALID_BEARER_TOKEN("invalid_bearer_token", "不合法的Token", "");
private final String code;
private final String info;
private final String msg;
}
}

View File

@@ -0,0 +1,35 @@
package ai.remi.comm.core.constant;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc Token的Key通用常量
*/
public class TokenConstants {
/**
* 令牌自定义标识
*/
public static final String AUTHENTICATION = "Authorization";
/**
* 补充令牌自定义标识
*/
public static final String SUPPLY_AUTHORIZATION = "X-Access-Token";
/**
* 令牌前缀
*/
public static final String PREFIX = "Remi";
/**
* 回调地址
*/
public static final String REDIRECT_URL = "redirect_url";
/**
* 令牌秘钥
*/
public final static String SECRET = "ce6e6cc15d0349b58f9d8c43e5f328ce7f329029434242c6aaaec5a06b930a4a4d73cf1a7e7b48eb80fad6a916ded78d614cc2e63779489d9a2aaaafd674c5515e708ba5869a4dcfa3e66799";
}

View File

@@ -0,0 +1,115 @@
package ai.remi.comm.core.constant;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 用户常量信息
*/
public class UserConstants {
/**
* 平台内系统用户的唯一标志
*/
public static final String SYS_USER = "SYS_USER";
/**
* 正常状态
*/
public static final String NORMAL = "0";
/**
* 异常状态
*/
public static final String EXCEPTION = "1";
/**
* 用户封禁状态
*/
public static final String USER_DISABLE = "1";
/**
* 角色封禁状态
*/
public static final String ROLE_DISABLE = "1";
/**
* 部门正常状态
*/
public static final String DEPT_NORMAL = "0";
/**
* 部门停用状态
*/
public static final String DEPT_DISABLE = "1";
/**
* 字典正常状态
*/
public static final String DICT_NORMAL = "0";
/**
* 是否为系统默认(是)
*/
public static final String YES = "Y";
/**
* 是否菜单外链(是)
*/
public static final String YES_FRAME = "0";
/**
* 是否菜单外链(否)
*/
public static final String NO_FRAME = "1";
/**
* 菜单类型(目录)
*/
public static final String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
public static final String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
public static final String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
public final static String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
public final static String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
public final static String INNER_LINK = "InnerLink";
/**
* 校验返回结果码
*/
public final static String UNIQUE = "0";
public final static String NOT_UNIQUE = "1";
/**
* 用户名长度限制
*/
public static final int USERNAME_MIN_LENGTH = 2;
public static final int USERNAME_MAX_LENGTH = 20;
/**
* 密码长度限制
*/
public static final int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20;
}

View File

@@ -0,0 +1,32 @@
package ai.remi.comm.core.enums;
import org.springframework.lang.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 请求方式
*/
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
static {
for (HttpMethod httpMethod : values()) {
mappings.put(httpMethod.name(), httpMethod);
}
}
@Nullable
public static HttpMethod resolve(@Nullable String method) {
return (method != null ? mappings.get(method) : null);
}
public boolean matches(String method) {
return (this == resolve(method));
}
}

View File

@@ -0,0 +1,23 @@
package ai.remi.comm.core.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 身份类型枚举定义
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum IdentityTypeEnum {
STARTDIS("remi", "瑞米官方账号"),
COMPANY("company", "集团公司账户"),
VISITOR("visitor", "游客体验账号");
private String code;
private String desc;
}

View File

@@ -0,0 +1,37 @@
package ai.remi.comm.core.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 权限范围枚举定义
*/
@Getter
@AllArgsConstructor
public enum PermissionScopeEnum {
GROUP("group","集团范围"),
COMPANY("company","公司范围"),
DEPT("dept","部门范围");
@JsonValue
private String code;
private String desc;
@JsonCreator
public static PermissionScopeEnum codeOf(String code) {
return Arrays.stream(PermissionScopeEnum.values())
.filter(each -> each.getCode().equals(code))
.findFirst()
.orElseThrow(() -> {
throw new IllegalArgumentException("非法参数");
});
}
}

View File

@@ -0,0 +1,43 @@
package ai.remi.comm.core.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 错误码设计描述
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ServiceTypeEnum {
WEB_SERVICE("webService", "/web-api/**", "管理端服务"),
APP_SERVICE("appService", "/app-api/**", "移动端服务");
private String code;
/**
* 服务对应的路径前缀
*/
private String pathPrefix;
private String desc;
public static ServiceTypeEnum codeOf(String code) {
return Arrays.stream(values())
.filter((each) -> each.getCode().equals(code)).findFirst()
.orElseThrow(()->new RuntimeException("找不到服务"));
}
public static ServiceTypeEnum pathPrefixOf(String pathPrefix){
return Arrays.stream(values())
.filter((each) -> each.getPathPrefix().equals(pathPrefix)).findFirst()
.orElseThrow(()->new RuntimeException("找不到服务"));
}
}

View File

@@ -0,0 +1,13 @@
package ai.remi.comm.core.enums;
public interface TypeEnum {
/**
* 类型编码
*/
Integer getCode();
/**
* 类型描述
*/
String getDesc();
}

View File

@@ -0,0 +1,19 @@
package ai.remi.comm.core.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum YesNoEnum implements TypeEnum{
NO(0, "否"),
YES(1, "是");
@JsonValue
private Integer code;
private String desc;
}

View File

@@ -0,0 +1,10 @@
package ai.remi.comm.core.excel;
public interface ExcelDynamicSelect {
/**
* 获取动态生成的下拉框可选数据
* @return 动态生成的下拉框可选数据
*/
String[] getSource();
}

View File

@@ -0,0 +1,29 @@
package ai.remi.comm.core.excel;
import java.lang.annotation.*;
@Documented
@Target({ElementType.FIELD})//用此注解用在属性上。
@Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
public @interface ExcelSelected {
/**
* 固定下拉内容
*/
String[] source() default {};
/**
* 动态下拉内容
*/
Class<? extends ExcelDynamicSelect>[] sourceClass() default {};
/**
* 设置下拉框的起始行,默认为第二行
*/
int firstRow() default 1;
/**
* 设置下拉框的结束行,默认为最后一行
*/
int lastRow() default 0x10000;
}

View File

@@ -0,0 +1,52 @@
package ai.remi.comm.core.result;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc PagerBean
*/
@Data
public class PagerBean<T> implements Serializable {
private static final long serialVersionUID = 4804053559968742915L;
/**
* 总记录数
*/
private Long total;
/**
* 当前页
*/
protected Long pageNum;
/**
* 页大小
*/
protected Long pageSize;
/**
* 列表数据
*/
private List<?> records;
/**
* 是否可导入
*/
private boolean if_import;
public PagerBean() {
}
public PagerBean(Long total, Long pageNum, Long pageSize, List<T> records) {
this.total = total;
this.pageNum = pageNum;
this.pageSize = pageSize;
this.records = records;
}
}

View File

@@ -0,0 +1,90 @@
package ai.remi.comm.core.result;
import ai.remi.comm.core.constant.Constants;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc ResultBean
*/
@Data
@SuperBuilder
public class ResultBean<T> implements Serializable {
private static final long serialVersionUID = 8676131899637805509L;
/**
* 成功
*/
public static final String SUCCESS = Constants.SUCCESS;
/**
* 失败
*/
public static final String ERROR = Constants.ERROR;
// 返回编码
private String code;
// 返回信息
private String msg;
// 返回数据封装
@Builder.Default
private T data = (T) "";
public ResultBean() {
}
private static <T> ResultBean<T> restResult(String code, String msg, T data) {
ResultBean<T> apiResult = new ResultBean<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static <T> ResultBean<T> success() {
return restResult(SUCCESS, null, null);
}
public static <T> ResultBean<T> success(String msg) {
return restResult(SUCCESS, msg, null);
}
public static <T> ResultBean<T> success(T data) {
return restResult(SUCCESS, null, data);
}
public static <T> ResultBean<T> success(String msg, T data) {
return restResult(SUCCESS, msg, data);
}
public static <T> ResultBean<T> error() {
return restResult(ERROR, null, null);
}
public static <T> ResultBean<T> error(String msg) {
return restResult(ERROR, msg, null);
}
public static <T> ResultBean<T> error(T data) {
return restResult(ERROR, null, data);
}
public static <T> ResultBean<T> error(String msg, T data) {
return restResult(ERROR, msg, data);
}
public static <T> ResultBean<T> error(String code, String msg, T data) {
return restResult(code, msg, data);
}
}

View File

@@ -0,0 +1,70 @@
package ai.remi.comm.core.spring;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.util.Map;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc SpringBeanUtils
*/
@Component
public class SpringBeans implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringBeans.applicationContext == null) {
SpringBeans.applicationContext = applicationContext;
}
}
/**
* Created by dianjiu on 2022-06-23 ,contact lidianjiu@njydsz.com. 获取applicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* Created by dianjiu on 2022-06-23 ,contact lidianjiu@njydsz.com. 通过name获取 Bean.
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* Created by dianjiu on 2022-06-23 ,contact lidianjiu@njydsz.com. 通过class获取Bean.
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* Created by dianjiu on 2022-06-23 ,contact lidianjiu@njydsz.com. 通过name,以及Clazz返回指定的Bean.
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
/**
* 根据clazz类型获取spring容器中的对象
*/
public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
return getApplicationContext().getBeansOfType(clazz);
}
/**
* 根据注解类从容器中获取对象
*/
public static <T> Map<String, Object> getBeansOfAnnotation(Class<? extends Annotation> clazz) {
return getApplicationContext().getBeansWithAnnotation(clazz);
}
}

View File

@@ -0,0 +1,26 @@
package ai.remi.comm.core.spring;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 通过"server.port"作为name格式调用getPropertiesValue()方法
*/
@Component
public class SpringProperties implements EnvironmentAware {
private static Environment env;
@Override
public void setEnvironment(Environment environment) {
env = environment;
}
public static String getString(String key) {
return env.getProperty(key);
}
}

View File

@@ -0,0 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ai.remi.comm.core.config.AuthFilterConfig,\
ai.remi.comm.core.spring.SpringProperties,\
ai.remi.comm.core.spring.SpringBeans

View File

@@ -0,0 +1,87 @@
<?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>
<artifactId>remi-comm-domain</artifactId>
<name>remi-comm-domain</name>
<description>remi-comm-domain</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--remi-comm-core-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<!--remi-comm-util-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
</dependency>
<!--jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- validator -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!--mapstruct-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,84 @@
package ai.remi.comm.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author dianjiu【】
* @email dianjiuxyz@gmail.com
* @desc BaseEntity
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
/**
* 乐观锁
*/
@Version
@TableField("revision")
private Integer revision;
/**
* 逻辑删除 (0正常 1删除)
* value = "" 默认的原值,默认为 0;
* delval = "" 删除后的值,默认为1;
*/
// @TableLogic(value="0",delval="1")
@TableField("deleted")
private Integer deleted;
/**
* 部门租户ID
*/
@TableField("dept_tenant_id")
private String deptTenantId;
/**
* 公司租户ID
*/
@TableField("company_tenant_id")
private String companyTenantId;
/**
* 创建人
*/
@TableField("created_by")
private String createdBy;
/**
* 创建时间
*/
@TableField(value = "created_at", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
* 更新人
*/
@TableField("updated_by")
private String updatedBy;
/**
* 更新时间
*/
@TableField(value = "updated_at", fill = FieldFill.UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,50 @@
package ai.remi.comm.domain.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc RootEntity
*/
public interface RootEntity extends Serializable {
public String getId();
public RootEntity setId(String id);
public Integer getRevision();
public RootEntity setRevision(Integer revision);
public Integer getDeleted();
public RootEntity setDeleted(Integer deleted);
public String getDeptTenantId();
public RootEntity setDeptTenantId(String deptTenantId);
public String getCompanyTenantId();
public RootEntity setCompanyTenantId(String companyTenantId);
public String getCreatedBy();
public RootEntity setCreatedBy(String createdBy);
public LocalDateTime getCreatedAt();
public RootEntity setCreatedAt(LocalDateTime createdAt);
public String getUpdatedBy();
public RootEntity setUpdatedBy(String updatedBy);
public LocalDateTime getUpdatedAt();
public RootEntity setUpdatedAt(LocalDateTime updatedAt);
}

View File

@@ -0,0 +1,39 @@
package ai.remi.comm.domain.query;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc PageQuery
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder(toBuilder = true)
public class PageQuery implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 当前页
*/
@NotBlank(message = "pageNum当前页不能为空")
private Integer pageNum;
/**
* 页大小
*/
@NotBlank(message = "pageSize页大小不能为空")
private Integer pageSize;
/**
* 指定排序字段
*/
private String orderBy;
}

View File

@@ -0,0 +1,90 @@
package ai.remi.comm.domain.tree;
import ai.remi.comm.util.collection.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
public class TreeBuild<T extends TreeNode> {
/**
* rootId
*/
public String rootId;
/**
* 保存参与构建树形的所有数据(通常数据库查询结果)
*/
public List<T> nodeList = new ArrayList<>();
/**
* 构造方法
*
* @param nodeList 将数据集合赋值给nodeList,即所有数据作为所有节点。
*/
public TreeBuild(String rootId, List<T> nodeList) {
this.rootId = rootId;
this.nodeList = nodeList;
}
/**
* 获取需构建的所有根节点(顶级节点) "0"
*
* @return 所有根节点List集合
*/
public List<T> getRootNode() {
// 保存所有根节点(所有根节点的数据)
List<T> rootNodeList = new ArrayList<>();
// treeNode:查询出的每一条数据(节点)
for (T treeNode : nodeList) {
// 判断当前节点是否为根节点,此处注意:若parentId类型是String,则要采用equals()方法判断。
if (this.rootId.equals(treeNode.getParentId())) {
// 是,添加
rootNodeList.add(treeNode);
}
}
return rootNodeList;
}
/**
* 根据每一个顶级节点(根节点)进行构建树形结构
*
* @return 构建整棵树
*/
public List<T> buildTree() {
// treeNodes:保存一个顶级节点所构建出来的完整树形
List<T> treeNodes = new ArrayList<T>();
// getRootNode():获取所有的根节点
for (T treeRootNode : getRootNode()) {
// 将顶级节点进行构建子树
treeRootNode = buildChildTree(treeRootNode);
// 完成一个顶级节点所构建的树形,增加进来
treeNodes.add(treeRootNode);
}
// 这里对父节点排序
CollectionUtils.sort(treeNodes, "sort");
// for循环结束,即获取所有的根节点,树形构建结束,设置树结果
return treeNodes;
}
/**
* 递归-----构建子树形结构
*
* @param pNode 根节点(顶级节点)
* @return 整棵树
*/
public T buildChildTree(T pNode) {
List<T> childTree = new ArrayList<T>();
// nodeList:所有节点集合(所有数据)
for (T treeNode : nodeList) {
// 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
if (treeNode.getParentId().equals(pNode.getId())) {
// 再递归进行判断当前节点的情况,调用自身方法
childTree.add(buildChildTree(treeNode));
}
}
// 这里对子节点排序
CollectionUtils.sort(childTree, "sort");
// for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
pNode.setChildren(childTree);
return pNode;
}
}

View File

@@ -0,0 +1,39 @@
package ai.remi.comm.domain.tree;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
import java.util.List;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc ResultBean
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class TreeNode<T extends TreeNode> implements Serializable {
private static final long serialVersionUID = 8676131899637805509L;
/**
* 当前节点ID
*/
private String id;
/**
* 父节点ID 顶级节点为0
*/
private String parentId;
/**
* 子节点
*/
private List<T> children;
}

View File

@@ -0,0 +1,57 @@
<?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>
<artifactId>remi-comm-email</artifactId>
<name>remi-comm-email</name>
<description>remi-comm-email</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--common-core-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<!--springboot-mail整合包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- thymeleaf 模版 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- freemarker 模版 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,42 @@
package ai.remi.comm.email.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import javax.annotation.Resource;
import java.util.Properties;
/**
* @author dianjiu【公众号 点九开源】
* @email startdis@njydsz.com
* @desc MinioConfig
*/
@Configuration
@EnableConfigurationProperties(EmailProperties.class)
public class EmailConfig {
@Resource
private EmailProperties emailProperties;
/**
* 初始化 JavaMailSender
*/
@Bean
public JavaMailSenderImpl javaMailSenderImpl() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setProtocol(emailProperties.getProtocol());
javaMailSender.setHost(emailProperties.getSmtpHost());
javaMailSender.setPort(Integer.parseInt(emailProperties.getSmtpPort()));
javaMailSender.setDefaultEncoding(emailProperties.getEncoding());
javaMailSender.setUsername(emailProperties.getFromMail());
javaMailSender.setPassword(emailProperties.getPassword());
// TODO 设置邮件参数
Properties props = new Properties();
// props.setProperty("mail.smtp.auth", emailProperties.get());
javaMailSender.setJavaMailProperties(props);
return javaMailSender;
}
}

View File

@@ -0,0 +1,56 @@
package ai.remi.comm.email.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @author dianjiu【公众号 点九开源】
* @email startdis@njydsz.com
* @desc Email配置类
*/
@Data
@Component
@ConfigurationProperties(prefix = "remi-comm.email")
public class EmailProperties {
/**
* 发送协议
*/
private String protocol;
/**
* 服务器地址
*/
private String smtpHost;
/**
* 服务器端口
*/
private String smtpPort;
/**
* 发件人名称
*/
private String fromName;
/**
* 发件人邮箱
*/
private String fromMail;
/**
* 发件人密码
*/
private String password;
/**
* 邮件编码
*/
private String encoding;
/**
* SSL配置
*/
@Data
@Configuration
@ConfigurationProperties("remi-comm.email.ssl")
class sslConf {
private String enabled;
private String sslPort;
}
}

View File

@@ -0,0 +1,57 @@
package ai.remi.comm.email.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class Email {
/**
* 收件人,多个收件人用“,”隔开
*/
private String to;
/**
* 抄送人,多个抄送人用“,”隔开
*/
private String cc;
/**
* 密送人,多个密送人用“,”隔开
*/
private String bcc;
/**
* 邮件主题
*/
private String subject;
/**
* 邮件内容
*/
private String content;
/**
* 图片资源路径
*/
private String rscPath;
/**
* 图片资源ID(CID)
*/
private String rscId;
/**
* 附件路径
*/
private String[] filePaths;
/**
* 邮件模板地址
*/
private String template;
/**
* 邮件模板变量
*/
private Map<String, Object> variable;
}

View File

@@ -0,0 +1,227 @@
package ai.remi.comm.email.service;
import ai.remi.comm.email.config.EmailProperties;
import ai.remi.comm.email.domain.Email;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
@Slf4j
@Component
public class EmailService {
/**
* 邮件配置
*/
@Resource
private EmailProperties emailProperties;
/**
* 邮件执行
*/
@Resource
private JavaMailSender mailSender;
/**
* thymeleaf
*/
@Resource
private TemplateEngine templateEngine;
/**
* freemarker
*/
@Resource
public Configuration configuration;
/**
* 发送文本邮件
*/
public void sendTextMail(Email email) {
SimpleMailMessage message = new SimpleMailMessage();
try {
message.setFrom(emailProperties.getFromName());
message.setTo(email.getTo());
message.setCc(email.getCc());
message.setBcc(email.getBcc());
message.setSubject(email.getSubject());
message.setText(email.getContent());
mailSender.send(message);
log.info("发送基础邮件成功!");
} catch (MailException e) {
log.error("发送基础邮件异常:" + e);
}
}
/**
* 发送html邮件
*/
public void sendHtmlMail(Email email) {
MimeMessage message = mailSender.createMimeMessage();
try {
//true表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(emailProperties.getFromName());
helper.setTo(email.getTo());
helper.setSubject(email.getSubject());
helper.setText(email.getContent(), true);
// 检查附件数组是否为空
String[] filePaths = email.getFilePaths();
if (checkFilePaths(filePaths)) {
for (String filePath : filePaths) {
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
}
}
mailSender.send(message);
log.info("发送html邮件成功");
} catch (MessagingException e) {
log.error("发送html邮件异常:" + e);
}
}
/**
* 发送正文是静态资源(图片)的邮件
*/
public void sendInlineMail(Email email) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(emailProperties.getFromName());
helper.setTo(email.getTo());
helper.setSubject(email.getSubject());
helper.setText(email.getContent(), true);
// 嵌入静态资源
FileSystemResource res = new FileSystemResource(new File(email.getRscPath()));
helper.addInline(email.getRscId(), res);
// 检查附件数组是否为空
String[] filePaths = email.getFilePaths();
if (checkFilePaths(filePaths)) {
for (String filePath : filePaths) {
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
}
}
mailSender.send(message);
log.info("发送嵌入图片的邮件成功!");
} catch (MessagingException e) {
log.error("发送嵌入图片的邮件异常:" + e);
}
}
/**
* 发送带附件的邮件
*/
public void sendAttachmentsMail(Email email) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(emailProperties.getFromName());
helper.setTo(email.getTo());
helper.setSubject(email.getSubject());
helper.setText(email.getContent(), true);
// 检查附件数组是否为空
String[] filePaths = email.getFilePaths();
if (checkFilePaths(filePaths)) {
for (String filePath : filePaths) {
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
}
}
mailSender.send(message);
log.info("发送带附件的邮件成功!");
} catch (MessagingException e) {
log.error("发送带附件的邮件异常:" + e);
}
}
public void sendThymeleafMail(Email email) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(emailProperties.getFromName());
helper.setTo(email.getTo());
helper.setSubject(email.getSubject());
helper.setText(email.getContent(), true);
// 设置邮件正文
Context context = new Context();
context.setVariables(email.getVariable());
String htmlContent = templateEngine.process(email.getTemplate(), context);
// 检查附件数组是否为空
String[] filePaths = email.getFilePaths();
if (checkFilePaths(filePaths)) {
for (String filePath : filePaths) {
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment("瑞米平台:" + fileName, file);
}
}
// 第二个参数设置为true表示HTML格式
helper.setText(htmlContent, true);
mailSender.send(message);
log.info("发送Thymeleaf模板邮件成功!");
} catch (MessagingException e) {
log.error("发送Thymeleaf模板邮件异常:" + e);
}
}
public void sendFreemarkerMail(Email email) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(emailProperties.getFromName());
helper.setTo(email.getTo());
helper.setSubject(email.getSubject());
helper.setText(email.getContent(), true);
// 设置邮件正文
Template template = configuration.getTemplate(email.getTemplate() + ".flt");
String htmlContent = FreeMarkerTemplateUtils.processTemplateIntoString(template, email.getVariable());
// 检查附件数组是否为空
String[] filePaths = email.getFilePaths();
if (checkFilePaths(filePaths)) {
for (String filePath : filePaths) {
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment("瑞米平台:" + fileName, file);
}
}
// 第二个参数设置为true表示HTML格式
helper.setText(htmlContent, true);
mailSender.send(message);
log.info("发送Freemarker模板邮件成功!");
} catch (Exception e) {
log.error("发送Freemarker模板邮件异常:" + e);
}
}
public static boolean checkFilePaths(String[] array) {
if (array == null) {
log.info("附件数组为null");
return false;
} else if (array.length == 0) {
log.info("附件数组为空");
return false;
} else {
log.info("附件数组不为空且不为null");
return true;
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ai.remi.comm.email.service.EmailService

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${title}</title>
</head>
<body>
<h1>${greeting}</h1>
<p>${message}</p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}">Welcome</title>
</head>
<body>
<h1 th:text="${greeting}">Hello,</h1>
<p th:text="${message}">Welcome to our site!</p>
</body>
</html>

View File

@@ -0,0 +1,6 @@
```yaml
i18n:
messages:
basename: i18n/messages
encoding: utf-8
```

View File

@@ -0,0 +1,55 @@
<?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>
<artifactId>remi-comm-exception</artifactId>
<name>remi-comm-exception</name>
<description>remi-comm-exception</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--remi-comm-domain-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-domain</artifactId>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,40 @@
package ai.remi.comm.exception.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class I18nConfig implements WebMvcConfigurer {
//@Value("${i18n.messages.basename:}")
//private String messagesBasename;
//@Value("${i18n.messages.encoding:}")
//private String messagesEncoding;
@Bean(name = "messageSource")
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource rbms = new ReloadableResourceBundleMessageSource();
//if (!StringUtils.isEmpty(messagesBasename)) {
// //设置path
// rbms.addBasenames(messagesBasename.split(","));
//}
//rbms.setDefaultEncoding(messagesEncoding);
rbms.setBasenames("classpath:i18n/messages");
rbms.setDefaultEncoding("UTF-8");
rbms.setUseCodeAsDefaultMessage(true);
rbms.setFallbackToSystemLocale(false);
return rbms;
}
@Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
}

View File

@@ -0,0 +1,89 @@
package ai.remi.comm.exception.custom;
import ai.remi.comm.core.constant.Constants;
import ai.remi.comm.exception.util.MessageUtils;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc BusinessException
*/
@Data
@NoArgsConstructor
public class BusinessException extends RuntimeException {
@Getter
@Setter
private String code;
@Getter
@Setter
private String key;
@Getter
@Setter
private Object[] params;
@Getter
@Setter
private String message;
public BusinessException(String key) {
super(MessageUtils.getMessage(key));
this.code = Constants.ERROR;
this.message = MessageUtils.getMessage(key);
}
public BusinessException(String key, Object[] params) {
super(MessageUtils.getMessage(key, params));
this.code = Constants.ERROR;
this.message = MessageUtils.getMessage(key, params);
}
public BusinessException(String code, String key) {
super(MessageUtils.getMessage(key));
this.code = code;
this.message = MessageUtils.getMessage(key);
}
public BusinessException(String code, String key, Object[] params) {
super(MessageUtils.getMessage(key, params));
this.code = code;
this.message = MessageUtils.getMessage(key, params);
}
public BusinessException(Throwable cause) {
super(cause);
}
public BusinessException(String code, Throwable cause) {
super(cause);
this.code = code;
}
public BusinessException(String code, String key, Throwable cause) {
super(MessageUtils.getMessage(key), cause);
this.code = code;
this.message = MessageUtils.getMessage(key);
}
public BusinessException(String code, String key, Object[] params, Throwable cause) {
super(MessageUtils.getMessage(key, params), cause);
this.code = code;
this.message = MessageUtils.getMessage(key, params);
}
public BusinessException(String code, String key, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(MessageUtils.getMessage(key), cause, enableSuppression, writableStackTrace);
this.code = code;
this.message = MessageUtils.getMessage(key);
}
public BusinessException(String code, String key, Object[] params, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(MessageUtils.getMessage(key, params), cause, enableSuppression, writableStackTrace);
this.code = code;
this.message = MessageUtils.getMessage(key, params);
}
}

View File

@@ -0,0 +1,52 @@
package ai.remi.comm.exception.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 错误码设计描述
* <p>
* 面向日志的错误码定义共十三位(十位有意义,三位连接符),并且应该具有如下分类: 应用标识,表示错误属于哪个应用,三位数字。 功能域标识,表示错误属于应用中的哪个功能模块,三位数字。 错误类型,表示错误属于那种类型,一位字母。
* 错误编码,错误类型下的具体错误,三位数字。
* <p>
* 面向外部传递的错误码共六位,并且有如下分类: 错误类型,表示错误来源,1位字母。 应用标识,表示错误属于哪个应用,2位数字。 错误编码,错误类型下的具体错误,3位数字。
* <p>
* 错误类型: A-表示错误来源于用户 B-表示错误来源于系统 C-表示错误来源于第三方接口
*/
@Getter
@AllArgsConstructor
public enum CommExceptionCode implements ExceptionCode {
/**
* 面向外部传递的错误码共六位,并且有如下分类:
* 错误类型,表示错误来源,1位字母。
* 应用标识,表示错误属于哪个应用,2位数字。
* 错误编码,错误类型下的具体错误,3位数字。
* <p>
* 错误类型: A-表示错误来源于用户 B-表示错误来源于系统 C-表示错误来源于第三方接口
*/
/****************************客户端异常****************************/
BUSINESS_ERROR("A01001", "business.error"),
ILLEGAL_ARGUMENT("A01002", "illegal.argument"),
NOT_LOGGED_IN("A01003", "not.logged.in"),
/****************************系统异常****************************/
SYSTEM_ERROR("B01001", "system.error"),
ASSERT_ERROR("B01002", "assert.error"),
/****************************三方异常****************************/
OTHER_SYSTEM_ERROR("C01001", "other.system.error"),
CALL_FEIGN_ERROR("C01002", "call.feign.error"),
CALL_SQL_ERROR("C01003", "call.sql.error"),
CALL_REDIS_ERROR("C01004", "call.redis.error"),
CALL_MINIO_ERROR("C01005", "call.minio.error"),
CALL_NACOS_ERROR("C01006", "call.nacos.error"),
CALL_JOB_ERROR("C01007", "call.job.error"),
CALL_MQ_ERROR("C01008", "call.mq.error"),
CALL_ES_ERROR("C01009", "call.es.error");
private String code;
private String key;
}

View File

@@ -0,0 +1,17 @@
package ai.remi.comm.exception.enums;
/**
* 异常code接口
*/
public interface ExceptionCode {
/**
* 异常code
* @return
*/
String getCode();
/**
* 异常消息
* @return
*/
String getKey();
}

View File

@@ -0,0 +1,24 @@
package ai.remi.comm.exception.util;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc ExceptionUtils
*/
public class ExceptionUtils {
/**
* 获取exception的详细错误信息。
*/
public static String getTrace(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
}
}

View File

@@ -0,0 +1,136 @@
package ai.remi.comm.exception.util;
import ai.remi.comm.core.spring.SpringBeans;
import ai.remi.comm.util.auth.AuthInfoUtils;
import ai.remi.comm.util.string.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import java.util.Locale;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc MessageUtils
*/
@Component
public class MessageUtils {
private static final Logger logger = LoggerFactory.getLogger(MessageUtils.class);
/**
* 获得多语言内容,默认根据当前用户的Local获取
*
* @param key 多语言key
* @return 翻译后的值,如获取不到则返回key
*/
public static String getMessage(String key) {
return getMessage(key, new Object[]{});
}
/**
* 获得多语言内容--带参数,默认根据当前用户的Local获取
*
* @param key 多语言key
* @param params 参数
* @return 翻译后的值,如获取不到则返回key
*/
public static String getMessage(String key, Object[] params) {
Locale locale = getLocale();
if (params == null) {
params = new Object[]{};
}
return getMessage(locale, key, params);
}
/**
* 获取多语言内容--带默认值,默认根据当前用户的Local获取
*
* @param key 多语言key
* @param defaultMsg 默认返回值
* @return 翻译后的值,获取不到返回默认值
*/
public static String getMessage(String key, String defaultMsg) {
return getMessage(key, new Object[]{}, defaultMsg);
}
/**
* 获得多语言内容--带参数--带默认值,默认根据当前用户的Local获取
*
* @param key 多语言key
* @param defaultMsg 默认返回值
* @param params 参数
* @return 翻译后的值,获取不到返回默认值
*/
public static String getMessage(String key, Object[] params, String defaultMsg) {
Locale locale = getLocale();
return getMessage(locale, key, params, defaultMsg);
}
/**
* 不带参数
*
* @param locale 语言信息
* @param key 多语言key
* @return 翻译后的值
*/
public static String getMessage(Locale locale, String key) {
return getMessage(locale, key, new Object[]{});
}
/**
* 获取多语言数据
*
* @param locale 语言信息
* @param key 多语言key
* @param params 参数
* @return 翻译后的值
*/
public static String getMessage(Locale locale, String key, Object[] params) {
try {
MessageSource messageSource = (MessageSource) SpringBeans.getBean("messageSource");
return messageSource.getMessage(key, params, locale);
} catch (Exception e) {
logger.error("get locale msg error", e);
}
return key;
}
/**
* 获取多语言数据
*
* @param key 多语言key
* @param params 参数
* @param defaultMsg 默认返回值
* @param locale 语言信息
* @return 翻译后的值
*/
public static String getMessage(Locale locale, String key, Object[] params, String defaultMsg) {
MessageSource messageSource = (MessageSource) SpringBeans.getBean("messageSource");
return messageSource.getMessage(key, params, defaultMsg, locale);
}
/**
* 获取当前语言
*
* @return
*/
private static Locale getLocale() {
String userLanguage = AuthInfoUtils.getUserLanguage();
if (StringUtils.isBlank(userLanguage)) {
return Locale.CHINA;
}
switch (userLanguage.toLowerCase()) {
case "zh_cn":
case "zh-CN":
case "zh":
return Locale.CHINA;
default:
return Locale.US;
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ai.remi.comm.exception.config.I18nConfig

View File

@@ -0,0 +1,59 @@
<?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>
<artifactId>remi-comm-feign</artifactId>
<name>remi-comm-feign</name>
<description>remi-comm-feign</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--remi-comm-util-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId>
</dependency>
<!--servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package ai.remi.comm.feign.annotation;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* @author Remi
* @email remi@dianjiu.cc
* @desc 自定义feign注解,添加basePackages路径
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
public @interface EnableRemiFeignClients {
String[] value() default {};
String[] basePackages() default {"ai.remi"};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}

View File

@@ -0,0 +1,52 @@
package ai.remi.comm.feign.aspect;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Remi
* @email remi@dianjiu.cc
* @desc Feign 配置注册
*/
@Configuration
public class FeignAutoConfiguration {
/**
* 自定义请求拦截器
* 传递用户信息请求头,防止丢失
* @return
*/
@Bean
public RequestInterceptor requestInterceptor() {
return new FeignRequestInterceptor();
}
/**
* 设置Feign客户端日志级别
* <p>
* 有四种日志记录级别可供选择:
* <p>
* - NONE – 不记录日志,这是默认设置
* - BASIC – 仅记录请求方法、URL 和响应状态
* - HEADERS – 将基本信息与请求和响应标头一起记录
* - FULL – 记录请求和响应的主体、标头和元数据
*/
@Bean
Logger.Level level() {
return Logger.Level.FULL;
}
/**
* Feign 的默认错误处理程序ErrorDecoder.default总是抛出FeignException。
* 种行为并不总是最有用的。因此,要自定义抛出的异常
* @return
*/
@Bean
public ErrorDecoder errorDecoder() {
return new RemiFeignErrorDecoder();
}
}

View File

@@ -0,0 +1,72 @@
package ai.remi.comm.feign.aspect;
import ai.remi.comm.core.constant.HeaderConstant;
import ai.remi.comm.core.enums.IdentityTypeEnum;
import ai.remi.comm.core.enums.PermissionScopeEnum;
import ai.remi.comm.util.auth.AuthInfoUtils;
import ai.remi.comm.util.http.ServletUtils;
import ai.remi.comm.util.ip.IpAddrUtils;
import ai.remi.comm.util.string.StringUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Optional;
/**
* @author Remi
* @email remi@dianjiu.cc
* @desc Feign 请求拦截器
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
private Logger log = LoggerFactory.getLogger(FeignRequestInterceptor.class);
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失
//String userId = headers.get(HeaderConstant.X_UNIQUE_ID);
if (StringUtils.isNotNull(httpServletRequest)) {
// 传递服务类型请求头,防止丢失
log.info("FeignHeaderRequestInterceptor X_SERVICE_TYPE:{}", AuthInfoUtils.getServiceTypeCode());
requestTemplate.header(HeaderConstant.X_SERVICE_TYPE, AuthInfoUtils.getServiceTypeCode());
// 传递部门租户请求头,防止丢失
requestTemplate.header(HeaderConstant.X_DEPT_TENANT_ID, AuthInfoUtils.getDeptTenantId());
// 传递公司租户请求头,防止丢失
requestTemplate.header(HeaderConstant.X_COMPANY_TENANT_ID, AuthInfoUtils.getCompanyTenantId());
// 传递用户信息请求头,防止丢失
requestTemplate.header(HeaderConstant.X_UNIQUE_ID, AuthInfoUtils.getUniqueId());
// 传递系统语言请求头,防止丢失
requestTemplate.header(HeaderConstant.X_USER_LANGUAGE, AuthInfoUtils.getUserLanguage());
// 传递用户设备请求头,防止丢失
requestTemplate.header(HeaderConstant.X_DISTINCT_ID, headers.get(HeaderConstant.X_DISTINCT_ID));
// 传递鉴权信息请求头,防止丢失
requestTemplate.header(HeaderConstant.X_ACCESS_TOKEN, headers.get(HeaderConstant.X_ACCESS_TOKEN));
// 判断用户类型是否为集团公司,是的话进一步判断是否为集团权限,是集团权限需要传递集团下属公司IDs
IdentityTypeEnum identityTypeEnum = AuthInfoUtils.getIdentityTypeEnum();
//集团公司的用户
if (identityTypeEnum == IdentityTypeEnum.COMPANY) {
//权限范围
String permissionScope = Optional.ofNullable(AuthInfoUtils.getPermissionScopeEnum())
.map(PermissionScopeEnum::getCode).orElse("");
requestTemplate.header(HeaderConstant.X_PERMISSION_SCOPE, permissionScope);
//当权限范围为集团时,为这个用户管理公司的id
requestTemplate.header(HeaderConstant.X_COMPANY_IDS, AuthInfoUtils.getHasPermissionCompanyIds());
//当权限范围为公司时,为这个用户管理部门的id
requestTemplate.header(HeaderConstant.X_COMPANY_IDS, AuthInfoUtils.getHasPermissionCompanyIds());
} else if (identityTypeEnum == IdentityTypeEnum.VISITOR) {
//访客体验的用户 do anything
}
// 传递请求来源请求头,防止丢失
requestTemplate.header("X-Forwarded-For", IpAddrUtils.getIpAddr(ServletUtils.getRequest()));
}
}
}

View File

@@ -0,0 +1,19 @@
package ai.remi.comm.feign.aspect;
import ai.remi.comm.feign.exception.BadRequestException;
import ai.remi.comm.feign.exception.NotFoundException;
import ai.remi.comm.feign.exception.OpenFeignException;
import feign.Response;
import feign.codec.ErrorDecoder;
public class RemiFeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
return switch (response.status()) {
case 400 -> new BadRequestException();
case 404 -> new NotFoundException("Remi Feign ");
default -> new OpenFeignException("Remi Feign error");
};
}
}

View File

@@ -0,0 +1,17 @@
package ai.remi.comm.feign.exception;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Remi
* @email remi@dianjiu.cc
* @desc BadRequestException
*/
@Data
@NoArgsConstructor
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,33 @@
package ai.remi.comm.feign.exception;
import ai.remi.comm.core.constant.Constants;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author Remi
* @email remi@dianjiu.cc
* @desc NotFoundException
*/
@Data
@NoArgsConstructor
public class NotFoundException extends Exception {
private static final long serialVersionUID = 1L;
@Getter
@Setter
private String code;
@Getter
@Setter
private String message;
public NotFoundException(String msg) {
super(msg);
this.code = Constants.ERROR;
}
public NotFoundException(String msg, Exception e) {
super(msg + " not found,because of" + e.getMessage());
}
}

View File

@@ -0,0 +1,63 @@
package ai.remi.comm.feign.exception;
import ai.remi.comm.core.constant.Constants;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author Remi
* @email remi@dianjiu.cc
* @desc OpenFeignException
*/
@Data
@NoArgsConstructor
public class OpenFeignException extends RuntimeException {
@Getter
@Setter
private String code;
@Getter
@Setter
private String message;
@Getter
@Setter
private Object data;
public OpenFeignException(Throwable cause) {
super(cause);
}
public OpenFeignException(String message) {
super(message);
this.code = Constants.ERROR;
this.message = message;
}
public OpenFeignException(String code, String message) {
super(message);
this.code = code;
this.message = message;
}
public OpenFeignException(String code, Throwable cause) {
super(cause);
this.code = code;
this.message = cause.getMessage();
}
public OpenFeignException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
public OpenFeignException(String code, String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
this.code = code;
this.message = message;
}
}

View File

@@ -0,0 +1,90 @@
<?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>
<artifactId>remi-comm-jdbc</artifactId>
<name>remi-comm-jdbc</name>
<description>remi-comm-jdbc</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--remi-comm-core-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<!--remi-comm-util-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<artifactId>mybatis-plus-boot-starter</artifactId>
<groupId>com.baomidou</groupId>
</dependency>
<dependency>
<artifactId>mybatis-plus-annotation</artifactId>
<groupId>com.baomidou</groupId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<artifactId>mybatis-spring-boot-starter</artifactId>
<groupId>org.mybatis.spring.boot</groupId>
</dependency>
<!-- druid-->
<dependency>
<artifactId>druid-spring-boot-starter</artifactId>
<groupId>com.alibaba</groupId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<artifactId>mysql-connector-java</artifactId>
<groupId>mysql</groupId>
<scope>runtime</scope>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,19 @@
package ai.remi.comm.jdbc.config;
import ai.remi.comm.jdbc.handler.CompanyTenantHandler;
import ai.remi.comm.jdbc.handler.DeptTenantHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 自动配置类
*/
@Configuration
@Import({CompanyTenantHandler.class, DeptTenantHandler.class,TenantConfig.class})
public class AutoConfig {
}

View File

@@ -0,0 +1,30 @@
package ai.remi.comm.jdbc.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc DruidConfig 配置类
*/
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
}

View File

@@ -0,0 +1,27 @@
package ai.remi.comm.jdbc.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 字段填充配置类
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "sql-intercept.field-fill")
public class FieldFillConfig {
private InterceptConfig createdByIntercept=new InterceptConfig();
private InterceptConfig updateByIntercept=new InterceptConfig();
private InterceptConfig createAtIntercept=new InterceptConfig();
private InterceptConfig updateAtIntercept=new InterceptConfig();
}

View File

@@ -0,0 +1,35 @@
package ai.remi.comm.jdbc.config;
import ai.remi.comm.jdbc.enums.InterceptTableStrategy;
import lombok.Data;
import java.util.HashSet;
import java.util.Set;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 拦截器配置类
*/
@Data
public class InterceptConfig {
/**
* 拦截表策略,默认拦截不包含这些表
*/
private InterceptTableStrategy interceptTableStrategy = InterceptTableStrategy.EXCLUDE;
/**
* 是否打开拦截配置
*/
private Boolean enable = false;
/**
* 不拦截或拦截的表
*/
private Set<String> tables = new HashSet<>();
/**
* 处理的字段
*/
private String column = "";
}

View File

@@ -0,0 +1,134 @@
package ai.remi.comm.jdbc.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import ai.remi.comm.jdbc.handler.*;
import ai.remi.comm.jdbc.interceptor.FieldFillInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc MybatisPlusConfig 配置类
*/
@Slf4j
@Configuration
@MapperScan(value = {"ai.remi.*.infra.mapper"})
public class MybatisPlusConfig {
@Resource
private TenantConfig tenantConfig;
@Resource
private FieldFillConfig fieldFillConfig;
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户(部门)拦截器
Boolean deptTenantEnable = tenantConfig.getDeptTenant().getEnable();
if (deptTenantEnable){
TenantLineInnerInterceptor tenantSqlParser = new TenantLineInnerInterceptor();
tenantSqlParser.setTenantLineHandler(getDeptTenantInterceptor());
interceptor.addInnerInterceptor(tenantSqlParser);
log.info("----------------部门租户拦截器开启-----------------------");
}
// 多租户(公司)拦截器
Boolean companyTenantEnable = tenantConfig.getCompanyTenant().getEnable();
if (companyTenantEnable){
TenantLineInnerInterceptor dealerSqlParser = new TenantLineInnerInterceptor();
dealerSqlParser.setTenantLineHandler(getCompanyTenantInterceptor());
interceptor.addInnerInterceptor(dealerSqlParser);
log.info("----------------公司租户拦截器开启-----------------------");
}
//创建人填充
Boolean createByEnable = fieldFillConfig.getCreatedByIntercept().getEnable();
if (createByEnable){
FieldFillInterceptor createByInterceptor = new FieldFillInterceptor();
createByInterceptor.setFieldFillHandler(getCreatedByHandler());
interceptor.addInnerInterceptor(createByInterceptor);
log.info("----------------创建人填充器开启-----------------------");
}
//更新人填充
Boolean updateByEnable = fieldFillConfig.getUpdateByIntercept().getEnable();
if (updateByEnable){
FieldFillInterceptor updateByInterceptor = new FieldFillInterceptor();
updateByInterceptor.setFieldFillHandler(getUpdatedByHandler());
interceptor.addInnerInterceptor(updateByInterceptor);
log.info("----------------更新人填充器开启-----------------------");
}
//创建时间
Boolean createAtEnable = fieldFillConfig.getCreateAtIntercept().getEnable();
if (createAtEnable){
FieldFillInterceptor createAtInterceptor = new FieldFillInterceptor();
createAtInterceptor.setFieldFillHandler(getCreatedAtHandler());
interceptor.addInnerInterceptor(createAtInterceptor);
log.info("----------------创建时间填充器开启-----------------------");
}
//更新时间
Boolean updateAtEnable = fieldFillConfig.getUpdateAtIntercept().getEnable();
if (updateAtEnable){
FieldFillInterceptor updateAtInterceptor = new FieldFillInterceptor();
updateAtInterceptor.setFieldFillHandler(getUpdatedAtHandler());
interceptor.addInnerInterceptor(updateAtInterceptor);
log.info("----------------更新时间填充器开启-----------------------");
}
//自带分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
//自带乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
@Bean
public UpdatedByHandler getUpdatedByHandler(){
return new UpdatedByHandler(fieldFillConfig);
}
@Bean
public UpdatedAtHandler getUpdatedAtHandler(){
return new UpdatedAtHandler(fieldFillConfig);
}
@Bean
public CreatedByHandler getCreatedByHandler(){
return new CreatedByHandler(fieldFillConfig);
}
@Bean
public CreatedAtHandler getCreatedAtHandler(){
return new CreatedAtHandler(fieldFillConfig);
}
@Bean
public DeptTenantHandler getDeptTenantInterceptor() {
return new DeptTenantHandler(tenantConfig);
}
@Bean
public CompanyTenantHandler getCompanyTenantInterceptor() {
return new CompanyTenantHandler(tenantConfig);
}
}

View File

@@ -0,0 +1,27 @@
package ai.remi.comm.jdbc.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 租户配置类
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "sql-intercept.tenant")
public class TenantConfig {
//不拦截的请求
private String anonUrlSet="";
private InterceptConfig deptTenant=new InterceptConfig();
private InterceptConfig companyTenant=new InterceptConfig();
}

View File

@@ -0,0 +1,20 @@
package ai.remi.comm.jdbc.enums;
/**
* 数据源
*
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
*/
public enum DataSourceType {
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}

View File

@@ -0,0 +1,22 @@
package ai.remi.comm.jdbc.enums;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 字段填充策略枚举
*/
public enum FieldFillStrategyEnum {
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}

View File

@@ -0,0 +1,11 @@
package ai.remi.comm.jdbc.enums;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 拦截表策略
*/
public enum InterceptTableStrategy {
INCLUDE,
EXCLUDE;
}

View File

@@ -0,0 +1,66 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.InterceptConfig;
import ai.remi.comm.jdbc.enums.FieldFillStrategyEnum;
import net.sf.jsqlparser.expression.Expression;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 字段填充拦截器抽象类
*/
@SuppressWarnings("checkstyle:Indentation")
public abstract class AbstractFieldFillHandler extends AbstractSqlHandler implements FieldFillHandler {
private FieldFillStrategyEnum fieldFillStrategyEnum;
public AbstractFieldFillHandler(FieldFillStrategyEnum fieldFillStrategyEnum, InterceptConfig interceptConfig) {
super(interceptConfig);
this.fieldFillStrategyEnum = fieldFillStrategyEnum;
}
protected abstract Expression doGetFieldFillValue();
@Override
public Expression getFieldFillValue() {
return doGetFieldFillValue();
}
/**
* 优先级配置填充字段--》子类实现getDefaultFieldFillColumn
* 如果都为指定则抛出异常
*/
@Override
public String getFieldFillColumn() {
return handleColumn(interceptConfig.getColumn());
}
@Override
public boolean createIgnore(String tableName) {
if (FieldFillStrategyEnum.UPDATE==fieldFillStrategyEnum){
return true;
}
return defaultIgnoreStrategy(tableName,interceptConfig);
}
@Override
protected boolean customIgnore(){
return false;
}
@Override
public boolean updateIgnore(String tableName) {
if (FieldFillStrategyEnum.INSERT==fieldFillStrategyEnum){
return true;
}
return defaultIgnoreStrategy(tableName,interceptConfig);
}
}

View File

@@ -0,0 +1,70 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.InterceptConfig;
import ai.remi.comm.jdbc.enums.InterceptTableStrategy;
import java.util.Set;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc SQL拦截器抽象类
*/
public abstract class AbstractSqlHandler {
protected InterceptConfig interceptConfig;
public AbstractSqlHandler(InterceptConfig interceptConfig) {
this.interceptConfig = interceptConfig;
}
protected boolean defaultIgnoreStrategy(String tableName, InterceptConfig interceptConfig) {
if (customIgnore()) {
return true;
}
InterceptTableStrategy interceptTableStrategy = interceptConfig.getInterceptTableStrategy();
Set<String> tables = interceptConfig.getTables();
if (InterceptTableStrategy.EXCLUDE.equals(interceptTableStrategy)) {
return tables.contains(tableName);
} else if (InterceptTableStrategy.INCLUDE.equals(interceptTableStrategy)) {
return !tables.contains(tableName);
}else {
throw new RuntimeException("未指定表拦截策略");
}
}
/**
* 如果传入的字段名为空串,则获取默认的字段
* @param column
* @return
*/
protected String handleColumn(String column) {
if (isNotBlank(column)){
return column;
}
column = getDefaultColumn();
if (isNotBlank(column)){
return column;
}
throw new RuntimeException(this.getClass()+"未指定填充字段,请检查");
}
public static boolean isNotBlank(String str) {
return str!=null&&str.trim().length() > 0;
}
/**
* 子类实现指定 字段名
* @return
*/
protected String getDefaultColumn(){
return "";
}
/**
* 子类需要实现自定义忽略策略
* @return
*/
protected abstract boolean customIgnore();
}

View File

@@ -0,0 +1,65 @@
package ai.remi.comm.jdbc.handler;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import ai.remi.comm.jdbc.config.InterceptConfig;
import ai.remi.comm.util.auth.TenantHolder;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.beans.factory.annotation.Value;
import java.util.List;
import java.util.Optional;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 租户拦截器抽象类
*/
@Slf4j
public abstract class AbstractTenantHandler extends AbstractSqlHandler implements TenantLineHandler {
@Value("${spring.application.name}")
private String applicationContextName;
private final static List<String> IGNORE_APP_NAME = List.of("remi-ids-web","remi-ams-web","remi-mps-web","remi-sso-web","remi-dfe-web","remi-wfe-web","remi-bie-web","remi-wpe-web","remi-job-web","remi-lce-web");
public AbstractTenantHandler(InterceptConfig interceptConfig) {
super(interceptConfig);
}
protected abstract String doGetTenantId();
@Override
public Expression getTenantId() {
return new StringValue(doGetTenantId());
}
@Override
public String getTenantIdColumn() {
return handleColumn(interceptConfig.getColumn());
}
@Override
public boolean ignoreTable(String tableName) {
return defaultIgnoreStrategy(tableName, interceptConfig);
}
@Override
protected boolean customIgnore(){
// 忽略的服务
if (IGNORE_APP_NAME.contains(applicationContextName)){
return true;
}
Boolean isAdmin=Optional.ofNullable(TenantHolder.isAdmin()).orElse(false);
// 是否是不拦截的请求
Boolean ignoredRequest = Optional.ofNullable(TenantHolder.getIgnoredRequest()).orElse(false);
// 拦截未打开、忽略的请求、ebg账号---》这些情况都忽略掉
return ignoredRequest || isAdmin;
}
}

View File

@@ -0,0 +1,32 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.TenantConfig;
import ai.remi.comm.util.auth.AuthInfoUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 部门租户ID字段填充拦截器
*/
@Slf4j
public class CompanyTenantHandler extends AbstractTenantHandler {
public CompanyTenantHandler(TenantConfig tenantConfig) {
super(tenantConfig.getCompanyTenant());
}
@Override
protected String doGetTenantId() {
return Optional.ofNullable(AuthInfoUtils.getCompanyTenantId()).orElse("");
}
@Override
protected String getDefaultColumn() {
return "company_tenant_id";
}
}

View File

@@ -0,0 +1,35 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.FieldFillConfig;
import ai.remi.comm.jdbc.enums.FieldFillStrategyEnum;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.TimestampValue;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 创建时间字段填充拦截器
*/
public class CreatedAtHandler extends AbstractFieldFillHandler {
public CreatedAtHandler(FieldFillConfig fieldFillConfig) {
super(FieldFillStrategyEnum.INSERT, fieldFillConfig.getUpdateAtIntercept());
}
@Override
protected Expression doGetFieldFillValue() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = format.format(new Date());
return new TimestampValue(date);
}
@Override
protected String getDefaultColumn() {
return "created_at";
}
}

View File

@@ -0,0 +1,38 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.FieldFillConfig;
import ai.remi.comm.jdbc.enums.FieldFillStrategyEnum;
import ai.remi.comm.util.auth.AuthInfo;
import ai.remi.comm.util.auth.AuthInfoUtils;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import java.util.Optional;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 创建人字段填充拦截器
*/
public class CreatedByHandler extends AbstractFieldFillHandler {
public CreatedByHandler(FieldFillConfig fieldFillConfig) {
super(FieldFillStrategyEnum.INSERT, fieldFillConfig.getCreatedByIntercept());
}
@Override
protected Expression doGetFieldFillValue() {
AuthInfo authInfo = AuthInfoUtils.getAuthInfo();
String uniqueId = Optional.ofNullable(authInfo)
.map(AuthInfo::getUniqueId).orElse("系统创建");
return new StringValue(uniqueId);
}
@Override
protected String getDefaultColumn() {
return "created_by";
}
}

View File

@@ -0,0 +1,31 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.TenantConfig;
import ai.remi.comm.util.auth.AuthInfoUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 部门租户ID字段填充拦截器
*/
@Slf4j
public class DeptTenantHandler extends AbstractTenantHandler {
public DeptTenantHandler(TenantConfig tenantConfig) {
super(tenantConfig.getDeptTenant());
}
@Override
protected String doGetTenantId() {
return Optional.ofNullable(AuthInfoUtils.getDeptTenantId()).orElse("");
}
@Override
protected String getDefaultColumn() {
return "dept_tenant_id";
}
}

View File

@@ -0,0 +1,43 @@
package ai.remi.comm.jdbc.handler;
import net.sf.jsqlparser.expression.Expression;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 字段填充拦截器
*/
public interface FieldFillHandler {
/**
* 获取自动填充值表达式
*/
Expression getFieldFillValue();
/**
* 获取自动填充的字段名
*/
String getFieldFillColumn();
/**
* 在创建的时候根据表名判断是否忽略拼接字段
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略,false:需要解析并拼接
*/
default boolean createIgnore(String tableName){
return false;
}
/**
* 在更新的时候根据表名判断是否忽略拼接字段
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略,false:需要解析并拼接
*/
default boolean updateIgnore(String tableName) {
return false;
}
}

View File

@@ -0,0 +1,70 @@
package ai.remi.comm.jdbc.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc Json类型转换器
*/
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private static ObjectMapper objectMapper = new ObjectMapper();
private Class<T> type;
public JsonTypeHandler(Class<T> type) {
if (type == null) {
throw new NullPointerException("Type argument cannot be null");
}
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, toJsonString(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parse(rs.getString(columnName));
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parse(rs.getString(columnIndex));
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parse(cs.getString(columnIndex));
}
private String toJsonString(Object parameter) {
try {
return objectMapper.writeValueAsString(parameter);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private T parse(String json) {
try {
return objectMapper.readValue(json, type);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,14 @@
package ai.remi.comm.jdbc.handler;
import java.util.List;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc List类型转换器
*/
public class ListTypeHandler extends JsonTypeHandler<List> {
public ListTypeHandler() {
super(List.class);
}
}

View File

@@ -0,0 +1,14 @@
package ai.remi.comm.jdbc.handler;
import java.util.Map;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc Map类型转换器
*/
public class MapTypeHandler extends JsonTypeHandler<Map> {
public MapTypeHandler() {
super(Map.class);
}
}

View File

@@ -0,0 +1,32 @@
package ai.remi.comm.jdbc.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc MyMetaObjectHandler 配置类
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill...");
this.setFieldValByName("createdAt", LocalDateTime.now(), metaObject);
//this.setFieldValByName("createdBy", AuthInfoUtils.getUniqueId(), metaObject);
this.setFieldValByName("updatedAt", LocalDateTime.now(), metaObject);
//this.setFieldValByName("updatedBy", AuthInfoUtils.getUniqueId(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill...");
this.setFieldValByName("updatedAt", LocalDateTime.now(), metaObject);
}
}

View File

@@ -0,0 +1,35 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.FieldFillConfig;
import ai.remi.comm.jdbc.enums.FieldFillStrategyEnum;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.TimestampValue;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 更新时间字段填充拦截器
*/
public class UpdatedAtHandler extends AbstractFieldFillHandler {
public UpdatedAtHandler(FieldFillConfig fieldFillConfig) {
super(FieldFillStrategyEnum.INSERT_UPDATE, fieldFillConfig.getUpdateAtIntercept());
}
@Override
protected Expression doGetFieldFillValue() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = format.format(new Date());
return new TimestampValue(date);
}
@Override
protected String getDefaultColumn() {
return "updated_at";
}
}

View File

@@ -0,0 +1,37 @@
package ai.remi.comm.jdbc.handler;
import ai.remi.comm.jdbc.config.FieldFillConfig;
import ai.remi.comm.jdbc.enums.FieldFillStrategyEnum;
import ai.remi.comm.util.auth.AuthInfo;
import ai.remi.comm.util.auth.AuthInfoUtils;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import java.util.Optional;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 更新人字段填充拦截器
*/
public class UpdatedByHandler extends AbstractFieldFillHandler {
public UpdatedByHandler(FieldFillConfig fieldFillConfig) {
super(FieldFillStrategyEnum.INSERT_UPDATE, fieldFillConfig.getUpdateByIntercept());
}
@Override
protected Expression doGetFieldFillValue() {
AuthInfo authInfo = AuthInfoUtils.getAuthInfo();
String uniqueId = Optional.ofNullable(authInfo)
.map(AuthInfo::getUniqueId).orElse("系统创建");
return new StringValue(uniqueId);
}
@Override
protected String getDefaultColumn() {
return "updated_by";
}
}

View File

@@ -0,0 +1,430 @@
package ai.remi.comm.jdbc.interceptor;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.toolkit.PropertyMapper;
import ai.remi.comm.jdbc.handler.FieldFillHandler;
import lombok.*;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 字段填充拦截器
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class FieldFillInterceptor extends JsqlParserSupport implements InnerInterceptor {
private FieldFillHandler fieldFillHandler;
/**
* 拦截器处理前
*
* @param sh
* @param connection
* @param transactionTimeout
*/
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
SqlCommandType sct = ms.getSqlCommandType();
if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
return;
}
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
mpBs.sql(parserMulti(mpBs.sql(), null));
}
}
/**
* 查询进程拦截器
*
* @param select
* @param index
* @param sql
* @param obj
*/
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
}
/**
* 删除进程拦截器
*
* @param delete
* @param index
* @param sql
* @param obj
*/
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
}
/**
* 查询属性进程拦截器
*
* @param selectBody
*/
protected void processSelectBody(SelectBody selectBody) {
if (selectBody == null) {
return;
}
if (selectBody instanceof PlainSelect) {
processPlainSelect((PlainSelect) selectBody);
} else if (selectBody instanceof WithItem) {
WithItem withItem = (WithItem) selectBody;
processSelectBody(withItem.getSubSelect().getSelectBody());
} else {
SetOperationList operationList = (SetOperationList) selectBody;
List<SelectBody> selectBodys = operationList.getSelects();
if (CollectionUtils.isNotEmpty(selectBodys)) {
selectBodys.forEach(this::processSelectBody);
}
}
}
/**
* 新增进程拦截器
*
* @param insert
* @param index
* @param sql
* @param obj
*/
@Override
protected void processInsert(Insert insert, int index, String sql, Object obj) {
if (fieldFillHandler.createIgnore(insert.getTable().getName())) {
// 过滤退出执行
return;
}
//获取所有字段
List<Column> columns = insert.getColumns();
if (CollectionUtils.isEmpty(columns)) {
// 针对没有任何列的insert 不处理
return;
}
//获取操作人字段
String FieldFillColumn = fieldFillHandler.getFieldFillColumn();
if (columns.stream().map(Column::getColumnName).anyMatch(i -> i.equals(FieldFillColumn))) {
// 针对已给出操作人列的insert 不处理 防止重复字段
return;
}
columns.add(new Column(fieldFillHandler.getFieldFillColumn()));
//获取重复表达式 列表
List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new StringValue(fieldFillHandler.getFieldFillColumn()));
equalsTo.setRightExpression(fieldFillHandler.getFieldFillValue());
duplicateUpdateColumns.add(equalsTo);
}
Select select = insert.getSelect();
if (select != null) {
this.processInsertSelect(select.getSelectBody());
} else if (insert.getItemsList() != null) {
ItemsList itemsList = insert.getItemsList();
if (itemsList instanceof MultiExpressionList) {
// 支持批量操作
((MultiExpressionList) itemsList).getExprList()
.forEach(el -> el.getExpressions().add(fieldFillHandler.getFieldFillValue()));
} else {
((ExpressionList) itemsList).getExpressions().add(fieldFillHandler.getFieldFillValue());
}
} else {
throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
}
}
/**
* update 语句处理
*/
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
final Table table = update.getTable();
if (fieldFillHandler.updateIgnore(table.getName())) {
// 过滤退出执行
return;
}
List<Column> columns = update.getColumns();
if (CollectionUtils.isEmpty(columns)) {
// 针对没有任何列的 update 不处理
return;
}
String FieldFillColumn = fieldFillHandler.getFieldFillColumn();
if (columns.stream().map(Column::getColumnName).anyMatch(i -> i.equals(FieldFillColumn))) {
// 针对已给出操作人列的 update 不处理 防止重复字段
return;
}
//这里出问题了,已修复,使用addUpdateSet()实现动态添加更新人和更新时间
//columns.add(new Column(fieldFillHandler.getFieldFillColumn()));
//update.getExpressions().add(fieldFillHandler.getFieldFillValue());
update.addUpdateSet(new Column(fieldFillHandler.getFieldFillColumn()), fieldFillHandler.getFieldFillValue());
}
/**
* 处理 insert into select
* <p>
* 进入这里表示需要 insert 的表启用了操作人插件,则 select 的表都启动了
*
* @param selectBody SelectBody
*/
protected void processInsertSelect(SelectBody selectBody) {
PlainSelect plainSelect = (PlainSelect) selectBody;
FromItem fromItem = plainSelect.getFromItem();
if (fromItem instanceof Table) {
processPlainSelect(plainSelect);
appendSelectItem(plainSelect.getSelectItems());
} else if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
appendSelectItem(plainSelect.getSelectItems());
processInsertSelect(subSelect.getSelectBody());
}
}
/**
* 追加 SelectItem
*
* @param selectItems SelectItem
*/
protected void appendSelectItem(List<SelectItem> selectItems) {
if (CollectionUtils.isEmpty(selectItems)) {
return;
}
if (selectItems.size() == 1) {
SelectItem item = selectItems.get(0);
if (item instanceof AllColumns || item instanceof AllTableColumns) {
return;
}
}
selectItems.add(new SelectExpressionItem(new Column(fieldFillHandler.getFieldFillColumn())));
}
/**
* 处理 PlainSelect
*/
protected void processPlainSelect(PlainSelect plainSelect) {
FromItem fromItem = plainSelect.getFromItem();
Expression where = plainSelect.getWhere();
processWhereSubSelect(where);
if (fromItem instanceof Table) {
Table fromTable = (Table) fromItem;
if (!fieldFillHandler.createIgnore(fromTable.getName())) {
plainSelect.setWhere(builderExpression(where, fromTable));
}
} else {
processFromItem(fromItem);
}
List<SelectItem> selectItems = plainSelect.getSelectItems();
if (CollectionUtils.isNotEmpty(selectItems)) {
selectItems.forEach(this::processSelectItem);
}
List<Join> joins = plainSelect.getJoins();
if (CollectionUtils.isNotEmpty(joins)) {
joins.forEach(j -> {
processJoin(j);
processFromItem(j.getRightItem());
});
}
}
/**
* 处理where条件内的子查询
* <p>
* 支持如下:
* 1. in
* 2. =
* 3. >
* 4. <
* 5. >=
* 6. <=
* 7. <>
* 8. EXISTS
* 9. NOT EXISTS
* <p>
* 前提条件:
* 1. 子查询必须放在小括号中
* 2. 子查询一般放在比较操作符的右边
*
* @param where where 条件
*/
protected void processWhereSubSelect(Expression where) {
if (where == null) {
return;
}
if (where instanceof FromItem) {
processFromItem((FromItem) where);
return;
}
if (where.toString().indexOf("SELECT") > 0) {
// 有子查询
if (where instanceof BinaryExpression) {
// 比较符号 , and , or , 等等
BinaryExpression expression = (BinaryExpression) where;
processWhereSubSelect(expression.getLeftExpression());
processWhereSubSelect(expression.getRightExpression());
} else if (where instanceof InExpression) {
// in
InExpression expression = (InExpression) where;
ItemsList itemsList = expression.getRightItemsList();
if (itemsList instanceof SubSelect) {
processSelectBody(((SubSelect) itemsList).getSelectBody());
}
} else if (where instanceof ExistsExpression) {
// exists
ExistsExpression expression = (ExistsExpression) where;
processWhereSubSelect(expression.getRightExpression());
} else if (where instanceof NotExpression) {
// not exists
NotExpression expression = (NotExpression) where;
processWhereSubSelect(expression.getExpression());
} else if (where instanceof Parenthesis) {
Parenthesis expression = (Parenthesis) where;
processWhereSubSelect(expression.getExpression());
}
}
}
protected void processSelectItem(SelectItem selectItem) {
if (selectItem instanceof SelectExpressionItem) {
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
if (selectExpressionItem.getExpression() instanceof SubSelect) {
processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
} else if (selectExpressionItem.getExpression() instanceof Function) {
processFunction((Function) selectExpressionItem.getExpression());
}
}
}
/**
* 处理函数
* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
*
* @param function
*/
protected void processFunction(Function function) {
ExpressionList parameters = function.getParameters();
if (parameters != null) {
parameters.getExpressions().forEach(expression -> {
if (expression instanceof SubSelect) {
processSelectBody(((SubSelect) expression).getSelectBody());
} else if (expression instanceof Function) {
processFunction((Function) expression);
}
});
}
}
/**
* 处理子查询等
*/
protected void processFromItem(FromItem fromItem) {
if (fromItem instanceof SubJoin) {
SubJoin subJoin = (SubJoin) fromItem;
if (subJoin.getJoinList() != null) {
subJoin.getJoinList().forEach(this::processJoin);
}
if (subJoin.getLeft() != null) {
processFromItem(subJoin.getLeft());
}
} else if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
} else if (fromItem instanceof ValuesList) {
logger.debug("Perform a subquery, if you do not give us feedback");
} else if (fromItem instanceof LateralSubSelect) {
LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
if (lateralSubSelect.getSubSelect() != null) {
SubSelect subSelect = lateralSubSelect.getSubSelect();
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
}
}
}
/**
* 处理联接语句
*/
protected void processJoin(Join join) {
if (join.getRightItem() instanceof Table) {
Table fromTable = (Table) join.getRightItem();
if (fieldFillHandler.createIgnore(fromTable.getName())) {
// 过滤退出执行
return;
}
join.setOnExpression(builderExpression(join.getOnExpression(), fromTable));
}
}
/**
* 处理条件
*/
protected Expression builderExpression(Expression currentExpression, Table table) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.getAliasColumn(table));
equalsTo.setRightExpression(fieldFillHandler.getFieldFillValue());
if (currentExpression == null) {
return equalsTo;
}
if (currentExpression instanceof OrExpression) {
return new AndExpression(new Parenthesis(currentExpression), equalsTo);
} else {
return new AndExpression(currentExpression, equalsTo);
}
}
/**
* 字段别名设置
*
* @param table 表对象
* @return 字段
*/
protected Column getAliasColumn(Table table) {
StringBuilder column = new StringBuilder();
if (table.getAlias() != null) {
column.append(table.getAlias().getName()).append(StringPool.DOT);
}
column.append(fieldFillHandler.getFieldFillColumn());
return new Column(column.toString());
}
@Override
public void setProperties(Properties properties) {
PropertyMapper.newInstance(properties).whenNotBlank("fieldFillHandler", ClassUtils::newInstance, this::setFieldFillHandler);
}
}

View File

@@ -0,0 +1,9 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ai.remi.comm.jdbc.config.AutoConfig,\
ai.remi.comm.jdbc.config.DruidConfig,\
ai.remi.comm.jdbc.config.FieldFillConfig,\
ai.remi.comm.jdbc.config.InterceptConfig,\
ai.remi.comm.jdbc.config.MybatisPlusConfig,\
ai.remi.comm.jdbc.config.TenantConfig

View File

@@ -0,0 +1,51 @@
<?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>
<artifactId>remi-comm-job</artifactId>
<name>remi-comm-job</name>
<description>remi-comm-job</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--remi-comm-core-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<!-- xxl-job 核心包-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,60 @@
package ai.remi.comm.job.config;
import ai.remi.comm.job.properties.XxlJobProperties;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* 定时任务配置
*
* @author dianjiu
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(value = XxlJobProperties.class)
@ConditionalOnProperty(value = "xxl-job.enabled", havingValue = "true")
public class XxlJobConfiguration {
@Resource
private XxlJobProperties xxlJobProperties;
/**
* 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
* <p>
* 1、引入依赖:
* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
* <p>
* 2、配置文件,或者容器启动变量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
* <p>
* 3、获取IP
* String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init start.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses());
xxlJobSpringExecutor.setAppname(xxlJobProperties.getExecutor().getAppname());
xxlJobSpringExecutor.setAddress(xxlJobProperties.getExecutor().getAddress());
xxlJobSpringExecutor.setIp(xxlJobProperties.getExecutor().getIp());
xxlJobSpringExecutor.setPort(xxlJobProperties.getExecutor().getPort());
xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
xxlJobSpringExecutor.setLogPath(xxlJobProperties.getExecutor().getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getExecutor().getLogRetentionDays());
log.info(">>>>>>>>>>> xxl-job config init end.");
return xxlJobSpringExecutor;
}
}

View File

@@ -0,0 +1,63 @@
package ai.remi.comm.job.properties;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* xxl-job的配置属性
*
* @author dianjiu
* 2021-12-28 09:40:38
*/
@Data
@ConfigurationProperties(prefix = "xxl-job")
public class XxlJobProperties {
/**
* 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
* eg:http://127.0.0.1:8080/xxl-job-admin
*/
private String adminAddresses;
/**
* 执行器通讯TOKEN [选填]:非空时启用;
*/
private String accessToken;
private Boolean enabled;
private Executor executor;
@Getter
@Setter
public static class Executor {
/**
* 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
* eg:remi-boot-job
*/
private String appname;
/**
* 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
*/
private String address;
/**
* 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
*/
private String ip;
/**
* 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
*/
private int port;
/**
* 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
*/
private String logPath;
/**
* 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
*/
private int logRetentionDays;
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ai.remi.comm.job.config.XxlJobConfiguration

View File

@@ -0,0 +1,69 @@
# 工程简介
瑞米平台公共的日志模块
## 操作日志
主要面向用户的,要求简单易懂,反映出用户所做的动作。
### 核心功能
- 要实现以下功能:
- 自定义spring boot starter
- 定义日志注解
- AOP拦截日志注解方法
- 定义日志动态内容模板
- 模板中又需要实现:
- 动态模板表达式解析:用强大的SpEL来解析表达式
- 自定义函数:支持目标方法前置/后置的自定义函数
##
### SQL脚本
```sql
DROP TABLE IF EXISTS t_system_log;
CREATE TABLE t_system_log
(
id VARCHAR(32) NOT NULL COMMENT '日志编号',
user_id VARCHAR(32) COMMENT '用户ID',
user_name VARCHAR(255) COMMENT '用户名称',
app_code VARCHAR(255) COMMENT '应用编码',
app_name VARCHAR(255) COMMENT '应用名称',
business_no VARCHAR(255) COMMENT '业务流水号',
business_type VARCHAR(255) COMMENT '业务类型(新增、修改、删除、查询、导入、导出、其他)',
log_content LONGTEXT COMMENT '日志模板内容',
log_extras LONGTEXT COMMENT '额外的日志内容',
request_url VARCHAR(255) COMMENT '请求地址',
request_ip VARCHAR(255) COMMENT '请求IP',
request_type VARCHAR(255) COMMENT '请求类型(POST、PUT、OPTIONS、DELETE)',
content_type VARCHAR(255) COMMENT '请求报文类型(map、json)',
request_body LONGTEXT COMMENT '请求报文',
response_body LONGTEXT COMMENT '响应报文',
request_time DATETIME COMMENT '请求时间',
response_time DATETIME COMMENT '响应时间',
execute_time BIGINT COMMENT '请求耗时',
trace_id VARCHAR(255) COMMENT '链路追踪ID',
error_info LONGTEXT COMMENT '异常信息',
user_agent VARCHAR(255) COMMENT '浏览器、操作系统、UI引擎标识',
revision INTEGER(11) COMMENT '乐观锁',
is_deleted INTEGER(1) COMMENT '逻辑删除;0-未删除 1-已删除',
group_tenant_id VARCHAR(32) COMMENT '部门租户ID',
company_tenant_id VARCHAR(32) COMMENT '公司租户ID',
created_by VARCHAR(32) COMMENT '创建人',
created_at DATETIME COMMENT '创建时间',
updated_by VARCHAR(32) COMMENT '更新人',
updated_at DATETIME COMMENT '更新时间',
PRIMARY KEY (id)
) COMMENT = '系统日志表';
```
## 系统日志
主要用于开发者调试排查系统问题的,不要求固定格式和可读性。
###
## 延伸阅读
https://tech.meituan.com/2021/09/16/operational-logbook.html
https://juejin.cn/post/7075184311397253128
http://mysql.taobao.org/monthly/2017/09/08/

View File

@@ -0,0 +1,69 @@
<?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>
<artifactId>remi-comm-log</artifactId>
<name>remi-comm-log</name>
<description>remi-comm-log</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--公共依赖-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId>
</dependency>
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-redis</artifactId>
</dependency>
<!--项目依赖-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-sys-api</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,23 @@
package ai.remi.comm.log.annotation;
import ai.remi.comm.log.config.LogRecordAutoConfigure;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc 日志记录开启注解
*/
@Documented
@Inherited
@Order(Integer.MIN_VALUE)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(LogRecordAutoConfigure.class)
public @interface EnableLogRecord {
}

View File

@@ -0,0 +1,74 @@
package ai.remi.comm.log.annotation;
import ai.remi.comm.log.enums.BusinessType;
import ai.remi.comm.log.enums.OperatorType;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc 操作日志记录
*/
@Inherited
@Documented
@Order(Integer.MIN_VALUE)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRecord {
/**
* 接口名称
*/
String content() default "";
/**
* 功能操作类型
*/
BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
boolean isSaveResponseData() default true;
/**
* 是否保存异常的信息
*/
boolean isSaveErrorInfo() default true;
/**
* 在方法成功执行后打点,记录方法的执行时间发送到指标系统,默认关闭
*
* @return
*/
boolean recordSuccessMetrics() default false;
/**
* 在方法成功失败后打点,记录方法的执行时间发送到指标系统,默认关闭
*/
boolean recordFailMetrics() default false;
/**
* 出现异常后忽略异常返回默认值,默认关闭
*/
boolean ignoreException() default false;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}

View File

@@ -0,0 +1,399 @@
package ai.remi.comm.log.aspect;
import ai.remi.comm.core.spring.SpringProperties;
import ai.remi.comm.log.annotation.LogRecord;
import ai.remi.comm.log.domain.OperateLog;
import ai.remi.comm.log.enums.BusinessStatus;
import ai.remi.comm.log.filter.PropertyPreExcludeFilter;
import ai.remi.comm.log.service.AsyncLogService;
import ai.remi.comm.redis.service.RedisService;
import ai.remi.comm.util.ip.IpAddrUtils;
import ai.remi.comm.util.ip.IpInfoUtils;
import ai.remi.comm.util.string.StringUtils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import ai.remi.comm.log.enums.RedisKeyEnum;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
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.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 操作日志记录处理
*/
@Slf4j
@Aspect
public class LogRecordAspect {
/**
* 排除敏感属性字段
*/
public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
private final ThreadLocal<Map<String, Object>> INFO_THREADLOCAL = new ThreadLocal<Map<String, Object>>();
/**
* 计算操作消耗时间
*/
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<>("Cost Time");
@Resource
private AsyncLogService asyncLogService;
@Resource
private RedisService redisService;
/**
* 处理请求前执行
*/
@Before(value = "@annotation(logRecord)")
public void boBefore(JoinPoint joinPoint, LogRecord logRecord) {
TIME_THREADLOCAL.set(System.currentTimeMillis());
//登出接口需要前置储存用户信息
handleLog(joinPoint, logRecord);
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(logRecord)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, LogRecord logRecord, Object jsonResult) {
handleLog(joinPoint, logRecord, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(logRecord)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, LogRecord logRecord, Exception e) {
handleLog(joinPoint, logRecord, e, null);
}
protected void handleLog(final JoinPoint joinPoint, LogRecord logRecord) {
// 传递线程信息
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(requestAttributes, true);
HttpServletRequest request = requestAttributes.getRequest();
// 记录请求内容,threadInfo存储所有内容
Map<String, Object> threadInfo = new HashMap<>();
//该方法用于获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等信息,但不包括后面的查询参数部分。
// 注意,getRequestRUL()方法返回的时StringBuffer类型,而不是String类型。
threadInfo.put("url", request.getRequestURL());
//该方法用于获取请求行中资源名称部分,即位于URL的主机和端口之后、参数部分之前的部分
threadInfo.put("uri", request.getRequestURI());
//该方法用于获取HTTP请求消息中的请求方式(如GET、POST等)
threadInfo.put("httpMethod", request.getMethod());
//该方法用于获取请求的协议名,例如http、https或ftp
threadInfo.put("scheme", request.getScheme());
//该方法用于获取请求客户端的IP地址,其格式类似于"192.168.0.1"
//threadInfo.put("ip", request.getRemoteAddr());
threadInfo.put("ip", IpAddrUtils.getIpAddr(request));
//检查是否为内网IP
String ipAddress = IpAddrUtils.internalIp(IpAddrUtils.getIpAddr(request)) ? "内网IP" : IpInfoUtils.getInfo(IpAddrUtils.getIpAddr(request)).getProvince() + "-" + IpInfoUtils.getInfo(IpAddrUtils.getIpAddr(request)).getCity();
threadInfo.put("ipAddress", ipAddress);
//该方法用于获取Content-Type头字段的值,结果为String类型
threadInfo.put("contentType", request.getContentType());
//包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。
//UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
threadInfo.put("platform", userAgent.getOperatingSystem().getName());
threadInfo.put("browser", userAgent.getBrowser().getName());
//从请求头获取操作人
threadInfo.put("operator", request.getHeader("X-Unique-Id"));
//从请求头获取部门租户
threadInfo.put("deptTenantId", request.getHeader("X-Dept-Tenant-Id"));
//从请求头获取公司租户
threadInfo.put("companyTenantId", request.getHeader("X-Company-Tenant-Id"));
//从请求头获取操作人
String accessToken = request.getHeader("X-Access-Token");
threadInfo.put("token", accessToken);
threadInfo.put("userId", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "id"));
threadInfo.put("userCode", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userCode"));
threadInfo.put("userName", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userName"));
threadInfo.put("userNameEn", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userNameEn"));
//从请求头获取系统语言
threadInfo.put("language", request.getHeader("X-USER-LANGUAGE"));
//从请求头获取业务流水号
threadInfo.put("businessNo", request.getHeader("X-Business-No"));
INFO_THREADLOCAL.set(threadInfo);
}
protected void handleLog(final JoinPoint joinPoint, LogRecord logRecord, final Exception e, Object jsonResult) {
try {
// *========解析请求信息=========*//
OperateLog operateLog = new OperateLog();
// *========获取注解信息=========*//
//获取方法签名
//MethodSignature signature = getSignature(joinPoint);
//获取注解方法
Method method = getMethod(joinPoint);
//获取注解对象
//LogRecord logRecord = getAnnotation(joinPoint, LogRecord.class);
// 获取连接点所在类名
String classname = joinPoint.getTarget().getClass().getSimpleName();
// 获取连接点所在方法名称
String methodName = method.getName();
String name = String.format("%s.%s()", classname, methodName);
// 保存请求方法
operateLog.setRequestMethod(name);
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, logRecord, operateLog, jsonResult);
// 是否需要保存request,参数和值
if (logRecord.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中
setRequestValue(joinPoint, operateLog, logRecord.excludeParamNames());
}
// 是否需要保存response,参数和值
if (logRecord.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
String result = JSON.toJSONString(jsonResult);
// 对登录日志的特殊处理
if (operateLog.getBusinessType()== 9) {
Object data = JSON.parseObject(result).get("data");
String accessToken = String.valueOf(JSON.parseObject(JSON.toJSONString(data)).get("accessToken"));
setOperateUser(accessToken, operateLog);
}
operateLog.setStatus(String.valueOf(JSON.parseObject(result).get("code")));
operateLog.setResponseBody(result);
}
//请求的成功或失败
if (e != null) {
operateLog.setStatus(BusinessStatus.FAIL.getCode());
// 是否需要保存errorMsg,参数和值
if (logRecord.isSaveErrorInfo()) {
operateLog.setStatus("111111");
operateLog.setErrorInfo(StrUtil.sub(e.getMessage(), 0, 5000));
}
}
// 保存数据库
//asyncLogService.saveOperateLog(operateLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
} finally {
TIME_THREADLOCAL.remove();
INFO_THREADLOCAL.remove();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param logRecord 日志切面
* @param operateLog 操作日志
*/
public void getControllerMethodDescription(JoinPoint joinPoint, LogRecord logRecord, OperateLog operateLog, Object jsonResult) throws Exception {
Map<String, Object> objectMap = INFO_THREADLOCAL.get();
// 业务模块
operateLog.setAppId(SpringProperties.getString("log.record.appId"));
operateLog.setAppCode(SpringProperties.getString("log.record.appCode"));
// 操作方法名称或描述
operateLog.setContent(logRecord.content());
// 业务操作类型
operateLog.setBusinessType(logRecord.businessType().getCode());
// 操作人类别
operateLog.setOperatorType(logRecord.operatorType().getCode());
// 操作人IP
operateLog.setUserIp(String.valueOf(objectMap.get("ip")));
operateLog.setIpAddress(String.valueOf(objectMap.get("ipAddress")));
// 操作人Id
//operateLog.setUserId(Objects.nonNull(objectMap.get("operator")) ? String.valueOf(objectMap.get("operator")) : null);
// 操作人信息
operateLog.setUserId(Objects.nonNull(objectMap.get("userId")) ? String.valueOf(objectMap.get("userId")) : null);
operateLog.setUserCode(Objects.nonNull(objectMap.get("userCode")) ? String.valueOf(objectMap.get("userCode")) : null);
operateLog.setUserName(Objects.nonNull(objectMap.get("userName")) ? String.valueOf(objectMap.get("userName")) : null);
operateLog.setUserNameEn(Objects.nonNull(objectMap.get("userNameEn")) ? String.valueOf(objectMap.get("userNameEn")) : null);
// 用户系统语言
operateLog.setUserLanguage(Objects.nonNull(objectMap.get("language")) ? String.valueOf(objectMap.get("language")) : null);
// 请求地址
operateLog.setRequestUrl(String.valueOf(objectMap.get("url")));
// 请求类型
operateLog.setRequestType(String.valueOf(objectMap.get("httpMethod")));
// 请求报文类型
operateLog.setContentType(Objects.nonNull(objectMap.get("contentType")) ? String.valueOf(objectMap.get("contentType")) : null);
// 业务流水号
operateLog.setBusinessNo(Objects.nonNull(objectMap.get("businessNo")) ? String.valueOf(objectMap.get("businessNo")) : null);
// 链路追踪ID
//logRecordDTO.setTraceId();
// 浏览器、操作系统、UI引擎标识
operateLog.setPlatform(String.valueOf(objectMap.get("platform")));
operateLog.setBrowser(String.valueOf(objectMap.get("browser")));
// 部门租户
operateLog.setDeptTenantId(String.valueOf(objectMap.get("deptTenantId")));
// 公司租户
operateLog.setCompanyTenantId(String.valueOf(objectMap.get("companyTenantId")));
// 设置消耗时间
operateLog.setRequestTime(LocalDateTime.ofEpochSecond(TIME_THREADLOCAL.get() / 1000, 0, ZoneOffset.ofHours(8)));
operateLog.setResponseTime(LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.ofHours(8)));
operateLog.setExecuteTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
}
/**
* 保存操作人信息
*
* @param accessToken
* @param operateLog
*/
private void setOperateUser(String accessToken, OperateLog operateLog) {
if (StringUtils.isNotBlank(accessToken)) {
String id = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "id"));
operateLog.setUserId(id);
String userCode = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userCode"));
operateLog.setUserCode(userCode);
String userName = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userName"));
operateLog.setUserName(userName);
String userNameEn = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userNameEn"));
operateLog.setUserNameEn(userNameEn);
}
}
/**
* 获取请求的参数,放到log中
*
* @param operateLog 操作日志
* @param excludeParamNames 排除指定的请求参数
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, OperateLog operateLog, String[] excludeParamNames) throws Exception {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 获取请求类型
// String requestType = operateLog.getRequestType();
String requestType = request.getMethod();
// 获取请求参数
Map<String, String> paramsMap = converMap(request.getParameterMap());
// 组装请求参数
if (MapUtils.isEmpty(paramsMap) && (HttpMethod.PUT.name().equals(requestType) || HttpMethod.POST.name().equals(requestType))) {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operateLog.setRequestBody(params);
} else {
operateLog.setRequestBody(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)));
}
}
/**
* 请求参数 转换
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> returnMap = new HashMap<>();
for (String key : paramMap.keySet()) {
returnMap.put(key, paramMap.get(key)[0]);
}
return returnMap;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
StringBuilder params = new StringBuilder();
if (ArrayUtil.isNotEmpty(paramsArray)) {
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
try {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params.append(jsonObj).append(StrUtil.SPACE);
} catch (Exception ignored) {
}
}
}
}
return params.toString().trim();
}
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) {
return new PropertyPreExcludeFilter().addExcludes(ArrayUtil.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}
/**
* 判断是否需要过滤的对象
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
private MethodSignature getSignature(JoinPoint joinPoint) {
//通过连接点获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature;
}
private Method getMethod(JoinPoint joinPoint) {
//通过连接点获取方法签名
MethodSignature signature = getSignature(joinPoint);
//获取注解服务的方法
Method method = signature.getMethod();
return method;
}
private <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
Method method = getMethod(joinPoint);
//获取注解类
T annotation = method.getAnnotation(annotationClass);
return annotation;
}
}

View File

@@ -0,0 +1,56 @@
package ai.remi.comm.log.config;
import ai.remi.comm.log.aspect.LogRecordAspect;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc
*/
@Slf4j
@RefreshScope
@Configuration
public class LogRecordAutoConfigure {
/**
* 是否开启监控配置参数
*/
@Value("${remi-log.record.enabled}")
private Boolean enabled;
/**
* 日志监控的应用名称
*/
@Value("${remi-log.record.appName}")
private String appName;
/**
* 在方法成功执行后打点,记录方法的执行时间发送到指标系统,默认关闭
* 在方法成功失败后打点,记录方法的执行时间发送到指标系统,默认关闭
*/
@Value("${remi-log.record.pushUrl}")
private String pushUrl;
/**
* 根据运行环境决定是否开启日志记录监控
*/
@PostConstruct
protected LogRecordAspect init() {
if(!enabled){
return null;
}
if (enabled && StringUtils.isBlank(appName)) {
log.error("Please config the remi-log.record property in application.yml file to enabled log record, but appName is null.");
throw new RuntimeException("Please config the remi-log.record property in application.yml file to config log record appName.");
}
return new LogRecordAspect();
}
}

Some files were not shown because too many files have changed in this diff Show More