zhiqiang.yu 2 hafta önce
işleme
8e2aa48f9f
100 değiştirilmiş dosya ile 9961 ekleme ve 0 silme
  1. 4 0
      .gitignore
  2. 40 0
      README.md
  3. 95 0
      pom.xml
  4. 171 0
      py-base/DB.md
  5. 74 0
      py-base/README.md
  6. 718 0
      py-base/base.md
  7. 192 0
      py-base/easy.md
  8. 346 0
      py-base/i18n.md
  9. 289 0
      py-base/pom.xml
  10. 593 0
      py-base/provider.md
  11. 47 0
      py-base/src/main/java/com/poyee/annotation/ApiAuthenticationToken.java
  12. 14 0
      py-base/src/main/java/com/poyee/annotation/CheckDataNull.java
  13. 21 0
      py-base/src/main/java/com/poyee/annotation/Desensit.java
  14. 16 0
      py-base/src/main/java/com/poyee/annotation/DesensitMethod.java
  15. 13 0
      py-base/src/main/java/com/poyee/annotation/DictTypeFormat.java
  16. 14 0
      py-base/src/main/java/com/poyee/annotation/ExcelI18nFromat.java
  17. 38 0
      py-base/src/main/java/com/poyee/annotation/I18n.java
  18. 34 0
      py-base/src/main/java/com/poyee/annotation/I18nToken.java
  19. 19 0
      py-base/src/main/java/com/poyee/annotation/JsonMapper.java
  20. 37 0
      py-base/src/main/java/com/poyee/annotation/Log.java
  21. 51 0
      py-base/src/main/java/com/poyee/annotation/MpjWapper.java
  22. 13 0
      py-base/src/main/java/com/poyee/annotation/PassToken.java
  23. 24 0
      py-base/src/main/java/com/poyee/annotation/Property.java
  24. 13 0
      py-base/src/main/java/com/poyee/annotation/ReadConverterExpFormat.java
  25. 60 0
      py-base/src/main/java/com/poyee/annotation/RedisCache.java
  26. 25 0
      py-base/src/main/java/com/poyee/annotation/RedisLock.java
  27. 36 0
      py-base/src/main/java/com/poyee/annotation/UserLoginToken.java
  28. 23 0
      py-base/src/main/java/com/poyee/annotation/db/CaseWhen.java
  29. 20 0
      py-base/src/main/java/com/poyee/annotation/db/Count.java
  30. 13 0
      py-base/src/main/java/com/poyee/annotation/db/Distinct.java
  31. 18 0
      py-base/src/main/java/com/poyee/annotation/db/Join.java
  32. 21 0
      py-base/src/main/java/com/poyee/annotation/db/LeftJoin.java
  33. 15 0
      py-base/src/main/java/com/poyee/annotation/db/OrderBy.java
  34. 19 0
      py-base/src/main/java/com/poyee/annotation/db/RightJoin.java
  35. 23 0
      py-base/src/main/java/com/poyee/annotation/db/Select.java
  36. 17 0
      py-base/src/main/java/com/poyee/annotation/db/Sum.java
  37. 88 0
      py-base/src/main/java/com/poyee/annotation/db/Where.java
  38. 30 0
      py-base/src/main/java/com/poyee/annotation/ds/DataSource.java
  39. 28 0
      py-base/src/main/java/com/poyee/annotation/ds/Transactional.java
  40. 35 0
      py-base/src/main/java/com/poyee/annotation/i18nSource.java
  41. 154 0
      py-base/src/main/java/com/poyee/aspectj/ApiAuthenticationTokenAspect.java
  42. 111 0
      py-base/src/main/java/com/poyee/aspectj/DataSourceAspect.java
  43. 126 0
      py-base/src/main/java/com/poyee/aspectj/DesensitAspect.java
  44. 134 0
      py-base/src/main/java/com/poyee/aspectj/I18nTokenAspect.java
  45. 207 0
      py-base/src/main/java/com/poyee/aspectj/LogAspect.java
  46. 38 0
      py-base/src/main/java/com/poyee/aspectj/MqConsumerAOP.java
  47. 305 0
      py-base/src/main/java/com/poyee/aspectj/RedisCacheAspect.java
  48. 211 0
      py-base/src/main/java/com/poyee/aspectj/RedisLockAspect.java
  49. 166 0
      py-base/src/main/java/com/poyee/aspectj/UserLoginTokenAspect.java
  50. 90 0
      py-base/src/main/java/com/poyee/aspectj/i18nAspect.java
  51. 15 0
      py-base/src/main/java/com/poyee/base/controller/ActuatorController.java
  52. 98 0
      py-base/src/main/java/com/poyee/base/controller/BaseController.java
  53. 154 0
      py-base/src/main/java/com/poyee/base/dto/AppBaseUser.java
  54. 19 0
      py-base/src/main/java/com/poyee/base/dto/BaseDto.java
  55. 71 0
      py-base/src/main/java/com/poyee/base/dto/BaseReq.java
  56. 28 0
      py-base/src/main/java/com/poyee/base/dto/MpjWrapper.java
  57. 91 0
      py-base/src/main/java/com/poyee/base/dto/Page.java
  58. 176 0
      py-base/src/main/java/com/poyee/base/dto/Result.java
  59. 73 0
      py-base/src/main/java/com/poyee/base/dto/UserInfo.java
  60. 43 0
      py-base/src/main/java/com/poyee/base/entity/BaseEntity.java
  61. 14 0
      py-base/src/main/java/com/poyee/base/mapper/IBaseMapper.java
  62. 201 0
      py-base/src/main/java/com/poyee/base/mapper/provider/BaseProvider.java
  63. 29 0
      py-base/src/main/java/com/poyee/base/mapper/provider/ErrMessageContent.java
  64. 151 0
      py-base/src/main/java/com/poyee/base/mapper/provider/IBaseProvider.java
  65. 27 0
      py-base/src/main/java/com/poyee/base/mapper/provider/domain/LeftJoinInfo.java
  66. 120 0
      py-base/src/main/java/com/poyee/base/mapper/provider/domain/MPage.java
  67. 16 0
      py-base/src/main/java/com/poyee/base/mapper/provider/domain/OrderByInfo.java
  68. 24 0
      py-base/src/main/java/com/poyee/base/mapper/provider/domain/TableInfo.java
  69. 99 0
      py-base/src/main/java/com/poyee/base/mapper/provider/domain/WhereInfo.java
  70. 154 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/FormatValueUtil.java
  71. 224 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/InsertUtil.java
  72. 76 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/JoinUtil.java
  73. 363 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/SelectUtil.java
  74. 638 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/SqlUtil.java
  75. 63 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/TableInfoUtil.java
  76. 167 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/UpdateUtil.java
  77. 184 0
      py-base/src/main/java/com/poyee/base/mapper/provider/util/WhereUtil.java
  78. 14 0
      py-base/src/main/java/com/poyee/base/service/BaseService.java
  79. 295 0
      py-base/src/main/java/com/poyee/base/service/impl/BaseServiceImpl.java
  80. 13 0
      py-base/src/main/java/com/poyee/common/CanonicalHostNamePropertyDefiner.java
  81. 64 0
      py-base/src/main/java/com/poyee/common/Result.java
  82. 97 0
      py-base/src/main/java/com/poyee/common/auth/UserRoleUtil.java
  83. 18 0
      py-base/src/main/java/com/poyee/common/enums/BusinessStatus.java
  84. 68 0
      py-base/src/main/java/com/poyee/common/enums/BusinessType.java
  85. 19 0
      py-base/src/main/java/com/poyee/common/enums/CheckAuthTypeEnums.java
  86. 23 0
      py-base/src/main/java/com/poyee/common/enums/DataAuth.java
  87. 12 0
      py-base/src/main/java/com/poyee/common/enums/DesensitType.java
  88. 25 0
      py-base/src/main/java/com/poyee/common/enums/I18nFormat.java
  89. 69 0
      py-base/src/main/java/com/poyee/common/enums/Language.java
  90. 28 0
      py-base/src/main/java/com/poyee/common/enums/OperatorType.java
  91. 70 0
      py-base/src/main/java/com/poyee/common/enums/Roles.java
  92. 39 0
      py-base/src/main/java/com/poyee/common/exception/AuthException.java
  93. 152 0
      py-base/src/main/java/com/poyee/common/exception/BaseException.java
  94. 68 0
      py-base/src/main/java/com/poyee/common/exception/BusinessException.java
  95. 64 0
      py-base/src/main/java/com/poyee/common/exception/DataException.java
  96. 146 0
      py-base/src/main/java/com/poyee/common/exception/GlobalExceptionHandler.java
  97. 40 0
      py-base/src/main/java/com/poyee/common/exception/ServiceException.java
  98. 66 0
      py-base/src/main/java/com/poyee/common/knife4j/Knife4jConfig.java
  99. 29 0
      py-base/src/main/java/com/poyee/common/swagger/ArrayRefProperty.java
  100. 335 0
      py-base/src/main/java/com/poyee/common/swagger/SwaggerConfig.java

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+*.class
+/.idea
+/py-base/target
+/py-starter/target

+ 40 - 0
README.md

@@ -0,0 +1,40 @@
+# 数据报表系统
+
+---------------------------
+### 说明
+```
+此服务支持 
+1.日报数据统计
+2.数据统计
+3.大屏数据
+4.数据可视化
+...
+后续支持更多功能
+此服务支持多数据源 根据不同数据源进行数据统计
+ ·[弃用] 接口请求参数 dbVersion 设置不同的数据源,在执行查询时,会自动根据该参数进行数据源切换
+```
+--------------------------
+## 运行环境
+```
+1.jdk 8
+2.postgresql 9.6
+  |- 项目插件 42.2.18
+  |- mybatis-plus
+3.springboot 2.3.5.RELEASE
+4.插件
+  |- knife4j
+  |- lombok
+```
+--------------------------
+## 结构
+
+> py-starter  启动模块
+> 
+> py-base  基础模块
+> - POYEE BASE - 基础插件 [README.md](py-base/README.md)
+> 
+> py-data 数据统计模块
+> 
+      
+### 接口文档
+> http://localhost:8080/doc.html </br>

+ 95 - 0
pom.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.5.RELEASE</version>
+    </parent>
+
+    <groupId>com.poyee</groupId>
+    <artifactId>poyee-dashboard</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <name>dashboard</name>
+    <description>大屏数据系统</description>
+
+    <properties>
+        <module.version>1.0-SNAPSHOT</module.version>
+        <java.version>1.8</java.version>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <springboot.version>2.3.5.RELEASE</springboot.version>
+        <druid.version>1.2.1</druid.version>
+        <commons.io.version>2.5</commons.io.version>
+        <fastjson.version>1.2.74</fastjson.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>${springboot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.12</version>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <!--base-->
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>py-base</artifactId>
+                <version>${module.version}</version>
+            </dependency>
+            <!--data-->
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>py-data</artifactId>
+                <version>${module.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>py-system</artifactId>
+                <version>${module.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>py-task</artifactId>
+                <version>${module.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>py-starter</module>
+        <module>py-base</module>
+        <module>py-data</module>
+        <module>py-system</module>
+        <module>py-task</module>
+    </modules>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 171 - 0
py-base/DB.md

@@ -0,0 +1,171 @@
+# DB注解包说明文档
+
+## 概述
+
+`com.poyee.annotation.db`包中包含了一系列用于数据库操作的注解,这些注解主要用于简化SQL查询构建、表关联、条件过滤、排序和聚合操作等。这些注解通常与MyBatis-Plus框架配合使用,用于增强其功能。
+
+## 注解列表
+
+### 1. Select
+
+**用途**:用于指定查询字段
+
+**属性**:
+- `table`:指定表类
+- `alias`:表别名,默认为空
+- `fieldName`:字段名,默认为空
+- `caseWhen`:聚合等条件取值,默认为空数组
+- `distinct`:是否去重,默认为false
+
+**示例**:
+```java
+@Select(table = UserEntity.class, fieldName = "username", distinct = true)
+private String username;
+```
+
+### 2. Join
+
+**用途**:用于外连接查询
+
+**属性**:
+- `table`:关联表
+- `on`:关联条件
+- `alias`:别名
+
+**示例**:
+```java
+@Join(table = "user_role", on = "user.id = user_role.user_id", alias = "ur")
+private String roleInfo;
+```
+
+### 3. LeftJoin
+
+**用途**:用于左连接查询
+
+**属性**:
+- `table`:关联表
+- `leftTable`:关联表(左表/主表),默认为void.class
+- `fieldName`:关联字段,默认为空
+- `leftFieldName`:左表关联字段,默认为空
+
+**示例**:
+```java
+@LeftJoin(table = RoleEntity.class, leftTable = UserEntity.class, fieldName = "id", leftFieldName = "roleId")
+private String roleName;
+```
+
+### 4. RightJoin
+
+**用途**:用于右连接查询
+
+**属性**:
+- `table`:表名
+- `on`:关联条件
+- `alias`:别名
+
+**示例**:
+```java
+@RightJoin(table = "user_role", on = "user.id = user_role.user_id", alias = "ur")
+private String roleInfo;
+```
+
+### 5. Where
+
+**用途**:用于指定查询条件
+
+**属性**:
+- `table`:表名/别名(二选一)
+- `alias`:别名/表名(二选一),默认为空
+- `field`:字段名(与orFields二选一),默认为空
+- `orFields`:字段名数组(与field二选一),默认为空数组
+- `columnType`:字段类型,默认为STRING
+- `operator`:操作符,默认为EQ(等于)
+- `isNull`:是否为空,默认为false
+- `isEnd`:是否为结束条件,默认为false
+
+**示例**:
+```java
+@Where(table = UserEntity.class, field = "status", operator = FieldOperator.EQ)
+private String status;
+```
+
+### 6. OrderBy
+
+**用途**:用于指定排序 使用到结果Dto 类的字段
+
+**属性**:
+- `sortType`:排序方式,默认为ASC(升序)
+- `index`:顺序,默认为1
+
+**示例**:
+```java
+@OrderBy(sortType = OrderBySortEnums.DESC, index = 2)
+private Date createTime;
+```
+
+### 7. Count
+
+**用途**:用于统计计数
+
+**属性**:
+- `distinct`:是否去重,默认为false
+- `caseWhen`:条件取值,默认为空数组
+
+**示例**:
+```java
+@Count(distinct = true)
+@TableField("user_id")
+private Long userCount;
+```
+
+### 8. Sum
+
+**用途**:用于求和
+
+**属性**:
+- `caseWhen`:条件取值,默认为空数组
+
+**示例**:
+```java
+@Sum
+@TableField("amount")
+private Double totalAmount;
+```
+
+### 9. CaseWhen
+
+**用途**:用于聚合等条件取值
+
+**属性**:
+- `when`:条件,默认为空
+- `then`:值,默认为空
+- `isElse`:是否是else,默认为false
+- `valueType`:值类型,默认为CONSTANT
+
+**示例**:
+```java
+@Count
+@TableField("status")
+private Long statusCount;
+
+@CaseWhen(when = "status = 1", then = "1", valueType = CaseWhenValueTypeEnums.CONSTANT)
+private Long activeCount;
+```
+
+## 使用场景
+
+这些注解主要用于以下场景:
+
+1. **复杂查询构建**:通过注解方式构建复杂的SQL查询,避免手写SQL
+2. **表关联**:使用Join、LeftJoin、RightJoin等注解实现表关联
+3. **条件过滤**:使用Where注解实现条件过滤
+4. **排序**:使用OrderBy注解实现结果排序
+5. **聚合操作**:使用Count、Sum等注解实现聚合操作
+6. **条件取值**:使用CaseWhen注解实现条件取值
+
+## 注意事项
+
+1. 这些注解通常需要与MyBatis-Plus框架配合使用
+2. 注解的使用需要遵循Java注解的规范
+3. 部分注解需要与其他注解(如TableField)一起使用才能生效
+4. 使用这些注解可以简化SQL构建,但也需要了解其底层实现原理,以便更好地使用

+ 74 - 0
py-base/README.md

@@ -0,0 +1,74 @@
+# POYEE BASE - 基础插件
+
+## 📋 项目简介
+
+> POYEE BASE 是一个提供基础功能和工具的核心插件库,为上层应用提供统一的底层支持。
+
+## 🌟 核心功能
+
+### 自定义注解 [`annotation`](src/main/java/com/poyee/annotation)
+- 数据库相关注解说明:[DB.md](DB.md)
+
+### 基础抽象方法 [`base`](src/main/java/com/poyee/base)
+> 说明:[base.md](base.md)
+- **控制层**:controller 抽象类
+- **业务层**:service 抽象类
+- **数据层**:mapper 抽象类
+  - mapper 接口
+  - provider 动态SQL工具
+    - [provider.md](provider.md) - 详细说明文档
+    - 根据实体类注解动态生成查询SQL,无需使用 mapper.xml 文件
+    - 工具内集成基础查询功能,复杂查询可自定义手写SQL
+    - 使用方式:mapper.java 继承 IBaseProvider
+    - 自定义SQL:业务模块在mapper包下创建provider包,创建*Provider类,编写自定义SQL逻辑
+    - 参考文档:[MyBatis Provider使用指南](https://blog.csdn.net/fmwind/article/details/81534357)
+- **数据模型**:entity、dto 实体类
+
+### 通用工具 [`common`](src/main/java/com/poyee/common)
+- 常量定义
+- 数据库生成工具插件
+- 业务异常处理工具 (exception)
+- JSON处理工具 (JsonMapperUtil)
+- API文档工具 (knife4j swagger2)
+
+### 配置模块 `config`
+- [自定义SQL注入器 (JoinSqlInjector)](src/main/java/com/poyee/config/JoinSqlInjector.java)
+- [Redis配置](src/main/java/com/poyee/config/RedisConfig.java)
+
+### 多数据源配置 [`dataSource`](src/main/java/com/poyee/datasource)
+
+### Excel处理工具 [`easy`](src/main/java/com/poyee/easy)
+> 说明:[easy.md](easy.md)
+
+### 枚举定义 [`enums`](src/main/java/com/poyee/enums)
+
+### 事件处理 [`handler`](src/main/java/com/poyee/handler)
+- Excel处理:
+  - [数据格式处理器 (EasyDataFormatHandler)](src/main/java/com/poyee/handler/EasyDataFormatHandler.java)
+  - [分页查询监听器 (PageReadListenerHandler)](src/main/java/com/poyee/handler/PageReadListenerHandler.java)
+- 消息队列:[`mq`](src/main/java/com/poyee/mq)
+- 缓存工具:[`redis`](src/main/java/com/poyee/redis)
+- 通用工具类:[`util`](src/main/java/com/poyee/util)
+
+### 国际化支持 [`i18n`](src/main/java/com/poyee/i18n)
+> 说明:[i18n.md](i18n.md)
+- [多语言枚举 (I18nMessageEnums)](src/main/java/com/poyee/i18n/I18nMessageEnums.java)
+- [多语言工具类 (I18nUtils)](src/main/java/com/poyee/i18n/I18nUtils.java)
+- [多语言配置 (MessageProperties)](src/main/java/com/poyee/i18n/MessageProperties.java)
+- [多语言配置文件 (message.yml)](src/main/resources/i18n/message.yml)
+
+## 🛠️ 开发环境
+
+<!-- 此处可添加开发环境相关信息,如JDK版本、构建工具等 -->
+
+## 🔧 技术架构
+
+<!-- 此处可添加技术架构相关信息,如框架版本、组件关系等 -->
+
+## 📚 使用指南
+
+<!-- 此处可添加项目使用说明、示例代码等 -->
+
+## 🔄 版本历史
+
+<!-- 此处可添加版本更新记录 -->

+ 718 - 0
py-base/base.md

@@ -0,0 +1,718 @@
+# Poyee Base 框架说明文档
+
+## 1. 概述
+
+`com.poyee.base` 包是 Poyee Checklist 项目的核心基础框架,提供了一套完整的分层架构实现,包括控制层(Controller)、服务层(Service)、数据访问层(Mapper)以及相关的数据传输对象(DTO)、请求对象(Req)和实体对象(Entity)。该框架采用了面向对象的设计思想,通过抽象基类和泛型机制,实现了代码复用和业务逻辑的统一管理。
+
+## 2. 核心组件
+
+### 2.1 基础架构
+
+#### 2.1.1 控制层(Controller)
+
+**BaseController**
+
+`BaseController` 是所有控制器的基类,提供了通用的请求处理和日志记录功能。
+
+```java
+public abstract class BaseController<S extends BaseService<T, R>, T extends BaseReq, R extends BaseDto> {
+    @Autowired
+    protected S baseService;
+    
+    // 日志记录
+    public void console(String label, Object obj) { ... }
+    
+    // 分页查询
+    public Result<R> listPage(T req, boolean checkUser) { ... }
+    
+    // 详情查询
+    public Result<R> info(Integer id) { ... }
+    
+    // 获取请求对象
+    public HttpServletRequest getRequest() { ... }
+}
+```
+
+#### 2.1.2 服务层(Service)
+
+**BaseService 接口**
+
+`BaseService` 接口定义了服务层的通用方法,包括查询、分页、计数等操作。
+
+```java
+public interface BaseService<T extends BaseReq, R extends BaseDto> extends IService<R> {
+    Result<R> listPage(T req, boolean checkUser);
+    List<R> selectJoinAllList(MPJLambdaWrapper<R> wrapper, T req);
+    QueryWrapper<R> checkWrapper(T req, boolean checkUser);
+    Long selectCount(MPJLambdaWrapper<R> wrapper);
+    List<R> getAllList(T req, boolean checkUser);
+    Result<R> info(Integer id);
+    Result<R> getOne(T req);
+    Result<R> getOneDto(T req);
+    UserInfo getUserInfo();
+    <Q extends BaseReq> IPage<R> ipage(MPJLambdaWrapper<R> wrapper, Q req);
+    <Q extends BaseReq> Result<R> ipageResult(MPJLambdaWrapper<R> wrapper, Q req);
+    MPJLambdaWrapper<R> mpjWrapper(Object req);
+    MPJLambdaWrapper<R> mpjWrapper(Object req, String alias);
+    MPJLambdaWrapper<R> mpjWrapper(MPJLambdaWrapper<R> mpjLambdaWrapper, Object req, String alias);
+}
+```
+
+**BaseServiceImpl 实现类**
+
+`BaseServiceImpl` 是 `BaseService` 接口的实现类,提供了各种通用方法的具体实现。
+
+```java
+public class BaseServiceImpl<M extends IBaseMapper<R>, T extends BaseReq, R extends BaseDto> 
+        extends ServiceImpl<M, R> implements BaseService<T, R> {
+    
+    // 检查用户权限
+    public <P extends BaseReq> void checkAndSetUserId(P p) { ... }
+    
+    // 分页查询
+    @Override
+    public Result<R> listPage(T req, boolean checkUser) { ... }
+    
+    // 获取单条数据
+    @Override
+    public Result<R> info(Integer id) { ... }
+    
+    // 构建查询条件
+    @Override
+    public QueryWrapper<R> checkWrapper(T req, boolean checkUser) { ... }
+    
+    // 构建MPJ查询条件
+    @Override
+    public MPJLambdaWrapper<R> mpjWrapper(Object req) { ... }
+}
+```
+
+#### 2.1.3 数据访问层(Mapper)
+
+**IBaseMapper 接口**
+
+`IBaseMapper` 接口继承了 MyBatis-Plus 的 `BaseMapper` 和 `MPJBaseMapper`,提供了基础的数据库操作方法。
+
+```java
+public interface IBaseMapper<T> extends BaseMapper<T>, MPJBaseMapper<T> {
+    // 基础方法由 BaseMapper 和 MPJBaseMapper 提供
+}
+```
+
+**IBaseProvider 接口**
+
+`IBaseProvider` 接口定义了动态 SQL 构建的方法,用于复杂查询场景。
+
+```java
+public interface IBaseProvider<T extends BaseReq, R extends BaseDto> {
+    List<Map<String, Object>> selectListMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+    long pageCount(String sql);
+    List<Map<String, Object>> page(String sql);
+    Map<String, Object> selectOneMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+    Map<String, Object> selectByIdMap(@Param("id") Serializable id, @Param("clazz") Class<R> clazz);
+    Map<String, Object> selectSumMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+    int save(T req);
+    int update(T req);
+}
+```
+
+**BaseProvider 实现类**
+
+`BaseProvider` 是 `IBaseProvider` 接口的抽象实现,提供了 SQL 构建的核心功能。
+
+```java
+public abstract class BaseProvider {
+    // 数据库操作类型
+    public DbMethodEnums dbMethod;
+    // 表计数器
+    public int tableCount = 0;
+    // 是否分页
+    public boolean isPage = false;
+    // 存储 insert
+    public Object insert;
+    // 记录 UPDATE
+    public Object update;
+    // 记录 SELECT
+    public Object select;
+    // 主表信息
+    public TableInfo superTable;
+    // 分页查询
+    public MPage mPage;
+    // 标记是否前端传排序字段
+    public boolean isFrontSort = false;
+    // 分页查询
+    public String pageCountSql;
+    // 记录 INSERT key-value(线程安全)
+    public final Map<String, Object> insertMap = new HashMap<>();
+    // 记录 UPDATE key-value(线程安全)
+    public final Map<String, Object> updateMap = new HashMap<>();
+    // 存储表信息的映射(线程安全)
+    public final Map<String, TableInfo> tableInfos = new ConcurrentHashMap<>();
+    // 记录 WHERE 条件(线程安全)
+    public final List<WhereInfo> whereInfos = new CopyOnWriteArrayList<>();
+    // 记录 SELECT 列(线程安全)
+    public final List<String> selectColumns = new CopyOnWriteArrayList<>();
+    // 记录 LEFT JOIN 信息(线程安全)
+    public final List<LeftJoinInfo> leftJoinInfos = new CopyOnWriteArrayList<>();
+    // 记录 ORDER BY 信息(线程安全)
+    // ...
+}
+```
+
+### 2.2 数据对象
+
+#### 2.2.1 基础请求对象(BaseReq)
+
+`BaseReq` 是所有请求对象的基类,提供了分页、排序等通用参数。
+
+```java
+@Data
+public class BaseReq {
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    public static List<String> types = Arrays.asList("pageNo", "pageSize", "sidx", "sord", "orderBy", "limit", "desensit");
+    
+    @ApiModelProperty(value = "页码,查询时使用", notes = "查询时使用", reference = "1", example = "1")
+    @TableField(exist = false)
+    private Integer pageNo = 1;
+    
+    @ApiModelProperty(value = "每页数量,查询时使用", notes = "查询时使用", reference = "10", example = "10")
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+    
+    @ApiModelProperty(value = "排序字段,查询时使用", notes = "查询时使用", reference = " createTime ")
+    @TableField(exist = false)
+    private String sidx;
+    
+    @ApiModelProperty(value = "排序规则,查询时使用", notes = "查询时使用", reference = " ase ")
+    @TableField(exist = false)
+    private String sord;
+    
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private String orderBy;
+    
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private Integer limit;
+    
+    public Integer getLimit() {
+        return (pageNo - 1) * pageSize;
+    }
+}
+```
+
+#### 2.2.2 基础数据传输对象(BaseDto)
+
+`BaseDto` 是所有数据传输对象的基类,通常包含实体的基本属性和一些扩展属性。
+
+```java
+@Data
+public class BaseDto {
+    // 基础属性
+}
+```
+
+#### 2.2.3 基础实体对象(BaseEntity)
+
+`BaseEntity` 是所有实体对象的基类,提供了分页、排序等通用参数。
+
+```java
+@Data
+public class BaseEntity {
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    public static List<String> types = Arrays.asList("pageNo", "pageSize", "sidx", "sord", "orderBy", "limit", "desensit");
+    
+    @ApiModelProperty(value = "页码,查询时使用", notes = "查询时使用", reference = "1")
+    @TableField(exist = false)
+    private Integer pageNo = 1;
+    
+    @ApiModelProperty(value = "每页数量,查询时使用", notes = "查询时使用", reference = "10")
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+    
+    @ApiModelProperty(value = "排序字段,查询时使用", notes = "查询时使用", reference = " createTime ")
+    @TableField(exist = false)
+    private String sidx;
+    
+    @ApiModelProperty(value = "排序规则,查询时使用", notes = "查询时使用", reference = " ase ")
+    @TableField(exist = false)
+    private String sord;
+    
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private String orderBy;
+    
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private Integer limit;
+    
+    public static boolean notIn(String type) {
+        return !types.contains(type);
+    }
+    
+    public Integer getLimit() {
+        return (pageNo - 1) * pageSize;
+    }
+}
+```
+
+#### 2.2.4 结果对象(Result)
+
+`Result` 是统一的响应结果对象,用于封装接口返回数据。
+
+```java
+@Data
+@ApiModel("通用返回结果")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Result<T> implements Serializable {
+    @ApiModelProperty("是否成功")
+    private boolean success;
+    
+    @ApiModelProperty("状态码")
+    private Integer code;
+    
+    @ApiModelProperty("返回消息")
+    private String msg;
+    
+    @ApiModelProperty("返回数据")
+    private T data;
+    
+    @ApiModelProperty("总记录数")
+    private Long total;
+    
+    // 静态工厂方法
+    public static Result ok() { ... }
+    public static <R extends BaseDto> Result<R> ok(R data) { ... }
+    public static Result error(String msg) { ... }
+    public static Result error(Integer code, String msg) { ... }
+    public static <R extends BaseDto> Result<R> page(IPage<R> iPage) { ... }
+    // ...
+}
+```
+
+#### 2.2.5 用户信息对象(UserInfo)
+
+`UserInfo` 封装了当前登录用户的信息,用于权限控制和数据过滤。
+
+```java
+@Data
+@ToString
+public class UserInfo implements Serializable {
+    // 用户ID
+    private Integer id;
+    // 用户ID
+    private String userId;
+    // 用户头像
+    private String avatar;
+    // 角色编码
+    private String roleCode;
+    // 角色编码
+    private String role;
+    // 请求时间
+    private Long iat;
+    // 过期时间
+    private Long exp;
+    // 名称
+    private String displayName;
+    // ...
+    
+    // 商家ID
+    private Integer merchantId;
+    // 商家头像
+    private String merchantAvatar;
+    // 商家名称
+    private String merchantName;
+    // 数据权限
+    private DataAuth dataAuth;
+    // 是否是商家
+    private boolean merchant;
+    // ...
+    
+    // 判断是否有合作伙伴角色权限
+    public boolean hasPartnerRoleAuth(Roles role) { ... }
+}
+```
+
+### 2.3 工具类和注解
+
+#### 2.3.1 国际化支持
+
+**i18n 注解**
+
+`i18n` 注解用于标记需要国际化的字段、方法或类。
+
+```java
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface i18n {
+    boolean value() default true;
+    String sheetName() default "";
+    I18nFormat[] format() default {};
+}
+```
+
+**I18nUtils 工具类**
+
+`I18nUtils` 提供了获取国际化消息的方法。
+
+```java
+@Configuration
+public class I18nUtils {
+    // 定义占位符的正则表达式模式
+    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\d+)\\}");
+    
+    private MessageProperties messageProperties;
+    
+    // 存储消息的Map,键是消息键,值是包含语言和消息的Map
+    private static Map<String, Map<String, String>> messages;
+    
+    // 初始化国际化工具类,并加载消息属性
+    @Autowired
+    public I18nUtils(MessageProperties messageProperties) { ... }
+    
+    // 根据枚举和参数获取国际化消息
+    public static String get(I18nMessageEnums i18n, Object... args) { ... }
+    
+    // ...
+}
+```
+
+**I18nMessageEnums 枚举**
+
+`I18nMessageEnums` 定义了系统中所有的国际化消息。
+
+```java
+@Getter
+public enum I18nMessageEnums {
+    // 请求成功
+    SUCCESS("success", "成功", "zh"),
+    // 请求失败
+    REQUEST_ERROR("request_error", "请求失败", "zh"),
+    // 无权操作
+    NO_PERMISSION("no_permission", "无权操作{0}", "zh"),
+    // ...
+    
+    private String code; // 多语言标识
+    private String message; // 默认信息
+    private String lang; // 默认语言
+    
+    I18nMessageEnums(String code, String message, String lang) { ... }
+}
+```
+
+#### 2.3.2 Servlet 工具类
+
+`ServletUtils` 提供了获取请求、响应、会话以及用户信息的方法。
+
+```java
+public class ServletUtils {
+    // 获取请求对象
+    public static HttpServletRequest getRequest() { ... }
+    
+    // 获取响应对象
+    public static HttpServletResponse getResponse() { ... }
+    
+    // 获取会话对象
+    public static HttpSession getSession() { ... }
+    
+    // 获取用户信息
+    public static UserInfo getUserInfo() { ... }
+    
+    // 判断是否国际版
+    public static boolean isI18n() { ... }
+    
+    // 判断接口是否支持国际版
+    public static boolean isI18nSupport() { ... }
+    
+    // ...
+}
+```
+
+#### 2.3.3 数据库注解
+
+**Select 注解**
+
+`Select` 注解用于指定查询字段。
+
+```java
+@Select(table = UserEntity.class, fieldName = "username", distinct = true)
+private String username;
+```
+
+**Where 注解**
+
+`Where` 注解用于指定查询条件。
+
+```java
+@Where(fieldName = "status", operator = EQ)
+private String status;
+```
+
+**LeftJoin 注解**
+
+`LeftJoin` 注解用于指定左连接查询。
+
+```java
+@LeftJoin(table = RoleEntity.class, leftTable = UserEntity.class, fieldName = "id", leftFieldName = "roleId")
+private String roleName;
+```
+
+**OrderBy 注解**
+
+`OrderBy` 注解用于指定排序字段。
+
+```java
+@OrderBy(fieldName = "createTime", sort = "DESC")
+private String createTime;
+```
+
+## 3. 使用示例
+
+### 3.1 创建控制器
+
+```java
+@Slf4j
+@Api(value = "球队基础信息管理", tags = "球队基础信息管理")
+@RestController
+@RequestMapping("/api/v1/teamInfo")
+public class TeamBaseInfoController extends BaseController<TeamBaseInfoService, TeamBaseInfoReq, TeamBaseInfoDto> {
+    
+    @ApiOperation(value = "详情", notes = "详情")
+    @ApiResponses({
+        @ApiResponse(code = 200, message = "返回结果:")
+    })
+    @Log(title = "详情", businessType = BusinessType.UPDATE)
+    @UserLoginToken(faceVerify = false, roles = {Roles.ADMIN, Roles.SHIPPING, Roles.CUSTOMER})
+    @i18n(format = {I18nFormat.SEARCH})  // 支持国际化
+    @PostMapping("/detail/{id}")
+    public Result<TeamBaseInfoDto> detail(@PathVariable("id") Long id) {
+        return baseService.detail(id);
+    }
+}
+```
+
+### 3.2 创建服务
+
+```java
+public interface TeamBaseInfoService extends BaseService<TeamBaseInfoReq, TeamBaseInfoDto> {
+    Page<TeamInfoPageDto> page(TeamInfoPageReq req);
+    Result add(TeamInfoCreateReq req);
+    Result update(TeamInfoUpdateReq req);
+    Result delete(RemoveInfoReq req);
+    List<TeamBaseInfoDto> searchTeamInfosByTeamSimpleName(TeamBaseInfoReq req);
+    List<TeamBaseInfoDto> getByIds(List<Long> ids);
+}
+```
+
+### 3.3 实现服务
+
+```java
+@Slf4j
+@Service
+public class TeamBaseInfoServiceImpl extends BaseServiceImpl<TeamBaseInfoMapper, TeamBaseInfoReq, TeamBaseInfoDto> implements TeamBaseInfoService {
+    
+    @Override
+    public Result add(TeamInfoCreateReq req) {
+        // 格式化简称
+        String simpleName = ObjectUtil.getSimpleName(req.getNameEn());
+        // 查询是否已经存在该名称
+        TeamBaseInfoDto dto = (TeamBaseInfoDto) baseMapper.selectOne(
+            new TeamBaseInfoReq(req.getSport(), simpleName), TeamBaseInfoDto.class);
+        if (Objects.nonNull(dto) && Objects.nonNull(dto.getId())) {
+            return Result.error(I18nUtils.get(DATA_EXIST, req.getNameEn()));
+        }
+        req.setSimpleName(simpleName);
+        return Result.ret(baseMapper.insert(req));
+    }
+    
+    @Override
+    public Result update(TeamInfoUpdateReq req) {
+        // 实现更新逻辑
+        // ...
+        return Result.ret(baseMapper.updateById(dto));
+    }
+    
+    // 其他方法实现
+    // ...
+}
+```
+
+### 3.4 创建 Mapper
+
+```java
+public interface TeamBaseInfoMapper extends IBaseMapper<TeamBaseInfoDto>, IBaseProvider<TeamBaseInfoReq, TeamBaseInfoDto> {
+    // 可以添加自定义的查询方法
+}
+```
+
+### 3.5 创建请求对象
+
+```java
+@Data
+@ApiModel(value = "球队基础信息请求参数", description = "球队基础信息请求参数")
+@TableName("tzy_team_info")
+public class TeamBaseInfoReq extends BaseReq {
+    @ApiModelProperty(value = "运动")
+    @TableField(value = "sport")
+    @Where(fieldName = "sport", operator = EQ)
+    private String sport;
+    
+    @ApiModelProperty(value = "简称")
+    @TableField(value = "simple_name")
+    @Where(fieldName = "simple_name", operator = EQ)
+    private String simpleName;
+    
+    // 构造方法
+    public TeamBaseInfoReq() {}
+    
+    public TeamBaseInfoReq(String sport, String simpleName) {
+        this.sport = sport;
+        this.simpleName = simpleName;
+    }
+}
+```
+
+### 3.6 创建数据传输对象
+
+```java
+@Data
+@ApiModel(value = "球队基础信息返回参数", description = "球队基础信息返回参数")
+@TableName("tzy_team_info")
+public class TeamBaseInfoDto extends BaseDto {
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id")
+    private Long id;
+    
+    @ApiModelProperty(value = "区分是否商家创建:merchant=商家创建")
+    @TableField(value = "code")
+    private String code;
+    
+    @ApiModelProperty(value = "中文名")
+    @TableField(value = "display_name")
+    private String displayName;
+    
+    @ApiModelProperty(value = "英文名")
+    @TableField(value = "display_name_en")
+    private String displayNameEn;
+    
+    @ApiModelProperty(value = "运动")
+    @TableField(value = "sport")
+    private String sport;
+    
+    @ApiModelProperty(value = "头像")
+    @TableField(value = "head_url")
+    private String headUrl;
+    
+    @ApiModelProperty(value = "国际化:其他语言翻译:json 字符串")
+    @TableField(value = "translations")
+    private String translations;
+    
+    @ApiModelProperty(value = "简称")
+    @TableField(value = "simple_name")
+    private String simpleName;
+}
+```
+
+## 4. 最佳实践
+
+### 4.1 分层架构
+
+- **控制层(Controller)**:负责处理请求、参数校验和返回结果,不包含业务逻辑。
+- **服务层(Service)**:负责业务逻辑处理,包括数据校验、业务规则和事务管理。
+- **数据访问层(Mapper)**:负责数据库操作,包括 CRUD 和复杂查询。
+
+### 4.2 代码生成
+
+框架提供了代码生成模板,可以快速生成基础的 Controller、Service、ServiceImpl、Mapper 等类。
+
+```
+// controller.java.vm
+package ${package.Controller};
+
+import ${package.Parent}.base.controller.BaseController;
+import ${package.Parent}.dto.${entity}Dto;
+import ${package.Entity}.${entity};
+import ${package.Service}.${table.serviceName};
+// ...
+
+@RestController
+@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
+public class ${table.controllerName} extends BaseController<${table.serviceName}, ${entity}, ${entity}Dto> {
+}
+```
+
+### 4.3 国际化支持
+
+使用 `i18n` 注解和 `I18nUtils` 工具类可以轻松实现国际化支持。
+
+```java
+@i18n(format = {I18nFormat.SEARCH})  // 支持国际化
+@PostMapping("/detail/{id}")
+public Result<TeamBaseInfoDto> detail(@PathVariable("id") Long id) {
+    return baseService.detail(id);
+}
+```
+
+### 4.4 异常处理
+
+使用全局异常处理器 `GlobalExceptionHandler` 统一处理异常,并返回友好的错误信息。
+
+```java
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+    @ExceptionHandler(AuthException.class)
+    public Result handleAuthException(AuthException e) {
+        return Result.error(e.getCode(), e.getMessage());
+    }
+    
+    @ExceptionHandler(BusinessException.class)
+    public Result handleBusinessException(BusinessException e) {
+        return Result.error(e.getCode(), e.getMessage());
+    }
+    
+    // 其他异常处理方法
+    // ...
+}
+```
+
+### 4.5 数据权限控制
+
+通过 `UserInfo` 和 `DataAuth` 实现数据权限控制,确保用户只能访问有权限的数据。
+
+```java
+// 在 BaseServiceImpl 中的 checkWrapper 方法中实现数据权限控制
+UserInfo userInfo = checkUser ? ServletUtils.getUserInfo() : null;
+if (null != userInfo && checkUser) {
+    switch (userInfo.getDataAuth()) {
+        case DEPT: // 部门[商家]级
+            if ("merchantId".equals(name) || "merId".equals(name)) {
+                wrapper = wrapper.eq(column, userInfo.getMerchantId());
+            }
+            break;
+        case PERSON: // 个人
+            if ("userId".equals(name) || "accountId".equals(name) || "createBy".equals(name)) {
+                wrapper = wrapper.eq(column, userInfo.getId());
+            }
+            break;
+        default:
+            if (null != value) {
+                wrapper = wrapper.eq(column, value);
+            }
+    }
+}
+```
+
+## 5. 总结
+
+Poyee Base 框架提供了一套完整的分层架构实现,通过抽象基类和泛型机制,实现了代码复用和业务逻辑的统一管理。该框架具有以下特点:
+
+1. **分层架构**:清晰的控制层、服务层、数据访问层分离,职责明确。
+2. **代码复用**:通过抽象基类和泛型机制,减少重复代码。
+3. **统一响应**:使用 `Result` 对象统一封装接口返回数据。
+4. **国际化支持**:通过 `i18n` 注解和 `I18nUtils` 工具类实现国际化。
+5. **数据权限控制**:通过 `UserInfo` 和 `DataAuth` 实现数据权限控制。
+6. **异常处理**:使用全局异常处理器统一处理异常。
+
+通过使用 Poyee Base 框架,可以快速构建高质量的企业级应用,提高开发效率和代码质量。

+ 192 - 0
py-base/easy.md

@@ -0,0 +1,192 @@
+# Poyee Easy 包功能说明
+
+## 概述
+
+`com.poyee.easy` 包是 Poyee 基础框架中的一个重要组件,主要提供了基于 EasyExcel 的 Excel 导入导出功能,并且特别支持国际化(i18n)处理。该包通过封装 Alibaba EasyExcel 库,提供了更高级的功能和更简洁的 API,使得 Excel 的导入导出操作更加便捷,特别是在需要多语言支持的国际化应用场景中。
+
+## 核心组件
+
+### 1. EasyExcelUtil
+
+这是一个用于 Excel 导出的工具类,主要功能包括:
+
+- 支持普通 Excel 导出和国际化 Excel 导出
+- 提供链式调用 API,如 `init()`, `i18nConfig()`, `build()` 等
+- 支持文件导出和流导出两种方式
+- 支持多 Sheet 导出,特别是国际化场景下的多语言 Sheet
+
+**主要方法:**
+
+- `init(Class<T> clazz, List<T> data)`: 初始化导出工具
+- `init(Class<T> clazz, List<T> data, String sheet)`: 初始化导出工具并指定 sheet 名称
+- `i18nConfig(EasyI18nConfig i18nConfig)`: 设置国际化配置
+- `build()`: 构建导出工具
+- `doWrite(String path)`: 导出到文件
+- `doWriteStream(OutputStream out)`: 导出到输出流
+
+**使用示例:**
+
+```java
+EasyExcelUtil<ChecklistExcelDto, Object> easyExcelUtil = EasyExcelUtil.init(ChecklistExcelDto.class, checklistExcelDtos, baseInfo.getDisplayName())
+    .i18nConfig(i18nConfig)
+    .build();
+easyExcelUtil.doWriteStream(outputStream);
+```
+
+### 2. EasyExcelImportUtil
+
+这是一个用于 Excel 导入的工具类,主要功能包括:
+
+- 支持普通 Excel 导入和国际化 Excel 导入
+- 提供链式调用 API,如 `create()`, `i18nConfig()`, `batch()`, `async()` 等
+- 支持批量处理和异步处理导入数据
+- 支持从 URL 下载 Excel 文件并解析
+
+**主要方法:**
+
+- `create(Class<T> clazz, Consumer<List<R>> consumer, Function<T, R> rowConverter)`: 创建导入工具
+- `i18nConfig(boolean isI18n, EasyI18nConfig i18nConfig)`: 设置国际化配置
+- `batch()`: 启用批量处理
+- `batch(int batchCount)`: 启用批量处理并设置批处理数量
+- `async(ThreadPoolExecutor executor)`: 启用异步处理
+- `doRead(InputStream inputStream)`: 从输入流读取
+- `doRead(String fileUrl)`: 从 URL 读取
+
+**使用示例:**
+
+```java
+List<PaniniChecklistImport> list = new ArrayList<>();
+EasyExcelImportUtil<PaniniChecklistImport, PaniniChecklistImport> importer = 
+    EasyExcelImportUtil.create(PaniniChecklistImport.class, list::addAll, Function.identity())
+    .i18nConfig(true, i18nConfig)
+    .batch();
+importer.doRead(url);
+```
+
+### 3. EasyExcelReadSheetListener
+
+这是一个 EasyExcel 的事件监听器,用于处理 Excel 读取过程中的事件,特别是国际化数据的处理:
+
+- 继承自 `AnalysisEventListener`,用于监听 Excel 读取过程
+- 支持多 Sheet 读取,特别是国际化场景下的多语言 Sheet
+- 提供数据转换和处理功能
+
+**主要方法:**
+
+- `EasyExcelReadSheetListener(EasyI18nConfig config, Function<T, R> rowConverter, Consumer<List<R>> consumer)`: 构造函数
+- `init()`: 初始化监听器
+- `invoke(T data, AnalysisContext context)`: 处理每行数据
+- `doAfterAllAnalysed(AnalysisContext context)`: 所有数据解析完成后的处理
+
+### 4. EasyI18nConfig
+
+这是一个国际化配置类,用于配置国际化 Excel 的导入导出:
+
+- 定义了 Sheet 的结构,包括 key、name、header 和 data 等属性
+- 支持多语言配置
+
+**主要属性:**
+
+- `sheet`: Sheet 配置列表
+
+**Sheet 类属性:**
+
+- `index`: Sheet 索引
+- `key`: Sheet 映射的字段
+- `name`: Sheet 名称
+- `header`: Sheet 头部映射
+- `data`: Sheet 数据
+
+**使用示例:**
+
+```java
+EasyI18nConfig i18nConfig = new EasyI18nConfig();
+List<EasyI18nConfig.Sheet> sheetList = new ArrayList<>();
+sheetList.add(new EasyI18nConfig.Sheet("playerTranslations", "player", Language.getLangEnMap()));
+sheetList.add(new EasyI18nConfig.Sheet("teamTranslations", "team", Language.getLangEnMap()));
+i18nConfig.setSheet(sheetList);
+```
+
+## 国际化支持
+
+该包特别强调了对国际化的支持,主要体现在:
+
+1. **多语言 Sheet**:支持在一个 Excel 文件中包含多个语言的 Sheet,如主数据 Sheet 和语言翻译 Sheet
+
+2. **国际化注解**:使用 `@ExcelI18nFromat` 和 `@i18n` 注解来标记需要国际化处理的字段
+
+3. **动态语言处理**:根据 `ServletUtils.isI18nSupport()` 判断是否需要进行国际化处理
+
+4. **JSON 处理**:使用 fastjson 库处理国际化数据的 JSON 格式化和解析
+
+## 业务应用
+
+在项目中,这些工具类主要用于:
+
+1. **Checklist 导入导出**:如 `CardChecklistBaseInfoServiceImpl` 中的 `downloadAndImport` 和 `downloadCheckList` 方法
+
+2. **国际化数据处理**:如 `doI18nImportChecklist` 和 `downloadI18nExcelAndParseExcel` 方法
+
+## 最佳实践
+
+### 导出 Excel
+
+```java
+// 1. 准备数据
+List<YourDto> dataList = ...
+
+// 2. 创建国际化配置(如果需要)
+EasyI18nConfig i18nConfig = new EasyI18nConfig();
+List<EasyI18nConfig.Sheet> sheetList = new ArrayList<>();
+sheetList.add(new EasyI18nConfig.Sheet("fieldName", "sheetName", languageMap));
+i18nConfig.setSheet(sheetList);
+
+// 3. 初始化导出工具
+EasyExcelUtil<YourDto, Object> easyExcelUtil = EasyExcelUtil.init(YourDto.class, dataList, "Sheet名称")
+    .i18nConfig(i18nConfig)  // 如果需要国际化
+    .build();
+
+// 4. 导出到文件或流
+easyExcelUtil.doWrite("path/to/file.xlsx");  // 导出到文件
+// 或
+easyExcelUtil.doWriteStream(outputStream);  // 导出到流
+```
+
+### 导入 Excel
+
+```java
+// 1. 准备接收数据的集合
+List<YourDto> list = new ArrayList<>();
+
+// 2. 创建国际化配置(如果需要)
+EasyI18nConfig i18nConfig = new EasyI18nConfig();
+List<EasyI18nConfig.Sheet> sheetList = new ArrayList<>();
+sheetList.add(new EasyI18nConfig.Sheet("fieldName", "sheetName", languageMap));
+i18nConfig.setSheet(sheetList);
+
+// 3. 创建导入工具
+EasyExcelImportUtil<YourDto, YourDto> importer = 
+    EasyExcelImportUtil.create(YourDto.class, list::addAll, Function.identity())
+    .i18nConfig(true, i18nConfig)  // 如果需要国际化
+    .batch(500)  // 批量处理,每500条处理一次
+    .async(executor);  // 如果需要异步处理
+
+// 4. 从文件或URL读取
+importer.doRead(inputStream);  // 从输入流读取
+// 或
+importer.doRead("http://example.com/file.xlsx");  // 从URL读取
+```
+
+## 注意事项
+
+1. 在使用国际化功能时,需要确保 DTO 类中有正确的注解标记
+
+2. 导入大量数据时,建议使用批量处理和异步处理功能
+
+3. 导出国际化 Excel 时,需要正确配置 EasyI18nConfig
+
+4. 使用 URL 导入时,需要确保 URL 可访问且格式正确
+
+## 总结
+
+`com.poyee.easy` 包是一个专注于 Excel 处理的工具包,它基于 EasyExcel 库,提供了更高级的功能,特别是国际化支持。它通过链式 API 和注解的方式,简化了 Excel 导入导出的开发工作,并且特别适合需要多语言支持的国际化应用场景。

+ 346 - 0
py-base/i18n.md

@@ -0,0 +1,346 @@
+# Poyee 国际化(i18n)功能说明
+
+## 1. 概述
+
+Poyee 框架提供了完善的国际化(i18n)支持,使应用能够根据用户的语言偏好自动切换界面文本、错误消息和业务数据。国际化功能主要由 `com.poyee.i18n` 包和相关注解、工具类组成,支持多语言消息配置、动态语言切换和国际化数据处理。
+
+## 2. 核心组件
+
+### 2.1 `i18n` 注解
+
+`i18n` 注解用于标记需要国际化的字段、方法或类。
+
+```java
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface i18n {
+    // 是否启用国际化
+    boolean value() default true;
+    // Excel导出时的sheet名称
+    String sheetName() default "";
+    // 国际化格式
+    I18nFormat[] format() default {};
+}
+```
+
+### 2.2 `I18nUtils` 工具类
+
+`I18nUtils` 是国际化功能的核心工具类,提供了获取国际化消息的方法。
+
+```java
+@Configuration
+public class I18nUtils {
+    // 定义占位符的正则表达式模式
+    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\d+)\\}");
+    
+    private MessageProperties messageProperties;
+    
+    // 存储消息的Map,键是消息键,值是包含语言和消息的Map
+    private static Map<String, Map<String, String>> messages;
+    
+    // 初始化国际化工具类,并加载消息属性
+    @Autowired
+    public I18nUtils(MessageProperties messageProperties) {
+        this.messageProperties = messageProperties;
+        messages = messageProperties.init();
+    }
+    
+    // 根据枚举和参数获取国际化消息
+    public static String get(I18nMessageEnums i18n, Object... args) {
+        String key = i18n.getCode();
+        Map<String, String> translations = messages.get(key);
+        String message;
+        if (Objects.isNull(translations)) {
+            // 如果没有找到对应的翻译,使用枚举中的默认消息
+            message = i18n.getMessage();
+        } else {
+            // 根据当前的locale获取对应的翻译
+            Locale locale = LocaleContextHolder.getLocale();
+            message = translations.getOrDefault(locale.getLanguage(), i18n.getLang());
+        }
+
+        // 替换消息中的占位符
+        return replacePlaceholders(message, args);
+    }
+    
+    // 其他辅助方法...
+}
+```
+
+### 2.3 `MessageProperties` 配置类
+
+`MessageProperties` 负责从配置文件中加载国际化消息。
+
+```java
+@Component
+public class MessageProperties {
+
+    @Autowired
+    private ResourceLoader resourceLoader;
+
+    public Map<String, Map<String, String>> init() {
+        return loadMessagesFromYaml();
+    }
+
+    public Map<String, Map<String, String>> loadMessagesFromYaml() {
+        Resource resource = resourceLoader.getResource("classpath:I18n/message.yml");
+        try (InputStream input = resource.getInputStream()) {
+            Yaml yaml = new Yaml();
+            Map map = yaml.loadAs(input, Map.class);
+            return map;
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to load message.yml", e);
+        }
+    }
+}
+```
+
+### 2.4 `I18nMessageEnums` 枚举
+
+`I18nMessageEnums` 定义了系统中所有的国际化消息。
+
+```java
+@Getter
+public enum I18nMessageEnums {
+    // 请求成功
+    SUCCESS("success", "成功", "zh"),
+    // 请求失败
+    REQUEST_ERROR("request_error", "请求失败", "zh"),
+    // 无权操作
+    NO_PERMISSION("no_permission", "无权操作{0}", "zh"),
+    // 更多消息...
+    
+    private String code; // 多语言标识
+    private String message; // 默认信息
+    private String lang; // 默认语言
+    
+    I18nMessageEnums(String code, String message, String lang) {
+        this.code = code;
+        this.message = message;
+        this.lang = lang;
+    }
+}
+```
+
+### 2.5 `i18nAspect` 切面类
+
+`i18nAspect` 是一个切面类,用于处理国际化相关的逻辑。
+
+```java
+@Slf4j
+@Aspect
+@Component
+public class i18nAspect {
+
+    // 配置织入点,针对使用了i18n注解的方法
+    @Pointcut("@annotation(com.poyee.annotation.I18n)")
+    public void logPointCut() {
+    }
+
+    // 在控制器的方法执行前,处理国际化逻辑
+    @Before("execution(public * com.poyee.controller.*.*(..))")
+    public void doBefore(JoinPoint joinPoint) {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        checkI18n(method);
+    }
+
+    // 判断是否支持国际化,并根据情况设置会话属性
+    private void checkI18n(Method method) {
+        if (method.isAnnotationPresent(i18n.class)) {
+            i18n i18n = method.getAnnotation(i18n.class);
+            HttpServletRequest request = ServletUtils.getRequest();
+            String language = request.getHeader("language");
+            boolean isI18n = true;
+            if(StringUtils.isBlank(language)) {
+                language = LocaleContextHolder.getLocale().getLanguage();
+                isI18n = false;
+            }
+
+            try {
+                HttpSession session = ServletUtils.getSession(false);
+                if (session == null) {
+                    log.warn("Session is null, cannot set I18n attributes.");
+                    return;
+                }
+
+                // 根据方法上的i18n注解和请求头中的语言信息,决定是否启用国际化
+                if(i18n.value() && isI18n) {
+                    session.setAttribute("language", language);
+                    I18nFormat[] format = i18n.format();
+                    if(format.length > 0) {
+                        session.setAttribute("i18nFormat", i18n.format());
+                        if (log.isInfoEnabled()) {
+                            log.info("i18nFormat:{}", format[0].getMsg());
+                        }
+                    }
+                    session.setAttribute("i18n", true);
+                } else {
+                    // 如果 I18n.value() 为 false,但 isI18n 为 true,仍不启用国际化
+                    session.setAttribute("i18n", false);
+                }
+            } catch (Exception e) {
+                log.error("Failed to set I18n attributes", e);
+            }
+        }
+    }
+}
+```
+
+### 2.6 `I18nFormat` 枚举
+
+`I18nFormat` 定义了国际化格式的类型。
+
+```java
+@Getter
+public enum I18nFormat {
+
+    INSERT("新增"),
+    UPDATE("编辑"),
+    DELETE("删除"),
+    SEARCH("查询"),
+    EXPORT("导出"),
+    IMPORT("导入")
+    ;
+
+    private String msg;
+
+    I18nFormat(String msg) {
+        this.msg = msg;
+    }
+}
+```
+
+## 3. 国际化配置文件
+
+国际化消息配置文件位于 `classpath:i18n/message.yml`,采用 YAML 格式,结构如下:
+
+```yaml
+success:
+  en: "Success"
+  zh: "成功"
+  
+request_error:
+  en: "Request failed"
+  zh: "请求失败"
+  
+no_permission:
+  en: "No permission to operate {0}"
+  zh: "无权操作{0}"
+
+# 更多消息...
+```
+
+## 4. 使用方法
+
+### 4.1 在控制器中启用国际化
+
+在控制器方法上添加 `@i18n` 注解,启用国际化支持:
+
+```java
+@RestController
+@RequestMapping("/api/users")
+public class UserController {
+
+    @GetMapping("/list")
+    @i18n(format = {I18nFormat.SEARCH})
+    public Result<List<UserDto>> listUsers() {
+        // 业务逻辑...
+        return Result.success(userList);
+    }
+    
+    @PostMapping("/create")
+    @i18n(format = {I18nFormat.INSERT})
+    public Result<Void> createUser(@RequestBody UserCreateReq req) {
+        // 业务逻辑...
+        return Result.success();
+    }
+}
+```
+
+### 4.2 获取国际化消息
+
+使用 `I18nUtils.get()` 方法获取国际化消息:
+
+```java
+// 简单消息
+String successMsg = I18nUtils.get(SUCCESS);
+
+// 带参数的消息
+String noPermissionMsg = I18nUtils.get(NO_PERMISSION, "用户管理");
+
+// 在异常中使用
+throw new ServiceException(I18nUtils.get(DATA_NOT_EXIST, "用户"));
+
+// 在返回结果中使用
+return Result.error(I18nUtils.get(PARAM_ERROR));
+```
+
+### 4.3 标记国际化字段
+
+在实体类或DTO中,使用 `@i18n` 注解标记需要国际化的字段:
+
+```java
+@Data
+public class ProductDto extends BaseDto {
+    private Integer id;
+    private String code;
+    
+    @TableField("name")
+    private String name;
+    
+    @i18n
+    @TableField("name_i18n")
+    private String nameI18n;
+    
+    // 其他字段...
+}
+```
+
+### 4.4 在服务层处理国际化字段
+
+在服务实现类中,可以根据是否启用国际化来决定是否查询国际化字段:
+
+```java
+public class ProductServiceImpl extends BaseServiceImpl<ProductMapper, ProductReq, ProductDto> implements ProductService {
+
+    @Override
+    public Result<Page<ProductDto>> page(ProductPageReq req) {
+        MPJLambdaWrapper<ProductDto> wrapper = new MPJLambdaWrapper<>();
+        wrapper.selectAll(ProductDto.class);
+        
+        // 根据是否启用国际化,决定是否查询国际化字段
+        checkI18n(wrapper, ProductDto.class);
+        
+        // 其他查询条件...
+        
+        return Result.success(baseMapper.selectPage(req.getPage(), wrapper));
+    }
+}
+```
+
+## 5. 最佳实践
+
+### 5.1 统一使用枚举定义消息
+
+所有国际化消息都应该在 `I18nMessageEnums` 中定义,避免硬编码消息字符串。
+
+### 5.2 合理使用占位符
+
+消息中的可变部分应使用占位符 `{0}`, `{1}` 等,而不是字符串拼接。
+
+### 5.3 控制器方法添加国际化注解
+
+所有需要支持国际化的控制器方法都应添加 `@i18n` 注解,并指定适当的 `format`。
+
+### 5.4 国际化字段命名规范
+
+国际化字段通常以原字段名加 `I18n` 后缀命名,如 `name` 和 `nameI18n`。
+
+## 6. 注意事项
+
+1. 国际化功能依赖于 Session,确保在无状态环境(如API网关)中正确传递语言信息。
+2. 国际化消息配置文件的修改需要重启应用才能生效。
+3. 默认语言为中文(zh),如需更改默认语言,需修改 `I18nMessageEnums` 中的定义。
+4. 在前端请求中,可通过 `language` 请求头指定语言,如 `language: en`。

+ 289 - 0
py-base/pom.xml

@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.poyee</groupId>
+        <artifactId>poyee-dashboard</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>py-base</artifactId>
+    <name>基础模块</name>
+    <description>基础模块</description>
+    <properties>
+        <baomidou.version>3.5.2</baomidou.version>
+        <mybatis.version>2.3.0</mybatis.version>
+        <redis.version>2.1.13.RELEASE</redis.version>
+        <jedis.version>3.1.0</jedis.version>
+        <redission.version>3.5.0</redission.version>
+    </properties>
+    <dependencies>
+        <!--spring-boot-starter-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+            <version>${springboot.version}</version>
+        </dependency>
+        <!--spring-->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <!--热更新-->
+        <!--<dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+        </dependency>-->
+        <!--aspectj-->
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+        </dependency>
+
+        <!-- redis 环境 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.session</groupId>
+            <artifactId>spring-session-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.5.0</version>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>3.5.0</version>
+        </dependency>
+
+        <!--七牛云存储-->
+        <!--<dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+            <version>[7.8.0, 7.8.99]</version>
+        </dependency>-->
+        <!--google-->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>29.0-jre</version>
+        </dependency>
+        <!--fluent-log-->
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+            <version>3.0.12</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sndyuk</groupId>
+            <artifactId>logback-more-appenders</artifactId>
+            <version>1.8.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.fluentd</groupId>
+            <artifactId>fluent-logger</artifactId>
+            <version>0.3.4</version>
+        </dependency>
+        <!--lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <!--fastjson-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+        <!--io常用工具类 -->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons.io.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.9.4</version>
+        </dependency>
+        <!--spring-jdbc-->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-jdbc</artifactId>
+        </dependency>
+        <!--阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+        <!--druid-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>${druid.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <!--mybatis-spring-->
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
+            <version>${mybatis.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-spring</artifactId>
+            <version>2.0.7</version>
+            <scope>compile</scope>
+        </dependency>
+        <!--mybatis-baomidou-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>${baomidou.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <version>${baomidou.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-core</artifactId>
+            <version>${baomidou.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>${baomidou.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${baomidou.version}</version>
+        </dependency>
+        <!--mybatis-plus-->
+        <dependency>
+            <groupId>com.github.yulichang</groupId>
+            <artifactId>mybatis-plus-join-boot-starter</artifactId>
+            <version>1.4.2.2</version>
+        </dependency>
+        <!--postgresql-->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>1.7</version>
+        </dependency>
+        <!--yaml-->
+        <!--<dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>-->
+        <!--httpClient-->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpmime</artifactId>
+        </dependency>
+        <!--jwt 鉴权-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.0</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.11</version>
+        </dependency>
+        <!--easyexcel-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.3.4</version>
+        </dependency>
+        <!-- knife4j 接口管理 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>2.0.9</version>
+        </dependency>
+        <!-- swagger2-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.10.5</version>
+            <!--<exclusions>
+                <exclusion>
+                    <groupId>io.swagger</groupId>
+                    <artifactId>swagger-annotations</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.swagger</groupId>
+                    <artifactId>swagger-models</artifactId>
+                </exclusion>
+            </exclusions>-->
+        </dependency>
+        <!--防止进入swagger页面报类型转换错误,排除2.9.2中的引用,手动增加1.5.21版本-->
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.5.21</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+            <version>1.5.21</version>
+        </dependency>
+        <!-- swagger2-UI-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>2.9.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+            <version>1.9.5</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 593 - 0
py-base/provider.md

@@ -0,0 +1,593 @@
+# Provider 包说明文档
+
+## 概述
+
+`com.poyee.base.mapper.provider` 包是一个用于动态构建 SQL 语句的工具包,它基于注解驱动,通过解析实体类和 DTO 上的注解来自动生成 SQL 查询语句。该包主要用于简化数据库操作,支持复杂的查询条件、连接查询、排序、分页等功能,使开发者能够以更加面向对象的方式编写数据库访问代码。
+
+## 核心类
+
+### BaseProvider
+
+`BaseProvider` 是整个包的核心抽象类,提供了基础的 SQL 构建功能,包括左连接和 WHERE 条件的处理。它维护了一系列线程安全的集合来存储 SQL 构建过程中的各种信息:
+
+```java
+public abstract class BaseProvider {
+    // 数据库操作类型
+    public DbMethodEnums dbMethod;
+    // 表计数器
+    public int tableCount = 0;
+    // 是否分页
+    public boolean isPage = false;
+    // 存储 insert
+    public Object insert;
+    // 记录 UPDATE
+    public Object update;
+    // 记录 SELECT
+    public Object select;
+    // 主表信息
+    public TableInfo superTable;
+    // 分页查询
+    public MPage mPage;
+    // 标记是否前端传排序字段
+    public boolean isFrontSort = false;
+    // 分页查询SQL
+    public String pageCountSql;
+    // 记录 INSERT key-value(线程安全)
+    public final Map<String, Object> insertMap = new HashMap<>();
+    // 记录 UPDATE key-value(线程安全)
+    public final Map<String, Object> updateMap = new HashMap<>();
+    // 存储表信息的映射(线程安全)
+    public final Map<String, TableInfo> tableInfos = new ConcurrentHashMap<>();
+    // 记录 WHERE 条件(线程安全)
+    public final List<WhereInfo> whereInfos = new CopyOnWriteArrayList<>();
+    // 记录 SELECT 列(线程安全)
+    public final List<String> selectColumns = new CopyOnWriteArrayList<>();
+    // 记录 LEFT JOIN 信息(线程安全)
+    public final List<LeftJoinInfo> leftJoinInfos = new CopyOnWriteArrayList<>();
+    // 记录 ORDER BY 信息(线程安全)
+    public final Map<Integer,String> orderByMap = new HashMap<>();
+    
+    // 各种SQL构建方法...
+}
+```
+
+`BaseProvider` 提供了以下主要方法:
+
+- `selectById`: 根据ID查询单条记录
+- `selectOne`: 查询单条记录
+- `selectSum`: 聚合查询(求和)
+- `save`: 插入记录
+- `update`: 更新记录
+- `pageWrapper`: 分页查询
+- `leftJoinWrapper`: 左连接查询
+- `mPage`: 分页查询(返回MPage对象)
+
+### IBaseProvider
+
+`IBaseProvider` 是一个接口,定义了基础的数据库操作方法,并通过 MyBatis 的 `@SelectProvider`、`@InsertProvider` 和 `@UpdateProvider` 注解将这些方法与 `BaseProvider` 的实现关联起来。
+
+```java
+public interface IBaseProvider<T extends BaseReq, R extends BaseDto> {
+    @SelectProvider(type = FinalProviderSql.class, method = "leftJoinWrapper")
+    List<Map<String, Object>> selectListMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+    
+    @SelectProvider(type = FinalProviderSql.class, method = "pageCountSql")
+    long pageCount(String sql);
+    
+    @SelectProvider(type = FinalProviderSql.class, method = "pageSql")
+    List<Map<String, Object>> page(String sql);
+    
+    @SelectProvider(type = FinalProviderSql.class, method = "selectOne")
+    Map<String, Object> selectOneMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+    
+    @SelectProvider(type = FinalProviderSql.class, method = "selectById")
+    Map<String, Object> selectByIdMap(@Param("id") Serializable id, @Param("clazz") Class<R> clazz);
+    
+    @SelectProvider(type = FinalProviderSql.class, method = "selectSum")
+    Map<String, Object> selectSumMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+    
+    @InsertProvider(type = FinalProviderSql.class, method = "save")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int save(T req);
+    
+    @UpdateProvider(type = FinalProviderSql.class, method = "update")
+    int update(T req);
+    
+    // 默认方法实现...
+}
+```
+
+`IBaseProvider` 还提供了一些默认方法,如 `selectByPrimaryKey`、`selectOne`、`sum`、`selectList` 和 `selectPage` 等,这些方法封装了对 MyBatis 返回结果的处理逻辑。
+
+### 工具类
+
+#### SqlUtil
+
+`SqlUtil` 是 SQL 生成工具类,负责将 `BaseProvider` 中收集的各种信息转换为最终的 SQL 字符串。它提供了以下主要方法:
+
+- `buildPage`: 构建分页查询
+- `buildOne`: 构建单条记录查询
+- `buildSum`: 构建聚合查询
+- `toSql`: 生成最终的 SQL 字符串
+
+#### SelectUtil
+
+`SelectUtil` 负责处理 SELECT 语句的构建,包括解析实体类上的注解、处理字段映射、构建查询条件等。
+
+#### UpdateUtil
+
+`UpdateUtil` 负责处理 UPDATE 语句的构建,包括解析实体类上的注解、处理字段映射等。
+
+#### InsertUtil
+
+`InsertUtil` 负责处理 INSERT 语句的构建,包括解析实体类上的注解、处理字段映射等。
+
+#### JoinUtil
+
+`JoinUtil` 负责处理 JOIN 语句的构建,包括解析 `@LeftJoin` 注解、构建连接条件等。
+
+#### WhereUtil
+
+`WhereUtil` 负责处理 WHERE 条件的构建,包括解析 `@Where` 注解、构建查询条件等。
+
+#### TableInfoUtil
+
+`TableInfoUtil` 负责处理表信息的初始化,包括解析 `@TableName` 注解、构建表信息对象等。
+
+### 领域模型
+
+#### TableInfo
+
+`TableInfo` 表示数据库表的信息,包括表名、别名、主键等。
+
+```java
+public class TableInfo {
+    // 实体类名
+    private String className;
+    // 实体类
+    private Class<?> clazz;
+    // 表名
+    private String tableName;
+    // 别名
+    private String alias;
+    // 索引
+    private Integer idx;
+    // 主键
+    private String primaryKey;
+}
+```
+
+#### WhereInfo
+
+`WhereInfo` 表示 WHERE 条件的信息,包括表信息、字段名、操作符、值等。
+
+```java
+public class WhereInfo {
+    // 表信息
+    private TableInfo tableInfo;
+    // 字段
+    private String column;
+    // or 条件
+    private List<String> orColumn;
+    // 值
+    private Object value;
+    // 结束值 (between 时使用)
+    private Object endValue;
+    // 操作符
+    private FieldOperator operator;
+    // 字段类型
+    private FieldType fieldType;
+}
+```
+
+#### LeftJoinInfo
+
+`LeftJoinInfo` 表示左连接的信息,包括表信息、关联表信息、关联条件等。
+
+```java
+public class LeftJoinInfo {
+    // 表信息
+    private TableInfo tableInfo;
+    // 关联表信息
+    private TableInfo leftTableInfo;
+    // 关联条件
+    private String fieldName;
+    // 关联字段
+    private String leftFieldName;
+    // 别名
+    private String alias;
+}
+```
+
+#### MPage
+
+`MPage` 表示分页查询的信息,包括当前页、每页大小、总记录数等。
+
+```java
+public class MPage<T> {
+    // 当前页
+    private long current;
+    // 每页大小
+    private long size;
+    // 总记录数
+    private long total;
+    // 记录列表
+    private List<T> records;
+    // 分页SQL
+    private String pageSql;
+    // 计数SQL
+    private String countSql;
+    // 排序信息
+    private List<OrderItem> orders;
+    // 是否优化计数SQL
+    private boolean optimizeCountSql;
+    // 是否查询总记录数
+    private boolean searchCount;
+    // 是否优化连接查询的计数SQL
+    private boolean optimizeJoinOfCountSql;
+}
+```
+
+## 注解
+
+### @Select
+
+`@Select` 注解用于指定查询字段,可以应用在实体类或 DTO 的字段上。
+
+```java
+public @interface Select {
+    // 表名
+    Class<?> table();
+    // 别名
+    String alias() default "";
+    // 字段名
+    String fieldName() default "";
+    // 聚合等条件取值
+    CaseWhen[] caseWhen() default {};
+    // 是否去重
+    boolean distinct() default false;
+}
+```
+
+### @Where
+
+`@Where` 注解用于指定查询条件,可以应用在实体类或 DTO 的字段上。
+
+```java
+public @interface Where {
+    // 表名
+    Class<?> table();
+    // 别名
+    String alias() default "";
+    // 字段名
+    String field() default "";
+    // 字段名数组(用于 OR 条件)
+    String[] orFields() default {};
+    // 字段类型
+    FieldType columnType() default FieldType.STRING;
+    // 操作符
+    FieldOperator operator() default FieldOperator.EQ;
+    // 是否为空
+    boolean isNull() default false;
+    // 是否结束(用于 BETWEEN 条件)
+    boolean isEnd() default false;
+}
+```
+
+### @LeftJoin
+
+`@LeftJoin` 注解用于指定左连接,可以应用在实体类或 DTO 的字段上。
+
+```java
+public @interface LeftJoin {
+    // 关联表
+    Class<?> table();
+    // 关联表(左表,主表)
+    Class<?> leftTable() default void.class;
+    // 关联字段
+    String fieldName() default "";
+    // 关联字段(左表关联字段)
+    String leftFieldName() default "";
+}
+```
+
+### @OrderBy
+
+`@OrderBy` 注解用于指定排序,可以应用在实体类或 DTO 的字段上。
+
+```java
+public @interface OrderBy {
+    // 排序方式
+    OrderBySortEnums sortType() default OrderBySortEnums.ASC;
+    // 顺序
+    int index() default 1;
+}
+```
+
+### @Count
+
+`@Count` 注解用于指定计数查询,可以应用在实体类或 DTO 的字段上。
+
+```java
+public @interface Count {
+    // 是否去重
+    boolean distinct() default false;
+    // 条件
+    CaseWhen[] caseWhen() default {};
+}
+```
+
+### @Sum
+
+`@Sum` 注解用于指定求和查询,可以应用在实体类或 DTO 的字段上。
+
+```java
+public @interface Sum {
+    // 条件
+    CaseWhen[] caseWhen() default {};
+}
+```
+
+## 使用示例
+
+### 1. 定义实体类和DTO
+
+```java
+// 实体类
+@Data
+@TableName("user")
+public class User {
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    
+    @TableField("name")
+    private String name;
+    
+    @TableField("age")
+    private Integer age;
+    
+    @TableField("email")
+    private String email;
+    
+    @TableField("create_time")
+    private Date createTime;
+}
+
+// 请求DTO
+@Data
+public class UserReq extends BaseReq {
+    @Where(table = User.class, field = "name")
+    private String name;
+    
+    @Where(table = User.class, field = "age", operator = FieldOperator.GE)
+    private Integer minAge;
+    
+    @Where(table = User.class, field = "age", operator = FieldOperator.LE)
+    private Integer maxAge;
+    
+    @Where(table = User.class, field = "email", operator = FieldOperator.LIKE)
+    private String email;
+    
+    @LeftJoin(table = Department.class, leftTable = User.class, fieldName = "id", leftFieldName = "dept_id")
+    private String departmentJoin;
+}
+
+// 响应DTO
+@Data
+public class UserDto extends BaseDto {
+    private Long id;
+    private String name;
+    private Integer age;
+    private String email;
+    private Date createTime;
+    
+    @Select(table = Department.class, fieldName = "name")
+    private String departmentName;
+    
+    @Count(distinct = true)
+    @TableField("id")
+    private Long userCount;
+    
+    @Sum
+    @TableField("age")
+    private Long totalAge;
+}
+```
+
+### 2. 定义Mapper接口
+
+```java
+@Mapper
+public interface UserMapper extends IBaseMapper<UserDto> {
+    // 可以添加自定义方法
+}
+```
+
+### 3. 使用示例
+
+```java
+@Service
+public class UserService extends BaseServiceImpl<UserReq, User, UserDto> {
+    @Autowired
+    private UserMapper userMapper;
+    
+    // 查询单条记录
+    public UserDto getUser(Long id) {
+        return userMapper.selectByPrimaryKey(id, UserDto.class);
+    }
+    
+    // 条件查询
+    public List<UserDto> getUserList(UserReq req) {
+        return userMapper.selectList(req, UserDto.class);
+    }
+    
+    // 分页查询
+    public Page<UserDto> getUserPage(UserReq req) {
+        return userMapper.selectPage(req, UserDto.class);
+    }
+    
+    // 统计查询
+    public UserDto getUserStats(UserReq req) {
+        return userMapper.sum(req, UserDto.class);
+    }
+    
+    // 保存
+    public int saveUser(UserReq req) {
+        return userMapper.save(req);
+    }
+    
+    // 更新
+    public int updateUser(UserReq req) {
+        return userMapper.update(req);
+    }
+}
+```
+
+## 高级用法
+
+### 1. 复杂条件查询
+
+```java
+@Data
+public class ComplexUserReq extends BaseReq {
+    // 多字段OR条件
+    @Where(table = User.class, orFields = {"name", "email"}, operator = FieldOperator.LIKE)
+    private String keyword;
+    
+    // BETWEEN条件
+    @Where(table = User.class, field = "create_time", operator = FieldOperator.BETWEEN)
+    private Date startTime;
+    
+    @Where(table = User.class, field = "create_time", operator = FieldOperator.BETWEEN, isEnd = true)
+    private Date endTime;
+    
+    // IN条件
+    @Where(table = User.class, field = "status", operator = FieldOperator.IN)
+    private List<Integer> statusList;
+}
+```
+
+### 2. 多表关联查询
+
+```java
+@Data
+public class UserWithDeptReq extends BaseReq {
+    @Where(table = User.class, field = "name", operator = FieldOperator.LIKE)
+    private String userName;
+    
+    @LeftJoin(table = Department.class, leftTable = User.class, fieldName = "id", leftFieldName = "dept_id")
+    private String deptJoin;
+    
+    @Where(table = Department.class, field = "name", operator = FieldOperator.LIKE)
+    private String deptName;
+    
+    @LeftJoin(table = Company.class, leftTable = Department.class, fieldName = "id", leftFieldName = "company_id")
+    private String companyJoin;
+    
+    @Where(table = Company.class, field = "name", operator = FieldOperator.LIKE)
+    private String companyName;
+}
+
+@Data
+public class UserWithDeptDto extends BaseDto {
+    private Long id;
+    private String name;
+    private Integer age;
+    
+    @Select(table = Department.class, fieldName = "name")
+    private String departmentName;
+    
+    @Select(table = Company.class, fieldName = "name")
+    private String companyName;
+}
+```
+
+### 3. 聚合查询
+
+```java
+@Data
+public class UserStatsDto extends BaseDto {
+    @Count(distinct = true)
+    @TableField("id")
+    private Long userCount;
+    
+    @Sum
+    @TableField("age")
+    private Long totalAge;
+    
+    @Select(table = Department.class, fieldName = "name")
+    private String departmentName;
+    
+    // CASE WHEN 条件
+    @Sum(caseWhen = {
+        @CaseWhen(when = "age > 30", then = "1", elseThen = "0")
+    })
+    @TableField("id")
+    private Long seniorUserCount;
+}
+```
+
+## 注意事项
+
+1. 所有的查询都需要传入 `Class<R> clazz` 参数,用于指定返回的 DTO 类型。
+2. 使用 `@Where` 注解时,需要指定 `table` 属性,用于确定查询条件所属的表。
+3. 使用 `@LeftJoin` 注解时,需要指定 `table` 和 `leftTable` 属性,用于确定连接的两个表。
+4. 使用 `@Select` 注解时,需要指定 `table` 和 `fieldName` 属性,用于确定查询的字段。
+5. 使用 `@OrderBy` 注解时,可以指定 `sortType` 和 `index` 属性,用于确定排序方式和顺序。
+6. 使用 `@Count` 和 `@Sum` 注解时,需要与 `@TableField` 注解一起使用,用于确定聚合的字段。
+7. 使用 `@CaseWhen` 注解时,需要指定 `when` 和 `then` 属性,用于构建 CASE WHEN 语句。
+
+## 总结
+
+`com.poyee.base.mapper.provider` 包提供了一种基于注解的方式来构建 SQL 语句,使开发者能够以更加面向对象的方式编写数据库访问代码。通过使用这些注解,开发者可以轻松实现复杂的查询条件、连接查询、排序、分页等功能,而无需手动编写 SQL 语句。
+
+这种方式的优点是:
+
+1. 代码更加简洁、易读、易维护
+2. 减少了 SQL 注入的风险
+3. 支持复杂的查询条件和连接查询
+4. 与 MyBatis 和 MyBatis-Plus 无缝集成
+
+缺点是:
+
+1. 学习成本较高
+2. 对于非常复杂的查询,可能不如直接编写 SQL 语句灵活
+3. 性能可能略低于手写 SQL 语句
+
+总体来说,这个包是一个非常有用的工具,特别适合于需要频繁进行数据库操作的项目。
+
+
+## 注意事项
+
+1. **学习成本**:使用该包需要了解各种注解的用法和 SQL 构建的原理,学习成本较高。
+2. **性能考虑**:动态构建 SQL 可能会带来一定的性能开销,对于性能要求极高的场景,可能需要直接使用原生 SQL。
+3. **复杂查询**:对于非常复杂的查询,可能不如直接编写 SQL 语句灵活。
+4. **调试难度**:动态构建的 SQL 可能难以调试,建议在开发环境中开启 SQL 日志。
+5. **版本兼容性**:在升级 MyBatis 或数据库版本时,需要注意兼容性问题。
+
+## 性能优化建议
+
+1. **表信息缓存**:实现表结构信息的缓存,避免重复解析表结构。
+2. **SQL片段缓存**:对于频繁使用的SQL片段,可以实现缓存机制。
+3. **批量操作**:使用批量插入和更新操作,减少数据库交互次数。
+4. **索引优化**:确保在WHERE条件和JOIN条件中使用的字段上创建了适当的索引。
+5. **查询优化**:
+    - 只查询需要的列,避免使用`SELECT *`
+    - 使用分页查询时,将COUNT查询和数据查询分离
+    - 对于大数据量的表,考虑使用分区表或分表策略
+6. **连接查询优化**:
+    - 减少连接表的数量,必要时使用子查询或分批查询
+    - 确保连接条件字段上有索引
+7. **代码优化**:
+    - 减少反射操作,可以使用缓存存储字段和注解信息
+    - 优化字符串拼接操作,使用StringBuilder而不是String连接
+    - 使用线程安全的集合类,避免并发问题
+8. **SQL执行计划分析**:
+    - 定期分析生成的SQL执行计划,找出性能瓶颈
+    - 对于复杂查询,考虑使用数据库视图或存储过程
+9. **监控和日志**:
+    - 实现SQL执行时间的监控和日志记录
+    - 对于执行时间超过阈值的SQL,进行告警和优化
+10. **使用连接池**:
+    - 配置合适的数据库连接池参数
+    - 监控连接池的使用情况,避免连接泄漏

+ 47 - 0
py-base/src/main/java/com/poyee/annotation/ApiAuthenticationToken.java

@@ -0,0 +1,47 @@
+package com.poyee.annotation;
+
+import com.poyee.common.enums.CheckAuthTypeEnums;
+import com.poyee.common.enums.Roles;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * api token验证注解
+ * 含有此注解的接口可支持 外网,内网访问,根据不同的访问规则验证不同类型的token
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiAuthenticationToken {
+
+    /**
+     * 是否需要验证
+     * @return
+     */
+    boolean required() default true;
+
+    /**
+     * 验证类型:authorization、private(内部访问时使用密钥对验证)
+     */
+    CheckAuthTypeEnums type() default CheckAuthTypeEnums.AUTHORIZATION;
+
+    /**
+     * token的key
+     * @return
+     */
+    String type_key() default "X-USER-BASE64";
+
+    /**
+     * @return
+     */
+    Roles[] roles() default {Roles.ADMIN};
+
+    /**
+     * 非验证来源
+     * @return
+     */
+    String pass_aud() default "partner";
+
+}

+ 14 - 0
py-base/src/main/java/com/poyee/annotation/CheckDataNull.java

@@ -0,0 +1,14 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CheckDataNull {
+}

+ 21 - 0
py-base/src/main/java/com/poyee/annotation/Desensit.java

@@ -0,0 +1,21 @@
+package com.poyee.annotation;
+
+import com.poyee.common.enums.DesensitType;
+
+import java.lang.annotation.*;
+
+/**
+ * 脱敏规则
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Desensit {
+    //脱敏字段
+    String field() default "";
+
+    //脱敏类型
+    DesensitType type() default DesensitType.DEFAULT;
+
+
+}

+ 16 - 0
py-base/src/main/java/com/poyee/annotation/DesensitMethod.java

@@ -0,0 +1,16 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 脱敏规则
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface DesensitMethod {
+
+    boolean desensit() default true;
+
+
+}

+ 13 - 0
py-base/src/main/java/com/poyee/annotation/DictTypeFormat.java

@@ -0,0 +1,13 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 字典表转换注解
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DictTypeFormat {
+    String value();
+}

+ 14 - 0
py-base/src/main/java/com/poyee/annotation/ExcelI18nFromat.java

@@ -0,0 +1,14 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Documented
+public @interface ExcelI18nFromat {
+    String value();
+
+    boolean exists() default true;
+
+    String fieldName() default "";
+}

+ 38 - 0
py-base/src/main/java/com/poyee/annotation/I18n.java

@@ -0,0 +1,38 @@
+package com.poyee.annotation;
+
+import com.poyee.common.enums.I18nFormat;
+import com.poyee.i18n.I18nSourcesEnum;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 国际化
+ */
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface I18n {
+
+    /**
+     * @return
+     */
+    boolean value() default true;
+    /**
+     * @return
+     */
+    String sheetName() default "";
+
+    /**
+     * @return
+     */
+    I18nFormat[] format() default {};
+
+    /**
+     * 国际化源
+     * @return
+     */
+    I18nSourcesEnum dataSource() default I18nSourcesEnum.NONE;
+
+}

+ 34 - 0
py-base/src/main/java/com/poyee/annotation/I18nToken.java

@@ -0,0 +1,34 @@
+package com.poyee.annotation;
+
+import com.poyee.common.enums.Roles;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 国际化注解
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface I18nToken {
+    /**
+     * @return
+     */
+    boolean local() default false;
+    /**
+     * @return
+     */
+    boolean required() default true;
+    /**
+     * @return
+     */
+    Roles[] roles() default {Roles.ADMIN};
+    /**
+     * 通过验证的来源
+     * @return
+     */
+    String pass_aub() default "";
+
+}

+ 19 - 0
py-base/src/main/java/com/poyee/annotation/JsonMapper.java

@@ -0,0 +1,19 @@
+package com.poyee.annotation;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.annotation.*;
+
+/**
+ * json映射
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface JsonMapper {
+
+    @NotNull
+    String name() default "";
+    //类型举例
+    String readConverterExp() default "" ;
+}

+ 37 - 0
py-base/src/main/java/com/poyee/annotation/Log.java

@@ -0,0 +1,37 @@
+package com.poyee.annotation;
+
+
+import com.poyee.common.enums.BusinessType;
+import com.poyee.common.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志记录注解
+ *
+ * @author zheng
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+    /**
+     * 模块
+     */
+    String title() default "";
+
+    /**
+     * 功能
+     */
+    BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    boolean isSaveRequestData() default true;
+}

+ 51 - 0
py-base/src/main/java/com/poyee/annotation/MpjWapper.java

@@ -0,0 +1,51 @@
+package com.poyee.annotation;
+
+
+import java.lang.annotation.*;
+
+/**
+ * 类属性注解
+ * 描述:搜索时使用
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MpjWapper {
+    //字段
+    String value() ;
+    //字段类型
+    Type columnType() default Type.STRING;
+    //操作符
+    Operator operator() default Operator.EQ;
+    //数据
+    String data() default "";
+
+    enum Type {
+        STRING,
+        NUMBER,
+        DATE,
+        BOOLEAN,
+        ENUM,
+    }
+
+    enum Operator{
+        EQ,
+        NE,
+        GT,
+        GE,
+        LT,
+        LE,
+        LIKE,
+        NOTLIKE,
+        IN,
+        NOTIN,
+        BETWEEN,
+        NOTBETWEEN,
+        ISNULL,
+        ISNOTNULL,
+        ISNOTEMPTY,
+        ISEMPTY,
+        ISNOT
+    }
+
+}

+ 13 - 0
py-base/src/main/java/com/poyee/annotation/PassToken.java

@@ -0,0 +1,13 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// 用来跳过验证的PassToken
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PassToken {
+    boolean required() default true;
+}

+ 24 - 0
py-base/src/main/java/com/poyee/annotation/Property.java

@@ -0,0 +1,24 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 字段属性
+ */
+@Documented
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Property {
+    /**
+     * class , field , map
+     *
+     * @return
+     */
+    String fieldType() default "field";
+
+    String defaultName() default "";
+
+    String defaultValue() default "";
+
+}
+

+ 13 - 0
py-base/src/main/java/com/poyee/annotation/ReadConverterExpFormat.java

@@ -0,0 +1,13 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 列举数据转换注解
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ReadConverterExpFormat {
+    String value();
+}

+ 60 - 0
py-base/src/main/java/com/poyee/annotation/RedisCache.java

@@ -0,0 +1,60 @@
+package com.poyee.annotation;
+
+import com.poyee.enums.CacheExpirePolicy;
+
+import java.lang.annotation.*;
+
+/**
+ * Redis 缓存注解,用于方法级别缓存
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RedisCache {
+
+    /**
+     * 缓存的 key,支持 SpEL 表达式,例如:a_b_#{merId}
+     */
+    String key();
+
+    /**
+     * 占位符数据源,例如:userContext、request、merchant、args 等
+     * 用于从指定源中提取 key 中的变量值(如 merId)
+     */
+    String keySource() default "userContext";
+
+    /**
+     * 缓存过期策略
+     * @see com.poyee.enums.CacheExpirePolicy
+     */
+    CacheExpirePolicy expirePolicy() default CacheExpirePolicy.TODAY;
+
+    /**
+     * 自定义过期时间(秒),当 expirePolicy = CUSTOM 时生效
+     */
+    long expireTime() default 3600;
+
+    /**
+     * 是否分组缓存,默认为 false
+     * @return
+     */
+    boolean group() default false;
+
+    /**
+     * 缓存分组名称,当 group = true 时生效
+     * @return
+     */
+    String groupName() default "default";
+
+    /**
+     * 是否记录缓存key
+     */
+    boolean logKey() default false;
+
+    /**
+     * 记录缓存key规则
+     */
+    String logKeyRule() default "key";
+
+
+}

+ 25 - 0
py-base/src/main/java/com/poyee/annotation/RedisLock.java

@@ -0,0 +1,25 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 分布式锁注解
+ *
+ * @author
+ */
+@Target({ ElementType.PARAMETER, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RedisLock {
+     /**锁的key,支持SpEL表达式*/
+     String lockKey();
+     /**时间单位*/
+     TimeUnit unit() default TimeUnit.SECONDS;
+     /**锁等待时间*/
+     long waitTime() ;
+     /**锁持续时间,-1:自动续锁*/
+     long leaseTime();
+     /**是否使用事务*/
+     boolean isUseTran() default false;
+}

+ 36 - 0
py-base/src/main/java/com/poyee/annotation/UserLoginToken.java

@@ -0,0 +1,36 @@
+package com.poyee.annotation;
+
+
+import com.poyee.common.enums.Roles;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// 需要登录才能进行操作的注解UserLoginToken
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UserLoginToken {
+    /**
+     * 是否需要登录
+     * @return
+     */
+    boolean required() default true;
+    /**
+     * 是否需要实名
+     * @return
+     */
+    boolean faceVerify() default false;
+    /**
+     * @return
+     */
+    Roles[] roles() default {Roles.ADMIN};
+
+    /**
+     * 非验证来源
+     * @return
+     */
+    String pass_aud() default "partner";
+
+}

+ 23 - 0
py-base/src/main/java/com/poyee/annotation/db/CaseWhen.java

@@ -0,0 +1,23 @@
+package com.poyee.annotation.db;
+
+import com.poyee.enums.CaseWhenValueTypeEnums;
+
+import java.lang.annotation.*;
+
+/**
+ * 聚合等条件取值
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface CaseWhen {
+    //  条件
+    String when() default "";
+    //  值
+    String then() default "";
+    //  是否是else
+    boolean isElse() default false;
+    //  值类型
+    CaseWhenValueTypeEnums valueType() default CaseWhenValueTypeEnums.CONSTANT;
+
+}

+ 20 - 0
py-base/src/main/java/com/poyee/annotation/db/Count.java

@@ -0,0 +1,20 @@
+package com.poyee.annotation.db;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.lang.annotation.*;
+
+/**
+ * 统计字段  计数
+ * 和 {@link TableField TableField}一起使用
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Count {
+
+    boolean distinct() default false;
+
+    CaseWhen[] caseWhen() default {};
+
+}

+ 13 - 0
py-base/src/main/java/com/poyee/annotation/db/Distinct.java

@@ -0,0 +1,13 @@
+package com.poyee.annotation.db;
+
+import java.lang.annotation.*;
+
+/**
+ * Created by poyi
+ * 去重
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Distinct {
+}

+ 18 - 0
py-base/src/main/java/com/poyee/annotation/db/Join.java

@@ -0,0 +1,18 @@
+package com.poyee.annotation.db;
+
+import java.lang.annotation.*;
+
+/**
+ * 外连接
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Join {
+    // 关联表
+    String table();
+    // 关联条件
+    String on();
+    // 别名
+    String alias();
+}

+ 21 - 0
py-base/src/main/java/com/poyee/annotation/db/LeftJoin.java

@@ -0,0 +1,21 @@
+package com.poyee.annotation.db;
+
+import java.lang.annotation.*;
+
+/**
+ * 左连接
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface LeftJoin {
+    // 关联表
+    Class<?> table();
+    // 关联表(左表。主表)
+    Class<?> leftTable() default void.class;
+    // 关联字段
+    String fieldName() default "";
+    /* 关联字段(左表关联字段)*/
+    String leftFieldName() default "";
+
+}

+ 15 - 0
py-base/src/main/java/com/poyee/annotation/db/OrderBy.java

@@ -0,0 +1,15 @@
+package com.poyee.annotation.db;
+
+import com.poyee.enums.OrderBySortEnums;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface OrderBy {
+    // 排序方式
+    OrderBySortEnums sortType() default OrderBySortEnums.ASC;
+    // 顺序
+    int index() default 1;
+}

+ 19 - 0
py-base/src/main/java/com/poyee/annotation/db/RightJoin.java

@@ -0,0 +1,19 @@
+package com.poyee.annotation.db;
+
+import java.lang.annotation.*;
+
+/**
+ * 右连接
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RightJoin {
+    // 表名
+    String table();
+    // 关联条件
+    String on();
+    // 别名
+    String alias();
+
+}

+ 23 - 0
py-base/src/main/java/com/poyee/annotation/db/Select.java

@@ -0,0 +1,23 @@
+package com.poyee.annotation.db;
+
+import java.lang.annotation.*;
+
+/**
+ * 查询
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Select {
+    // 表名
+    Class<?> table();
+    // 别名
+    String alias() default "";
+    // 字段名
+    String fieldName() default "";
+    // 聚合等条件取值
+    CaseWhen[] caseWhen() default {};
+
+    boolean distinct() default false;
+
+}

+ 17 - 0
py-base/src/main/java/com/poyee/annotation/db/Sum.java

@@ -0,0 +1,17 @@
+package com.poyee.annotation.db;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.lang.annotation.*;
+
+/**
+ * 统计字段  记和
+ * 和 {@link TableField TableField}一起使用
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Sum {
+
+    CaseWhen[] caseWhen() default {};
+}

+ 88 - 0
py-base/src/main/java/com/poyee/annotation/db/Where.java

@@ -0,0 +1,88 @@
+package com.poyee.annotation.db;
+
+import com.poyee.enums.FieldOperator;
+import com.poyee.enums.FieldType;
+
+import java.lang.annotation.*;
+
+/**
+ * 条件
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Where {
+    // 表名、别名 2选一
+    Class<?> table() ;
+    // 别名 、表名 2选一
+    String alias() default "";
+    // 字段名 或字段名 2选一
+    String field() default "";
+    // 字段名 或字段名 2选一
+    String[] orFields() default {};
+    //字段类型
+    FieldType columnType() default FieldType.STRING;
+    //操作符
+    FieldOperator operator() default FieldOperator.EQ;
+    // 是否为空
+    boolean isNull() default false;
+    // 是否为结束条件
+    boolean isEnd() default false;
+    // 自定义查询条件数组,用于组合多个查询条件
+    CustomConditions[] customConditions() default {};
+    // 是否使用自定义的 or 条件
+    // 如果为 true,则 customConditions 中的条件将使用 OR 连接,而不是默认的 AND 连接
+    // 注意:如果 isCustomOr 为 true,则 customConditions 中的条件必须是独立的查询条件,不能依赖于其他条件
+    // 例如:
+    // @Where(table = User.class, isCustomOr = true,
+    //         customConditions = {
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "active"),
+    //             @CustomCondition(field = "role", operator = FieldOperator.IN, orFields = {"admin", "user"})
+    //         },{
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "logOff")
+    //         })
+    // 上述条件将生成 SQL 语句:WHERE (status = 'active' AND role IN ('admin', 'user')) OR (status = 'logOff')
+    // 如果为 false,则 customConditions 中的条件将使用 AND 连接
+    // 例如:
+    // @Where(table = User.class, isCustomOr = false,
+    //         customConditions = {
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "active"),
+    //             @CustomCondition(field = "role", operator = FieldOperator.IN, orFields = {"admin", "user"})
+    //         },{
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "logOff")
+    //         })
+    // 上述条件将生成 SQL 语句:WHERE status = 'active' AND role IN ('admin', 'user') AND status = 'logOff'
+    // 注意:如果 isCustomOr 为 false,则 customConditions 中的条件必须是可以同时成立的条件,否则将导致查询结果为空
+    // 注意:customConditions 中的条件每组条件之间是 AND 关系,而组与组之间 根据 isCustomOr 的值决定是 AND 还是 OR 关系
+    // 默认值为 false,表示使用 AND 连接
+    // 如果需要使用 OR 连接,则需要显式设置 isCustomOr 为 true
+    boolean isCustomOr() default false;
+
+    /**
+     * 用于替代 Where[] 的嵌套注解,避免循环引用
+     */
+    @Target({})
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface CustomConditions {
+        CustomCondition[] value() default {};
+    }
+
+    @Target({})
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface CustomCondition {
+        // 字段名 或字段名 2选一
+        String field() default "";
+        // 字段名 或字段名 2选一
+        String[] orFields() default {};
+        //字段类型
+        FieldType columnType() default FieldType.STRING;
+        //操作符
+        FieldOperator operator() default FieldOperator.EQ;
+        // 是否为空
+        boolean isNull() default false;
+        // 是否为结束条件
+        boolean isEnd() default false;
+        //默认值
+        String defaultValue() default "";
+    }
+}

+ 30 - 0
py-base/src/main/java/com/poyee/annotation/ds/DataSource.java

@@ -0,0 +1,30 @@
+package com.poyee.annotation.ds;
+
+
+import com.poyee.datasource.enums.DataSourceType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义多数据源切换注解
+ * <p>
+ * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
+ *
+ * @author zheng
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface DataSource {
+    /**
+     * 切换数据源名称
+     */
+    DataSourceType value() default DataSourceType.MASTER;
+    // 事务管理器
+    String txManager() default "dynamicTransactionManager"; // 事务管理器
+    // 读写分离
+    boolean readOnly() default false;
+    // 抛出指定异常后回滚
+    Class<? extends Throwable>[] rollbackFor() default {};
+}

+ 28 - 0
py-base/src/main/java/com/poyee/annotation/ds/Transactional.java

@@ -0,0 +1,28 @@
+package com.poyee.annotation.ds;
+
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Propagation;
+
+import java.lang.annotation.*;
+
+/**
+ *  数据源切换注解
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Transactional {
+
+    Propagation propagation() default Propagation.REQUIRED;
+
+    Isolation isolation() default Isolation.DEFAULT;
+
+    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
+
+    boolean readOnly() default false;
+
+    Class<? extends Throwable>[] rollbackFor() default {};
+
+    Class<? extends Throwable>[] noRollbackFor() default {};
+}

+ 35 - 0
py-base/src/main/java/com/poyee/annotation/i18nSource.java

@@ -0,0 +1,35 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *  国际化 数据来源
+ */
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface i18nSource {
+
+    /**
+     * 多语言数据来源 表
+     * @return
+     */
+    Class<?> sourceTable() default void.class;
+
+    /**
+     * 多语言数据来源 字段
+     * @return
+     */
+    String sourceField() default "";
+
+    /**
+     * key:value
+     * 多语言数据来源 字段值
+     * @return
+     */
+    String key() default "";
+
+
+}

+ 154 - 0
py-base/src/main/java/com/poyee/aspectj/ApiAuthenticationTokenAspect.java

@@ -0,0 +1,154 @@
+package com.poyee.aspectj;
+
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.poyee.annotation.ApiAuthenticationToken;
+import  com.poyee.common.auth.UserRoleUtil;
+import com.poyee.common.exception.AuthException;
+import com.poyee.common.exception.BusinessException;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.framework.jwt.JwtUtils;
+import com.poyee.i18n.I18nUtils;
+import com.poyee.util.ServletUtils;
+import com.poyee.util.StringUtils;
+import com.poyee.util.security.Base64Util;
+import com.poyee.util.security.RsaUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import static com.poyee.i18n.I18nMessageEnums.AUTH_USER_ERROR;
+
+/**
+ * @Description: 登录拦截
+ * 可支持 外网,内网访问,根据不同的访问规则验证不同类型的token
+ * Created by poyee on 2025/10/31.
+ */
+@Slf4j
+@Aspect
+@Component
+public class ApiAuthenticationTokenAspect {
+
+    @Value("${whitelist.id:0}")
+    private String whitelistId;
+    // 配置织入点
+    @Pointcut("@annotation(com.poyee.annotation.ApiAuthenticationToken)")
+    public void logPointCut() {
+    }
+    /**
+     * @param
+     */
+    @Before("logPointCut()")
+    public void doBefore(JoinPoint joinPoint) {
+        // 接收到请求,记录请求内容
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        // 检查用户权限和参数
+        checkAuthTokenByType(method);
+    }
+
+    /**
+     * 根据注解类型判断是否需要验证token
+     * @param method
+     */
+    private void checkAuthTokenByType(Method method) {
+        // 判断方法是否需要登录
+        if (method.isAnnotationPresent(ApiAuthenticationToken.class)) {
+            //如果存在注解 则获取注解配置进行对不同类型的token进行验证
+            ApiAuthenticationToken apiAuthenticationToken = method.getAnnotation(ApiAuthenticationToken.class);
+            log.info("apiAuthenticationToken:{}",apiAuthenticationToken);
+            if (apiAuthenticationToken.required()) {
+                //判断类型
+                switch (apiAuthenticationToken.type()) {
+                    case AUTHORIZATION:
+                        //验证 token
+                        checkAuthorization(apiAuthenticationToken);
+                        break;
+                    case PRIVATE:
+                        //验证token
+                        checkPrivate(apiAuthenticationToken);
+                        break;
+                    default:
+                        //无对应验证类型则返回无权限
+                        throw new AuthException("无访问权限");
+                }
+            }else{
+                log.info("不需要验证");
+            }
+        }else{
+            log.info("不需要验证");
+        }
+    }
+
+    /**
+     * @param apiAuthenticationToken
+     */
+    private void checkAuthorization(ApiAuthenticationToken apiAuthenticationToken) {
+        String authKey = apiAuthenticationToken.type_key();
+        HttpServletRequest request = ServletUtils.getRequest();
+        String userInfoStr = request.getHeader(authKey);
+        if(StringUtils.isBlank(userInfoStr)){
+            throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+        }
+        //保存用户信息
+        com.poyee.util.ServletUtils.getSession().setAttribute("x-user-base64", userInfoStr);
+        String user = "";
+        try {
+            user = new String(Base64Util.decode(userInfoStr), StandardCharsets.UTF_8);
+            JSONObject.parseObject(user);
+        } catch (JSONException jsonExe) {
+            log.error(" 转换json 异常 {} ", userInfoStr);
+            throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+        } catch (IllegalArgumentException ie) {
+            log.error(" token bease64解析 异常 {} ", userInfoStr);
+            throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+        }
+        if (StringUtils.isBlank(user)) {
+            throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+        }
+        request.getSession().setAttribute("userInfo", user);
+        //权限判断
+        if (!UserRoleUtil.builder(whitelistId).checkRole(apiAuthenticationToken.pass_aud(), Arrays.asList(apiAuthenticationToken.roles()))) {
+            throw new BusinessException(403, I18nUtils.get(AUTH_USER_ERROR, "[403]"));
+        }
+    }
+
+    /**
+     * @param apiAuthenticationToken
+     */
+    private void checkPrivate(ApiAuthenticationToken apiAuthenticationToken) {
+        //验证token
+        try {
+            String userInfoStr = apiAuthenticationToken.type_key();
+            String token = ServletUtils.getRequest().getHeader(userInfoStr);
+            //保存用户信息
+            ServletUtils.getSession().setAttribute("x-user-base64", userInfoStr);
+            String user = RsaUtil.decrypt(RsaUtil.getPrivateKeyFromResource(RsaUtil.private_path), token);
+            if(StringUtils.isBlank(user)){
+                throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+            }
+            ServletUtils.getRequest().getSession().setAttribute("userInfo", user);
+            //权限判断
+            if (!UserRoleUtil.builder(whitelistId).checkRole(apiAuthenticationToken.pass_aud(), Arrays.asList(apiAuthenticationToken.roles()))) {
+                throw new BusinessException(403, I18nUtils.get(AUTH_USER_ERROR, "[403]"));
+            }
+        }catch (Exception e) {
+            throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+        }
+//        throw new ServiceException("暂不支持");
+    }
+
+
+}

+ 111 - 0
py-base/src/main/java/com/poyee/aspectj/DataSourceAspect.java

@@ -0,0 +1,111 @@
+package com.poyee.aspectj;
+
+import com.poyee.annotation.ds.DataSource;
+import com.poyee.datasource.DynamicDataSource;
+import com.poyee.datasource.holder.DataSourceContextHolder;
+import com.poyee.util.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import java.util.Objects;
+
+/**
+ * 多数据源切换切面类
+ * <p>
+ * 该类通过 AOP 实现方法或类级别的数据源动态切换。
+ * 使用 @DataSource 注解指定数据源名称,支持在方法或类级别使用。
+ * <p>
+ * 示例:
+ * <pre>
+ * @DataSource(DataSourceEnum.MASTER)
+ * public void queryMasterData() {
+ *     // 数据库操作将使用 master 数据源
+ * }
+ * </pre>
+ * <pre>
+ * @DataSource(DataSourceEnum.SLAVE)
+ * @Service
+ * public class SlaveService {
+ *     // 该类中所有方法默认使用 slave 数据源
+ * }
+ * </pre>
+ */
+@Slf4j
+@Aspect
+@Order(0)
+@Component
+public class DataSourceAspect {
+
+    protected Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Autowired
+    private DynamicDataSource dynamicDataSource;
+
+    /**
+     * 定义切点:匹配带有 @DataSource 注解的方法 或 类
+     * <p>
+     * - `@annotation(com.poyee.annotation.DataSource)`:方法上带有 @DataSource 注解
+     * - `@within(com.poyee.annotation.DataSource)`:类上带有 @DataSource 注解
+     */
+    @Pointcut("@annotation(com.poyee.annotation.ds.DataSource)" + "|| @within(com.poyee.annotation.ds.DataSource)")
+    public void dsPointCut() {
+        // 切点方法,用于绑定切点表达式
+    }
+
+    /**
+     * 环绕通知:在目标方法执行前后切换数据源
+     * <p>
+     * 1. 获取目标方法或类上的 @DataSource 注解
+     * 2. 根据注解值设置当前线程的数据源
+     * 3. 执行目标方法
+     * 4. 清理数据源上下文,防止线程复用导致数据源混乱
+     *
+     * @param point 切面连接点(包含目标方法信息)
+     * @return 方法执行结果
+     * @throws Throwable 抛出异常
+     */
+    @Around("dsPointCut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable {
+        DataSource dataSource = getDataSource(point);
+
+        if (StringUtils.isNotNull(dataSource)) {
+            String name = dataSource.value().name();
+            log.info("Switching to data source: {}", name);
+
+            dynamicDataSource.initDataSource(name);
+            DataSourceContextHolder.setDataSource(name);
+        }
+
+        try {
+            return point.proceed();
+        } finally {
+            DataSourceContextHolder.clearDataSource();
+        }
+    }
+
+    /**
+     * 获取目标方法或类上的 @DataSource 注解
+     * <p>
+     * 先从方法上查找注解,如果不存在则从类上查找
+     *
+     * @param point 切面连接点
+     * @return DataSource 注解对象(如果存在),否则为 null
+     */
+    public DataSource getDataSource(ProceedingJoinPoint point) {
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
+        if (Objects.nonNull(dataSource)) {
+            return dataSource;
+        }
+        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
+    }
+}

+ 126 - 0
py-base/src/main/java/com/poyee/aspectj/DesensitAspect.java

@@ -0,0 +1,126 @@
+package com.poyee.aspectj;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.poyee.annotation.Desensit;
+import com.poyee.annotation.DesensitMethod;
+import com.poyee.base.dto.Result;
+import com.poyee.base.entity.BaseEntity;
+import com.poyee.common.enums.DesensitType;
+import com.poyee.common.exception.BaseException;
+import com.poyee.util.StringUtils;
+import com.poyee.util.security.Md5Utils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ */
+@Slf4j
+@Aspect
+@Component
+public class DesensitAspect {
+
+    private static Object setDesensitData(Object obj) {
+        JSONObject json = new JSONObject();
+        try {
+            if (null == obj) {
+                return null;
+            }
+            if (obj.getClass().getName().indexOf("ArrayList") > 0) {
+                List<Object> objlist = (List<Object>) obj;
+                JSONArray jsonArray = new JSONArray();
+                if (!objlist.isEmpty()) {
+                    objlist.forEach(item -> {
+                        jsonArray.add(setDesensitData(item));
+                    });
+                    return jsonArray;
+                }
+            } else {
+                json = (JSONObject) JSONObject.toJSON(obj);
+                JSONObject finalJson = json;
+                Arrays.stream(obj.getClass().getDeclaredFields()).forEach(field -> {
+                    field.setAccessible(true);
+                    Desensit annotation = field.getAnnotation(Desensit.class);
+                    if (null != annotation) {
+                        DesensitType type = annotation.type();
+                        if (type.equals(DesensitType.NULL)) {
+                            finalJson.remove(field.getName());
+                        } else if (type.equals(DesensitType.DEFAULT)) {
+                            //脱敏操作
+                            try {
+                                String value = StringUtils.desenstiseStr((String) field.get(obj), 4);
+                                finalJson.put(field.getName(), value);
+                            } catch (IllegalAccessException e) {
+                                throw new RuntimeException(e);
+                            }
+                        } else if (type.equals(DesensitType.MD5)) {
+                            //MD5加密
+                            try {
+                                finalJson.put(field.getName(), Md5Utils.hash((String) field.get(obj)));
+                            } catch (IllegalAccessException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
+                    }
+                });
+                BaseEntity.types.forEach(s -> {
+                    finalJson.remove(s);
+                });
+                json = finalJson;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new BaseException("数据转换异常[1001]!");
+        }
+        return json;
+    }
+
+    // 配置织入点
+    @Pointcut("@annotation(com.poyee.annotation.DesensitMethod)")
+    public void logPointCut() {
+    }
+
+    /**
+     * @param
+     */
+//    @AfterReturning("execution(public * com.poyee.*.service.impl.*(..))")
+    @AfterReturning(pointcut = "logPointCut()", returning = "result")
+    public Object doAfter(JoinPoint joinPoint, Result result) {
+        try {
+            DesensitMethod desensitMethod = getAnnotationLog(joinPoint);
+            if (desensitMethod.desensit()) {
+                Object obj = result.getData();
+                result.setData(setDesensitData(obj));
+            }
+            log.info(" 返回参数:{} ", result);
+            return result;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private DesensitMethod getAnnotationLog(JoinPoint joinPoint) throws Exception {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null) {
+            return method.getAnnotation(DesensitMethod.class);
+        }
+        return null;
+    }
+}

+ 134 - 0
py-base/src/main/java/com/poyee/aspectj/I18nTokenAspect.java

@@ -0,0 +1,134 @@
+package com.poyee.aspectj;
+
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.poyee.annotation.I18nToken;
+import com.poyee.base.dto.UserInfo;
+import com.poyee.common.exception.BusinessException;
+import com.poyee.framework.jwt.JwtUtils;
+import com.poyee.i18n.I18nUtils;
+import com.poyee.util.ServletUtils;
+import com.poyee.util.security.Base64Util;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.Objects;
+
+import static com.poyee.i18n.I18nMessageEnums.AUTH_USER_ERROR;
+
+@Slf4j
+@Aspect
+@Component
+public class I18nTokenAspect {
+
+    @Value("${user.info-url:https://coresvc-dev.hobbystocks.cn/user/}")
+    private String userInfoUrl;
+    
+    @Value("${spring.profiles.active:dev}")
+    private String activeProfile;
+    
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    
+    // 配置织入点
+    @Pointcut("@annotation(com.poyee.annotation.I18nToken)")
+    public void logPointCut() {
+    }
+    
+    /**
+     * 前置通知:处理I18nToken注解的权限检查和参数验证
+     */
+    @Before("logPointCut()")
+    public void doBefore(JoinPoint joinPoint) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        
+        // 头部信息打印(仅在开发环境)
+        if ("dev".equals(activeProfile) || "local".equals(activeProfile)) {
+            printHeaders(request);
+        }
+
+        // 检查用户权限和参数
+        validateUserPermissionAndParams(request, method );
+    }
+    
+    /**
+     * 打印请求头信息
+     */
+    private void printHeaders(HttpServletRequest request) {
+        try {
+            log.info(" ---- 头部信息打印开始 ---- ");
+            Enumeration<String> headerNames = request.getHeaderNames();
+            while (headerNames.hasMoreElements()) {
+                String name = headerNames.nextElement();
+                log.info(" -- 头部信息-- {} 值:{} ", name, request.getHeader(name));
+            }
+            log.info(" ---- 头部信息打印结束 ---- ");
+        } catch (Exception e) {
+            log.warn("打印头部信息失败: {}", e.getMessage());
+        }
+    }
+    
+
+    /**
+     * 验证用户权限和参数
+     */
+    private void validateUserPermissionAndParams(HttpServletRequest request, Method method) {
+        //获取I18nToken注解
+        I18nToken i18nToken = method.getAnnotation(I18nToken.class);
+        if (Objects.nonNull(i18nToken) && i18nToken.required()) {
+            //验证用户信息
+            String userInfoStr = request.getHeader("X-USER-BASE64");
+            userInfoStr = StringUtils.isNotEmpty(userInfoStr) ? userInfoStr : request.getHeader("x-user-base64");
+            boolean isAuthorization = false;
+            String authorization = "";
+            if(StringUtils.isEmpty(userInfoStr)){
+                log.error(" 未获取到用户信息  X-USER-BASE64  ");
+                authorization = request.getHeader("Authorization");
+                if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
+                    isAuthorization = true;
+                    authorization = authorization.replaceAll("Bearer ", "");
+                }
+                //未获取用户信息
+//                throw new AuthException(I18nUtils.get(RELOGIN));
+            }
+            //保存用户信息
+            ServletUtils.getSession().setAttribute("x-user-base64", userInfoStr);
+            String user = "";
+            try {
+                if(isAuthorization && StringUtils.isNotBlank(authorization)){
+                    user = JSONObject.toJSONString(JwtUtils.getTokenUserInfo(authorization));
+                }else {
+                    user = new String(Base64Util.decode(userInfoStr), StandardCharsets.UTF_8);
+                }
+                UserInfo userInfo = JSONObject.parseObject(user, UserInfo.class);
+                if(Objects.nonNull(userInfo) && Objects.nonNull(userInfo.getMerchantId())){
+                    userInfo.setMerchant(true);
+                }
+                //保存用户信息到session
+                ServletUtils.setUserInfo(userInfo);
+            }catch (JSONException jsonExe) {
+                log.error(" 转换json 异常 {} ", userInfoStr);
+                throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+            }
+            if (StringUtils.isEmpty(user)) {
+                throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+            }
+        }
+
+    }
+}

+ 207 - 0
py-base/src/main/java/com/poyee/aspectj/LogAspect.java

@@ -0,0 +1,207 @@
+package com.poyee.aspectj;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.support.spring.PropertyPreFilters;
+import com.poyee.annotation.Log;
+import com.poyee.common.enums.BusinessStatus;
+import com.poyee.framework.domain.SysOperLog;
+import com.poyee.framework.service.LocationService;
+import com.poyee.util.ObjectUtil;
+import com.poyee.util.ServletUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.*;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * 操作日志记录处理
+ *
+ * @author zheng
+ */
+@Aspect
+@Component
+public class LogAspect {
+
+    @Autowired
+    private LocationService locationService;
+    // 存储请求开始时间
+    private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
+
+    /**
+     * 排除敏感属性字段
+     */
+    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
+    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
+
+    // 配置织入点
+    @Pointcut("@annotation(com.poyee.annotation.Log)")
+    public void logPointCut() {
+    }
+    @Before("logPointCut()")
+    public void doBefore() {
+        startTimeThreadLocal.set(System.currentTimeMillis());
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
+        handleLog(joinPoint, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param e         异常
+     */
+    @AfterThrowing(value = "logPointCut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
+        handleLog(joinPoint, e, null);
+    }
+
+    /**
+     * 日志记录
+     * @param joinPoint
+     * @param e
+     * @param jsonResult
+     */
+    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
+        try {
+            // 获得注解
+            Log controllerLog = getAnnotationLog(joinPoint);
+            if (controllerLog == null) {
+                return;
+            }
+            // *========数据库日志=========*//
+            SysOperLog operLog = new SysOperLog();
+            operLog.setLanguage(LocaleContextHolder.getLocale().getLanguage());
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = ServletUtils.getIpAddress();
+            operLog.setOperIp(ip);
+            // 返回参数
+            operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000));
+            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
+            if (e != null) {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(controllerLog, operLog);
+            log.info("<<<< 接口请求 参数 >>>>>>> {}", operLog);
+            // 计算耗时
+            long duration = System.currentTimeMillis() - startTimeThreadLocal.get();
+            operLog.setDuration(duration);
+            // 打印日志
+            log.info("接口请求: [{}] 方法: [{}] 耗时: [{}ms] IP: [{}] 地点: [{}] 参数: [{}]",
+                     operLog.getOperUrl(),
+                     operLog.getMethod(),
+                     operLog.getDuration(),
+                     operLog.getOperIp(),
+                     operLog.getOperLocation(),
+                     operLog.getOperParam()
+            );
+            //异步执行
+            log.info("<<<< 异步执行获取ip开始 >>>>>>>");
+            long getIpStartTimestamp = System.currentTimeMillis();
+            asyncRecordLocation(operLog);
+            log.info("<<<< 异步执行获取ip耗时: [{}]ms >>>>>>>", System.currentTimeMillis() - getIpStartTimestamp);
+        } catch (Exception exp) {
+            // 记录本地异常日志
+            log.error("==前置通知异常==");
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     *
+     * @param log     日志
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(Log log, SysOperLog operLog) throws Exception {
+        // 设置action动作
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(log.title());
+        // 设置操作人类别
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (log.isSaveRequestData()) {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(operLog);
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     *
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(SysOperLog operLog) throws Exception {
+        Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();
+        if (!ObjectUtil.isEmpty(map)) {
+            PropertyPreFilters.MySimplePropertyPreFilter excludefilter = new PropertyPreFilters().addFilter();
+            excludefilter.addExcludes(EXCLUDE_PROPERTIES);
+            String params = JSONObject.toJSONString(map, excludefilter);
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+            log.info(" >>>请求日志<<< {}", operLog);
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null) {
+            return method.getAnnotation(Log.class);
+        }
+        return null;
+    }
+
+
+    /**
+     * 异步记录操作地点
+     * @param operLog
+     */
+    @Async("logTaskExecutor")
+    public void asyncRecordLocation(SysOperLog operLog) {
+        try {
+            String address = locationService.getLocationByIp(operLog.getOperIp());
+            operLog.setOperLocation(address);
+            log.info("<<<< 远程查询操作地点 >>>>>>> {}", address);
+
+            // 可选:保存操作日志到数据库
+            // logService.saveOperLog(operLog);
+        } catch (Exception e) {
+            log.warn("记录操作地点失败", e);
+        }
+    }
+}

+ 38 - 0
py-base/src/main/java/com/poyee/aspectj/MqConsumerAOP.java

@@ -0,0 +1,38 @@
+package com.poyee.aspectj;
+
+import org.slf4j.MDC;
+
+import java.util.UUID;
+
+/**
+ * 自定义aop
+ *
+ * @author Administrator
+ *
+ */
+//@Component
+//@Aspect
+//@Slf4j
+public class MqConsumerAOP {
+
+    private static final String TRACE_ID = "TRACE_ID";
+
+//    @Pointcut("@annotation(org.springframework.amqp.rabbit.annotation.RabbitListener)")
+    public void mqPointCut() {
+    }
+
+//    @Before("mqPointCut()")
+    public void mqConsumerBefore() {
+        String tid = UUID.randomUUID().toString().replace("-", "");
+        if(MDC.get(TRACE_ID)==null){
+            MDC.put(TRACE_ID, tid);
+        }
+    }
+
+//    @After("mqPointCut()")
+    public void mqConsumerAfter() {
+        MDC.remove(TRACE_ID);
+    }
+
+
+}

+ 305 - 0
py-base/src/main/java/com/poyee/aspectj/RedisCacheAspect.java

@@ -0,0 +1,305 @@
+package com.poyee.aspectj;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.poyee.annotation.RedisCache;
+import com.poyee.base.dto.BaseReq;
+import com.poyee.base.dto.Page;
+import com.poyee.base.dto.Result;
+import com.poyee.base.dto.UserInfo;
+import com.poyee.enums.CacheExpirePolicy;
+import com.poyee.util.DateUtils;
+import com.poyee.util.ServletUtils;
+import com.poyee.util.SpElUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ *  Redis 缓存切面
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(1)
+public class RedisCacheAspect {
+
+    private final RedisTemplate<String, Object> redisTemplate;
+
+    public RedisCacheAspect(RedisTemplate<String, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Around("@annotation(redisCache)")
+    public Object doAround(ProceedingJoinPoint joinPoint, RedisCache redisCache) throws Throwable {
+        String keyExpression = redisCache.key();
+        String keySource = redisCache.keySource();
+        CacheExpirePolicy expirePolicy = redisCache.expirePolicy();
+        long customExpireTime = redisCache.expireTime();
+
+        if (expirePolicy == CacheExpirePolicy.CUSTOM) {
+            expirePolicy.setExpireTime(customExpireTime);
+        }
+
+        // 构建上下文变量
+        Map<String, Object> contextVars = new HashMap<>();
+
+        //如果获取不到用户信息, 判断方法参数中 doCache 是否为 true,如果是则解析方法参数中数据 进行缓存, 否则直接返回结果
+        Object[] args = joinPoint.getArgs();
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method method = methodSignature.getMethod();
+        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
+        String[] paramNames = nameDiscoverer.getParameterNames(method);
+
+        // 检查方法参数中是否有 doCache 参数且为 true
+        boolean doCache = false;
+        Object arg = null;
+        for (int i = 0; i < args.length; i++) {
+            if (paramNames != null && i < paramNames.length) {
+                arg = args[i];
+                if(arg instanceof BaseReq && ((BaseReq) arg).isDoRedis()){
+                    doCache = true;
+                }
+            }
+        }
+
+        // 从指定 source 获取变量值(如 userContext)
+        switch (keySource) {
+            case "userContext":
+                //根据
+                List<String> strings = SpElUtils.extractLabels(keyExpression);
+                String keyWord = strings.get(0);
+                if(doCache && Objects.nonNull(arg)){
+                    Field declaredField = arg.getClass().getDeclaredField(keyWord);
+                    declaredField.setAccessible(true);
+                    Object valueObj = declaredField.get(arg);
+                    contextVars.put(keyWord, valueObj);
+                } else {
+                    //获取当前用户信息
+                    UserInfo userInfo = ServletUtils.getUserInfo();
+                    try {
+                        Class<?> aClass = userInfo.getClass();
+                        Field declaredField = aClass.getDeclaredField(keyWord);
+                        declaredField.setAccessible(true);
+                        Object valueObj = declaredField.get(userInfo);
+                        contextVars.put(keyWord, valueObj);
+                    } catch (Exception e) {
+
+                    }
+                }
+                break;
+            case "args":
+//                Object[] args = joinPoint.getArgs();
+//                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+//                Method method = methodSignature.getMethod();
+//                DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
+//                String[] paramNames = nameDiscoverer.getParameterNames(method);
+                for (int i = 0; i < args.length; i++) {
+                    if (paramNames != null && i < paramNames.length) {
+                        contextVars.put(paramNames[i], args[i]);
+                    }
+                }
+                break;
+            default:
+                // 自定义 source,可扩展
+                break;
+        }
+
+        // 解析 key
+        String finalKey = SpElUtils.parseKey(keyExpression, contextVars);
+
+        // 查询缓存
+        Object cachedValue = redisTemplate.opsForValue().get(finalKey);
+        if (cachedValue != null) {
+            //判断是否为JSONObject 进行转换
+            if(cachedValue instanceof String){
+                //获取过期时间
+                long expireTime = redisTemplate.getExpire(finalKey);
+                log.debug("缓存命中[剩余时间:{} s],返回结果:{}", expireTime,(cachedValue.toString().length() > 200 ? cachedValue.toString().substring(0, 200) +"..." : cachedValue.toString()));
+                //解析为json 并 取得 type 区分是 page 还是result
+                String type = "result";
+                if(cachedValue.toString().startsWith("{")){
+                    JSONObject jsonObject = JSONObject.parseObject((String) cachedValue);
+                    type = jsonObject.getString("type");
+                }
+                if(type.equals("page")){
+                    Page page = JSONObject.parseObject((String) cachedValue, Page.class, Feature.OrderedField);
+                    //设置缓存过期时间
+                    page.setRedisCacheTimes(expireTime);
+                    //如果缓存key 为空 则设置  key
+                    if(StringUtils.isBlank(page.getRedisCacheKey())){
+                        page.setRedisCacheKey(finalKey);
+                    }
+                    //如果是分页 则判断 结果是否排序 排序字段和规则, 对结果重新排序
+//                    doCheckSortByPageOrderBy(page);
+                    return page;
+                }else{
+                    Result result = JSONObject.parseObject((String) cachedValue, Result.class, Feature.OrderedField);
+                    //设置缓存过期时间
+                    result.setRedisCacheTimes(expireTime);
+                    //如果缓存key 为空 则设置  key
+                    if(StringUtils.isBlank(result.getRedisCacheKey())){
+                        result.setRedisCacheKey(finalKey);
+                    }
+                    return result;
+                }
+            }
+            return cachedValue;
+        }
+
+        // 执行目标方法
+        Object result = joinPoint.proceed();
+
+        // 判断返回值是否有效(非空、非异常)
+        if (result == null || result instanceof Boolean && !(Boolean) result) {
+            return result;
+        }
+        //处理 返回 code
+        if((result.getClass().getName().equals("com.poyee.base.dto.Result") || result.getClass().getName().equals("com.poyee.base.dto.Page") )
+                && result.getClass().getDeclaredField("code") != null){
+            try{
+                Field codeField = result.getClass().getDeclaredField("code");
+                codeField.setAccessible(true);
+                Object codeObj = codeField.get(result);
+                if(codeObj != null && Integer.parseInt(codeObj.toString()) != 200 && Integer.parseInt(codeObj.toString()) != 0 ){
+                    return result;
+                }
+            }catch (Exception e){
+                //忽略异常
+            }
+        }
+        // 设置缓存
+        long expireSeconds = expirePolicy.getExpireSeconds();
+        //设置 查询数据库操作时间
+        try{
+            Field codeField = result.getClass().getDeclaredField("lastOptTime");
+            codeField.setAccessible(true);
+            Object codeObj = codeField.get(result);
+            //设置当前时间为最后一次操作时间
+            if(codeObj == null){
+                codeField.set(result, DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
+            }
+            //设置缓存key
+            codeField = result.getClass().getDeclaredField("redisCacheKey");
+            codeField.setAccessible(true);
+            codeField.set(result, finalKey);
+            //设置缓存时间
+            codeField = result.getClass().getDeclaredField("redisCacheTimes");
+            codeField.setAccessible(true);
+            codeField.set(result, expirePolicy.getExpireSeconds());
+
+        }catch (Exception e){
+            log.error("设置 查询数据库操作时间 异常",e);
+        }
+        String jsonString = JSONObject.toJSONString(result);
+        if (expireSeconds > 0) {
+
+        } else {
+            // 默认设置缓存时间为1小时 (3600秒)
+            expireSeconds = 3600;
+        }
+        redisTemplate.opsForValue().set(finalKey, jsonString, expireSeconds, java.util.concurrent.TimeUnit.SECONDS);
+        //判断是否需要缓存key集合
+        if(redisCache.logKey()
+                && StringUtils.isNotBlank(redisCache.logKeyRule())
+                && !Objects.equals(redisCache.logKeyRule(),"key")){
+            String logKeyExpression = redisCache.logKeyRule();
+            // 解析 key
+            String finalLogKey = SpElUtils.parseKey(logKeyExpression, contextVars);
+            // redis 缓存 设置 list add 值
+            redisTemplate.opsForList()
+                         .leftPush(finalLogKey, finalKey);
+            // 设置列表过期时间,与缓存过期时间一致
+            redisTemplate.expire(finalLogKey, expireSeconds, java.util.concurrent.TimeUnit.SECONDS);
+        }
+        log.debug("缓存结果:{}", jsonString.length() > 200 ? jsonString.substring(0, 200) + "..." : jsonString);
+        return result;
+    }
+
+    private void doCheckSortByPageOrderBy(Page page) {
+        String orderByField = page.getOrderBy();
+        if(StringUtils.isNotBlank(orderByField)) {
+            List<?> rows = page.getRows();
+            boolean isDesc = page.isDesc(); // 是否降序
+
+            try {
+                rows.sort((o1, o2) -> {
+                    if (o1 == null || o2 == null) return 0;
+
+                    // 使用反射获取 orderByField 的值
+                    Object v1 = getFieldValue(o1, orderByField);
+                    Object v2 = getFieldValue(o2, orderByField);
+
+                    if (v1 == null && v2 == null) return 0;
+                    if (v1 == null) return isDesc ? 1 : -1;
+                    if (v2 == null) return isDesc ? -1 : 1;
+
+                    // 按类型安全比较
+                    int result;
+                    if (v1 instanceof Comparable && v2.getClass()
+                                                      .isAssignableFrom(v1.getClass())) {
+                        result = ((Comparable) v1).compareTo(v2);
+                    } else {
+                        // 转为字符串比较(兜底)
+                        result = v1.toString()
+                                   .compareTo(v2.toString());
+                    }
+
+                    return isDesc ? -result : result; // 反转符号实现降序
+                });
+            } catch (Exception e) {
+                log.warn("缓存分页数据排序失败,orderByField={}", orderByField, e);
+            }
+        }
+    }
+
+    // 使用反射获取字段值
+    private Object getFieldValue(Object obj, String fieldName) {
+        if (obj == null || StringUtils.isBlank(fieldName)) return null;
+
+        Class<?> clazz = obj.getClass();
+        String[] fields = fieldName.split("\\."); // 支持嵌套属性 a.b.c
+
+        try {
+            for (String field : fields) {
+                Field declaredField = findField(clazz, field);
+                if (declaredField == null) return null;
+                declaredField.setAccessible(true);
+                obj = declaredField.get(obj);
+                if (obj == null) break;
+                clazz = obj.getClass();
+            }
+            return obj;
+        } catch (Exception e) {
+            log.debug("获取对象 {} 字段 {} 值失败", obj.getClass().getSimpleName(), fieldName, e);
+            return null;
+        }
+    }
+
+    // 支持父类字段查找
+    private Field findField(Class<?> clazz, String name) {
+        while (clazz != null) {
+            try {
+                return clazz.getDeclaredField(name);
+            } catch (NoSuchFieldException e) {
+                clazz = clazz.getSuperclass();
+            }
+        }
+        return null;
+    }
+
+}

+ 211 - 0
py-base/src/main/java/com/poyee/aspectj/RedisLockAspect.java

@@ -0,0 +1,211 @@
+package com.poyee.aspectj;
+
+import com.poyee.annotation.RedisLock;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.redis.util.RedisUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.Signature;
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 分布式
+ * key 用例:@RedisLock(lockKey = "update_group_info_+#{groupInfoId}", waitTime = 30, leaseTime = -1)
+ * @author zheng
+ */
+@Slf4j
+@Aspect
+@Component
+public class RedisLockAspect {
+
+    @Resource(name = "masterTransactionManager")
+    private DataSourceTransactionManager transactionManager;
+    @Autowired
+    private RedisUtils redisUtils;
+
+    public static final String PREFIX = "#{";
+    public static final String SUBFIX = "}";
+
+    // 配置织入点
+    @Pointcut("@annotation(com.poyee.annotation.RedisLock)")
+    public void lockPointCut() {
+    }
+
+    @Around("com.poyee.aspectj.RedisLockAspect.lockPointCut()")
+    public Object lockAround(ProceedingJoinPoint pjp) throws Throwable {
+        Signature signature = pjp.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method targetMethod = methodSignature.getMethod();
+        RedisLock annotation = targetMethod.getAnnotation(RedisLock.class);
+        String lockKey = annotation.lockKey();
+        lockKey= generateKeyBySpEL(lockKey,pjp);
+        log.debug("redis key:{}",lockKey);
+        TimeUnit unit = annotation.unit();
+        long waitTime = annotation.waitTime();
+        long leaseTime = annotation.leaseTime();
+        boolean useTran = annotation.isUseTran();
+
+        TransactionStatus status=null;
+        boolean isLock=false;
+        try {
+            isLock = redisUtils.tryLock(lockKey, unit, waitTime, leaseTime);
+            if (isLock) {
+                Object proceed;
+                if(useTran) {
+                    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+                    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+                    def.setIsolationLevel(2);
+                    status = transactionManager.getTransaction(def);
+                    proceed = pjp.proceed();
+                    transactionManager.commit(status);
+                }else {
+                     proceed = pjp.proceed();
+                }
+                redisUtils.unlock(lockKey);
+                isLock=false;
+                return proceed;
+            }
+        }catch (ServiceException e) {
+            log.error("业务处理异常,{}", e);
+            if(status!=null){
+                transactionManager.rollback(status);
+            }
+            throw e;
+        } catch (Exception e) {
+            log.error("分布式锁执行异常,{}", e);
+            if(status!=null){
+                transactionManager.rollback(status);
+            }
+        } finally {
+            try {
+                if(isLock){
+                    redisUtils.unlock(lockKey);
+                }
+            } catch (Exception e) {
+                log.error("分布式锁释放异常,{}", e);
+            }
+        }
+        throw new ServiceException(500,"lock失败");
+    }
+
+
+    /**
+     * @param contextEl 带表达式的内容
+     *                  举例: "用户名:"+#{user.name}+用户年龄:+#{user.age}
+     *                  文字+#{map.name} ,不支持对象中嵌套map 如:{user.map.name}
+     *                  最多支持三个变量 #{user.data.phone}
+     * @param joinPoint 环绕切面 pjp
+     * @return
+     */
+    public static String generateKeyBySpEL(String contextEl, JoinPoint joinPoint) {
+        try {
+            StringBuilder context = new StringBuilder();
+            if (contextEl.contains(PREFIX) && contextEl.contains(SUBFIX) && contextEl.contains("+")) {
+                String[] strings = (contextEl.split("\\+"));
+                for (String s : strings) {
+                    if (s.contains(PREFIX) && s.contains(SUBFIX)) {
+                        String spel = s.substring(s.indexOf(PREFIX) + PREFIX.length(), s.lastIndexOf(SUBFIX));
+                        context.append((resolverExpression(spel, joinPoint)));
+                    } else {
+                        context.append(s);
+                    }
+                }
+            }
+            return context.toString();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String resolverExpression(String expression, JoinPoint joinPoint) throws Exception {
+        String first = null;
+        String second = null;
+        String third = null;
+        if (expression.contains(".")) {
+            List<String> list = Arrays.asList((expression.split("\\.")));
+            if (list.size() > 3) {
+                throw new ServiceException(500, "不支持三个变量以上表达式" + expression);
+            }
+            for (int i = 0; i < list.size(); i++) {
+                if (i == 0) {
+                    first = list.get(0);
+                }
+                if (i == 1) {
+                    second = list.get(1);
+                }
+                if (i == 2) {
+                    third = list.get(2);
+                }
+            }
+        } else {
+            first = expression;
+        }
+
+        Object[] args = joinPoint.getArgs();
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method method = methodSignature.getMethod();
+        Class<?>[] parameterTypes = method.getParameterTypes();
+        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
+        String[] paramNames = nameDiscoverer.getParameterNames(method);
+        for (int i = 0; i < paramNames.length; i++) {
+            Class<?> parameterType = parameterTypes[i];
+            String paramName = paramNames[i];
+            Object paramValue = args[i];
+            if (first.equals(paramName)) {
+                if (second == null) {
+                    return paramValue.toString();
+                }
+                if (second != null) {
+                    if (paramValue instanceof Map<?, ?>) {
+                        return (String) (((Map<?, ?>) paramValue).get(second));
+                    } else {
+                        return String.valueOf(getFieldValue(paramValue, second + (third != null ? "." + third : "")));
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private static Object getFieldValue(Object target, String fieldName) throws Exception {
+        Class<?> clazz = target.getClass();
+        String[] fs = fieldName.split("\\.");
+        try {
+            for (int i = 0; i < fs.length-1; i++) {
+                Field f = clazz.getDeclaredField(fs[i]);
+                f.setAccessible(true);
+                target = f.get(target);
+                if (target != null) {
+                    return null;
+                }
+                clazz = target.getClass();
+            }
+            Field f = clazz.getDeclaredField(fs[fs.length - 1]);
+            f.setAccessible(true);
+            return f.get(target);
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+
+}

+ 166 - 0
py-base/src/main/java/com/poyee/aspectj/UserLoginTokenAspect.java

@@ -0,0 +1,166 @@
+package com.poyee.aspectj;
+
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.poyee.base.dto.AppBaseUser;
+import com.poyee.base.dto.UserInfo;
+import com.poyee.common.auth.UserRoleUtil;
+import com.poyee.common.enums.DataAuth;
+import com.poyee.common.enums.Roles;
+import com.poyee.common.exception.AuthException;
+import com.poyee.common.exception.BusinessException;
+import com.poyee.framework.jwt.JwtUtils;
+import com.poyee.annotation.UserLoginToken;
+import com.poyee.i18n.I18nUtils;
+import com.poyee.util.ServletUtils;
+import com.poyee.util.http.HttpClient;
+import com.poyee.util.security.Base64Util;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static com.poyee.i18n.I18nMessageEnums.*;
+
+/**
+ * 用户登录切面
+ */
+@Slf4j
+@Aspect
+@Component
+public class UserLoginTokenAspect {
+
+    @Value("${user.info-url:https://coresvc-dev.hobbystocks.cn/user/}")
+    private String userInfoUrl;
+    @Value("${whitelist.id:0}")
+    private String whitelistId;
+    // 配置织入点
+    @Pointcut("@annotation(com.poyee.annotation.UserLoginToken)")
+    public void logPointCut() {
+    }
+
+
+    /**
+     * @param
+     */
+    @Before("execution(public * com.poyee.*controller.*.*(..))")
+    public void doBefore(JoinPoint joinPoint) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        String userInfoStr = request.getHeader("X-USER-BASE64");
+        userInfoStr = StringUtils.isNotEmpty(userInfoStr) ? userInfoStr : request.getHeader("x-user-base64");
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        log.info(" X-USER-BASE64 >>> {}",userInfoStr);
+        String authorization = request.getHeader("Authorization");
+        log.info(" authorization >>> {} ",authorization);
+
+        //设置 国际语言
+        com.poyee.util.ServletUtils.getSession().setAttribute("i18n", false);
+        //检查是否需要 UserLoginToken 认证用户信息
+        //检查用户权限
+        checkUserLoginToken(request, userInfoStr, method);
+    }
+    /**
+     * 检查用户权限
+     * @param request
+     * @param userInfoStr
+     * @param method
+     */
+    private void checkUserLoginToken(HttpServletRequest request, String userInfoStr, Method method) {
+        if (method.isAnnotationPresent(UserLoginToken.class)) {
+            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
+            if (userLoginToken.required()) {
+                boolean isAuthorization = false;
+                if (org.apache.commons.lang3.StringUtils.isEmpty(userInfoStr)) {
+                    try {
+                        userInfoStr = request.getHeader("authorization");
+                        if (userInfoStr.startsWith("Bearer ")) {
+                            isAuthorization = true;
+                            userInfoStr = userInfoStr.replaceAll("Bearer ", "");
+                        }
+                    }catch (Exception e){
+                        log.error(" 获取token 异常 {} ", userInfoStr);
+                        throw new BusinessException(401, I18nUtils.get(AUTH_USER_ERROR, "401"));
+                    }
+                }
+                //保存用户信息
+                com.poyee.util.ServletUtils.getSession().setAttribute("x-user-base64", userInfoStr);
+                String user = "";
+                try {
+                    if(isAuthorization){
+                        user = JSONObject.toJSONString(JwtUtils.getTokenUserInfo(userInfoStr));
+                    }else {
+                        user = new String(Base64Util.decode(userInfoStr), StandardCharsets.UTF_8);
+                        JSONObject.parseObject(user);
+                    }
+                } catch (JSONException jsonExe) {
+                    log.error(" 转换json 异常 {} ", userInfoStr);
+                    //使用jwt进行获取用户信息
+                    user = JSONObject.toJSONString(JwtUtils.getTokenUserInfo(userInfoStr));
+                } catch (IllegalArgumentException ie) {
+                    log.error(" token bease64解析 异常 {} ", userInfoStr);
+                    try {
+                        user = new String(Base64Util.uriDecode(userInfoStr));
+                        log.error(" bease64解析 异常>> 使用uri 解析 》》》{}", user);
+                    } catch (IllegalArgumentException ie1) {
+                        log.error(" uriBease64解析 异常>> 使用uri 解析 》》》 {}", userInfoStr);
+                        throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+                    }
+                }
+                if (org.apache.commons.lang3.StringUtils.isEmpty(user)) {
+                    throw new BusinessException(402, I18nUtils.get(AUTH_USER_ERROR, ",请重新登录[402]"));
+                }
+                request.getSession().setAttribute("userInfo", user);
+            }
+            if (userLoginToken.faceVerify()) {
+                if (!getAppUserInfo()) {
+                    throw new BusinessException(403, I18nUtils.get(AUTH_USER_NOT_REAL_NAME, ",请先进行实名认证[403]"));
+                }
+            }
+            //权限判断
+            if (!UserRoleUtil.builder(whitelistId).checkRole(userLoginToken.pass_aud(), Arrays.asList(userLoginToken.roles()))) {
+                throw new BusinessException(403, I18nUtils.get(AUTH_USER_ERROR, "[403]"));
+            }
+        }
+    }
+
+    /**
+     * @return
+     */
+    private boolean getAppUserInfo() {
+        com.poyee.base.dto.UserInfo userInfo = com.poyee.util.ServletUtils.getUserInfo();
+        if (null == userInfo || null == userInfo.getId()) {
+            throw new BusinessException( I18nUtils.get(RELOGIN, "[405]"));
+        }
+        String str = HttpClient.get(userInfoUrl + userInfo.getId());
+        log.info(" 调用接口查询用户详情信息》》》{} ", str);
+        try {
+            AppBaseUser appBaseUser = JSONObject.parseObject(str, AppBaseUser.class);
+            if (1 != appBaseUser.getFaceVerify()) {
+                throw new BusinessException(403, I18nUtils.get(AUTH_USER_NOT_REAL_NAME, "[403]"));
+            }
+            com.poyee.util.ServletUtils.getSession().setAttribute("appBaseUser", appBaseUser);
+        } catch (Exception e) {
+//            throw new BusinessException(403, "请先进行实名认证[403]!");
+        }
+        return true;
+    }
+
+}

+ 90 - 0
py-base/src/main/java/com/poyee/aspectj/i18nAspect.java

@@ -0,0 +1,90 @@
+package com.poyee.aspectj;
+
+import com.poyee.annotation.I18n;
+import com.poyee.common.enums.I18nFormat;
+import com.poyee.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.lang.reflect.Method;
+
+/**
+ * 国际化切面类,用于处理国际化相关的逻辑
+ */
+@Slf4j
+@Aspect
+@Component
+public class i18nAspect {
+
+    // 配置织入点,针对使用了i18n注解的方法
+    @Pointcut("@annotation(com.poyee.annotation.I18n)")
+    public void logPointCut() {
+    }
+
+    /**
+     * 在控制器的方法执行前,处理国际化逻辑
+     * @param joinPoint 切入点对象,包含被拦截的方法信息
+     */
+    @Before("execution(public * com.poyee.controller.*.*(..))")
+//    @Before("logPointCut()")
+    public void doBefore(JoinPoint joinPoint) {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        checkI18n(method);
+    }
+
+    /**
+     * 判断是否支持国际化,并根据情况设置会话属性
+     * @param method 被拦截的方法对象,用于获取注解信息
+     */
+    private void checkI18n(Method method) {
+        if (method.isAnnotationPresent(I18n.class)) {
+            I18n i18n = method.getAnnotation(I18n.class);
+            HttpServletRequest request = ServletUtils.getRequest();
+            String language = request.getHeader("Accept-Language");
+            boolean isI18n = true;
+            if(StringUtils.isBlank(language)) {
+                language = LocaleContextHolder.getLocale().getLanguage();
+                isI18n = false;
+            }
+
+            try {
+                HttpSession session = ServletUtils.getSession(false);
+                if (session == null) {
+                    log.warn("Session is null, cannot set I18n attributes.");
+                    return;
+                }
+
+                // 根据方法上的i18n注解和请求头中的语言信息,决定是否启用国际化
+                if(i18n.value() && isI18n) {
+                    session.setAttribute("language", language);
+                    I18nFormat[] format = i18n.format();
+                    if(format.length > 0) {
+                        session.setAttribute("i18nFormat", i18n.format());
+                        if (log.isInfoEnabled()) {
+                            log.info("i18nFormat:{}", format[0].getMsg());
+                        }
+                    }
+                    session.setAttribute("i18n", true);
+                } else {
+                    // 如果 I18n.value() 为 false,但 isI18n 为 true,仍不启用国际化
+                    session.setAttribute("i18n", false);
+                }
+            } catch (Exception e) {
+                log.error("Failed to set I18n attributes", e);
+            }
+        }
+    }
+
+}

+ 15 - 0
py-base/src/main/java/com/poyee/base/controller/ActuatorController.java

@@ -0,0 +1,15 @@
+package com.poyee.base.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/actuator")
+public class ActuatorController {
+
+    @RequestMapping("/health")
+    public String health() {
+        return "ok";
+    }
+
+}

+ 98 - 0
py-base/src/main/java/com/poyee/base/controller/BaseController.java

@@ -0,0 +1,98 @@
+package com.poyee.base.controller;
+
+import com.poyee.base.dto.BaseDto;
+import com.poyee.base.dto.BaseReq;
+import com.poyee.base.dto.UserInfo;
+import com.poyee.base.service.BaseService;
+import com.poyee.common.exception.AuthException;
+import com.poyee.i18n.I18nUtils;
+import com.poyee.util.DateUtils;
+import com.poyee.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.Objects;
+
+import static com.poyee.i18n.I18nMessageEnums.GET_USER_INFO_ERROR;
+import static com.poyee.i18n.I18nMessageEnums.NO_PERMISSION;
+
+/**
+ *
+ */
+@Slf4j
+public abstract class BaseController<S extends BaseService<T, R>, T extends BaseReq, R extends BaseDto> {
+
+    @Autowired
+    protected S baseService;
+
+    /**
+     * 日志
+     * @param label
+     * @param obj
+     */
+    public void console(String label, Object obj ){
+        //当前操作用户 及 角色 ,操作时间
+        String userName = "游客";
+        String roleCode = "游客";
+        try{
+            UserInfo userInfo = ServletUtils.getUserInfo();
+            String name = StringUtils.isNotBlank(userInfo.getDisplayName()) ? userInfo.getDisplayName() : userInfo.getSub();
+            if(StringUtils.isBlank(name)){
+                userName = StringUtils.isNotBlank(userInfo.getUserId()) ? "用户id:"+userInfo.getUserId() : userName;
+            }
+            String role = userInfo.getRoleCode();
+            if (StringUtils.isBlank(role)) {
+                roleCode = userInfo.isMerchant() ? "商户" : roleCode;
+            }
+        } catch (Exception e) {
+            log.error("当前用户[{}] 角色【{}】获取用户信息异常", userName, roleCode, e);
+        } finally {
+            log.info("{} {}\n" + " 当前用户[{}] 角色【{}】操作时间[{}] ", label, obj,
+                     userName, roleCode , DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date()));
+        }
+    }
+
+    /**
+     * 检查商户权限
+     * @param p
+     * @param <P>
+     */
+    public <P extends BaseReq> void checkAndSetMerchantId(P p){
+        try {
+            UserInfo userInfo = ServletUtils.getUserInfo();
+            if(userInfo.isMerchant()) {
+                //解析 p 中的user_id
+                if (Objects.nonNull(p)) {
+                    Field merchantId = p.getClass().getDeclaredField("merchantId");
+                    if (Objects.nonNull(merchantId)) {
+                        merchantId.setAccessible(true);
+                        String typeName = merchantId.getType().getName();
+                        if (Objects.equals("java.lang.Long", typeName)) {
+                            merchantId.set(p, Long.valueOf(userInfo.getMerchantId()));
+                        } else if (Objects.equals("java.lang.Integer", typeName)) {
+                            merchantId.set(p, userInfo.getMerchantId());
+                        }
+                    }
+                }
+            }
+        } catch (NoSuchFieldException ne) {
+            log.error("请求参数中无userId",ne);
+            throw new AuthException(402, I18nUtils.get(NO_PERMISSION, "【权限不足】"));
+        } catch (Exception e) {
+            log.error("获取用户信息失败",e);
+            throw new AuthException(I18nUtils.get(GET_USER_INFO_ERROR));
+        }
+    }
+    /**
+     * 获取request
+     */
+    public HttpServletRequest getRequest() {
+        return ServletUtils.getRequest();
+    }
+
+
+}

+ 154 - 0
py-base/src/main/java/com/poyee/base/dto/AppBaseUser.java

@@ -0,0 +1,154 @@
+package com.poyee.base.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ *
+ * </p>
+ *
+ * @author lsz
+ * @since 2022-12-09
+ */
+@Data
+@ApiModel(value = "AppBaseUser对象", description = "")
+public class AppBaseUser implements Serializable {
+
+    @ApiModelProperty("id")
+    private Integer id;
+
+    @ApiModelProperty("所属程序")
+    private String appid;
+
+    @ApiModelProperty("真实姓名")
+    private String realname;
+
+    @ApiModelProperty("昵称")
+    private String nickname;
+
+    @ApiModelProperty("头像")
+    private String avatar;
+
+    @ApiModelProperty("积分")
+    private Long point;
+
+    @ApiModelProperty("会员等级")
+    private Integer level;
+
+    @ApiModelProperty("生日")
+    private Date birthday;
+
+    @ApiModelProperty("性别")
+    private Integer sex;
+
+    @ApiModelProperty("openid")
+    private String openid;
+
+    @ApiModelProperty("unionid")
+    private String unionid;
+
+    @ApiModelProperty("注册渠道")
+    private String registerChannel;
+
+    @ApiModelProperty("状态")
+    private Integer status;
+
+    @ApiModelProperty("删除标记")
+    private Integer delFlg;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty("创建人")
+    private String createBy;
+
+    @ApiModelProperty("创建时间")
+    private Date createTime;
+
+    @ApiModelProperty("更新人")
+    private String updateBy;
+
+    @ApiModelProperty("更新时间")
+    private Date updateTime;
+
+    @ApiModelProperty("账号")
+    private String username;
+
+    @ApiModelProperty("会员成长值")
+    private Integer growthNum;
+
+    @ApiModelProperty("会员码")
+    @TableField("code")
+    private String code;
+
+    @ApiModelProperty("是否接受通知消息,1接受,0拒绝")
+    private Integer notifyFlag;
+
+    @ApiModelProperty("app极光注册id")
+    private String smsRegisterId;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    @TableField("notify_type")
+    private String notifyType;
+
+    @ApiModelProperty("通过人脸识别标志:1")
+    private Integer faceVerify;
+
+    @ApiModelProperty("支付开关")
+    private Integer openPsd;
+
+    @ApiModelProperty("支付密码")
+    private String payPsd;
+
+    @ApiModelProperty("登陆密码")
+    private String loginPsd;
+
+    @ApiModelProperty("是否拒绝自提")
+    private Integer refusePickUp;
+
+    @ApiModelProperty("备用")
+    private String prop1;
+
+    @ApiModelProperty("备用")
+    private String prop2;
+
+    @ApiModelProperty("备用")
+    private String prop3;
+
+    @ApiModelProperty("备用")
+    private String prop4;
+
+    @ApiModelProperty("悬浮窗口开关")
+    private Integer windowOpen;
+
+    @ApiModelProperty("身份证姓名")
+    private String certName;
+
+    @ApiModelProperty("支付宝账号")
+    private String alipayAccount;
+
+    @ApiModelProperty("开票权限")
+    private Integer openInvoice;
+
+    @ApiModelProperty("编辑黑名单")
+    private Integer blacklist;
+
+    @ApiModelProperty("身份证号")
+    private String idCard;
+
+    /**
+     * 用户身份证信息
+     **/
+    @ApiModelProperty("用户身份证信息")
+    private String userCertData;
+    private String phone;
+
+}

+ 19 - 0
py-base/src/main/java/com/poyee/base/dto/BaseDto.java

@@ -0,0 +1,19 @@
+package com.poyee.base.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 出参
+ */
+@Data
+@ApiModel(description = "基础数据输出对象")
+public class BaseDto {
+
+    @ApiModelProperty(value = "data_source" ,hidden = true)
+    @TableField(value = "data_source",exist = false)
+    private String dataSource;
+
+}

+ 71 - 0
py-base/src/main/java/com/poyee/base/dto/BaseReq.java

@@ -0,0 +1,71 @@
+package com.poyee.base.dto;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 入参
+ */
+@ApiModel(description = "基础数据输入对象")
+@Data
+public class BaseReq {
+
+    @ApiModelProperty(value = "数据源",example = "slave", required = true,hidden = true)
+    @TableField(exist = false)
+    private String dbVersion;
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    public static List<String> types = Arrays.asList("pageNo", "pageSize", "sidx", "sord", "orderBy", "limit", "desensit");
+    @ApiModelProperty(value = "页码,查询时使用", notes = "查询时使用", reference = "1",example = "1")
+    @TableField(exist = false)
+    private Integer pageNo = 1;
+    @ApiModelProperty(value = "每页数量,查询时使用", notes = "查询时使用", reference = "10", example = "10")
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+    @ApiModelProperty(value = "排序字段,查询时使用", notes = "查询时使用", reference = " createTime ")
+    @TableField(exist = false)
+    private String sidx;
+    @ApiModelProperty(value = "排序规则,查询时使用", notes = "查询时使用", reference = " ase ")
+    @TableField(exist = false)
+    private String sord;
+    @ApiModelProperty(value = "排序方式",hidden = true)
+    @TableField(exist = false)
+    private String orderBy;
+    @ApiModelProperty(value = "时间戳",hidden = true)
+    @TableField(exist = false)
+    private Long timestamp;
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private Integer limit;
+
+    @ApiModelProperty(value = "创建时间", hidden = true)
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    private Date createTime;
+    @ApiModelProperty(value = "创建人", hidden = true)
+    @TableField(value = "create_by", fill = FieldFill.INSERT)
+    private String createBy;
+    @ApiModelProperty(value = "更新时间", hidden = true)
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+    @ApiModelProperty(value = "更新人", hidden = true)
+    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+    @ApiModelProperty(value = "是否执行redis缓存:定时缓存处理标记不验证用户信息",hidden = true)
+    @TableField(exist = false)
+    private boolean doRedis;
+    @ApiModelProperty(value = "服务方法名称:定时缓存处理时记录方法",hidden = true)
+    @TableField(exist = false)
+    private String serviceMethodName;
+
+    public Integer getLimit() {
+        return (pageNo - 1) * pageSize;
+    }
+
+}

+ 28 - 0
py-base/src/main/java/com/poyee/base/dto/MpjWrapper.java

@@ -0,0 +1,28 @@
+package com.poyee.base.dto;
+
+import com.poyee.annotation.MpjWapper;
+import com.poyee.util.ObjectUtil;
+import lombok.Data;
+
+/**
+ *
+ */
+@Data
+public class MpjWrapper {
+
+    private MpjWapper field;
+
+    private String data;
+
+    public MpjWrapper builder(MpjWapper field,Object data) {
+        if(this.data!=null){
+            //data 使用 逗号拼接
+            this.data = ObjectUtil.trim(this.data+","+data);
+        }else{
+            this.data = ObjectUtil.trim(data.toString());
+        }
+        this.field = field;
+        return this;
+    }
+
+}

+ 91 - 0
py-base/src/main/java/com/poyee/base/dto/Page.java

@@ -0,0 +1,91 @@
+package com.poyee.base.dto;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ *
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Page<T>  {
+    @ApiModelProperty(value = "返回结果 封装类")
+    private String type = "page";
+
+    @ApiModelProperty(value = "状态码:0=正常")
+    private Integer code;
+    @ApiModelProperty(value = "总记录数")
+    private Long total;
+    @ApiModelProperty(value = "数据列表")
+    private List<T> rows;
+    @ApiModelProperty(value = "当前页码")
+    private Long pageNo;
+    @ApiModelProperty(value = "当前页条数")
+    private Long size;
+
+    @ApiModelProperty(value = "最后操作时间")
+    private String lastOptTime;
+    //缓存key
+    @ApiModelProperty(value = "缓存key")
+    private String redisCacheKey;
+    //缓存时间
+    @ApiModelProperty(value = "缓存时间:s")
+    private Long redisCacheTimes;
+    //排序字段
+    @ApiModelProperty(value = "排序字段",hidden = true)
+    private String orderBy;
+    @ApiModelProperty(value = "排序字段是否降序",hidden = true)
+    private boolean  isDesc;
+
+    public Page(Long total, List<T> rows) {
+        this.code = 200;
+        this.rows = rows;
+        if(Objects.nonNull(total)){
+            this.total = total;
+        }else if (Objects.nonNull(rows)) {
+            this.total = (long) rows.size();
+        }
+    }
+
+    public Page(List<T> rows) {
+        this.rows = rows;
+        if (Objects.nonNull(rows)) {
+            this.total = (long) rows.size();
+        }
+    }
+
+    public Page<T> setRows(List<T> rows) {
+        this.rows = rows;
+        return this;
+    }
+
+    public Page<T> setTotal(Long total) {
+        this.total = total;
+        return this;
+    }
+
+    public Page<T> setPage(Long pageNo) {
+        this.pageNo = pageNo;
+        this.size = Objects.nonNull(rows)?(long) rows.size():0;
+        return this;
+    }
+
+    public static <R extends BaseDto> Page<R> iPage(IPage<R> iPage) {
+        Page<R> r = new Page<>();
+        r.setCode(200);
+        r.setTotal(iPage.getTotal());
+        if(iPage.getTotal() > 0){
+            r.setRows(iPage.getRecords());
+        }
+        r.setTotal(iPage.getTotal());
+        return r;
+    }
+}

+ 176 - 0
py-base/src/main/java/com/poyee/base/dto/Result.java

@@ -0,0 +1,176 @@
+package com.poyee.base.dto;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.poyee.constant.ResultCode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ *
+ */
+@ApiModel(description = "返回结果 封装类")
+@AllArgsConstructor
+@Data
+public class Result<T> {
+
+    @ApiModelProperty(value = "返回结果 封装类")
+    private String type = "result";
+    /**
+     * @see ResultCode
+     */
+    @ApiModelProperty(value = "状态码",example = "200=成功,400=失败,401=未认证,404=接口不存在,500=服务器内部错误")
+    private Integer code;
+
+    @ApiModelProperty(value = "状态描述")
+    private String msg;
+
+    @ApiModelProperty(value = "返回对象数据")
+    private Object data;
+
+    @ApiModelProperty(value = "分页数据")
+    private Page<T> page;
+    //最后一次操作时间
+    @ApiModelProperty(value = "最后一次操作时间")
+    private String lastOptTime;
+    //缓存key
+    @ApiModelProperty(value = "缓存key")
+    private String redisCacheKey;
+    //缓存时间
+    @ApiModelProperty(value = "缓存时间:s")
+    private Long redisCacheTimes;
+
+    public Result(){
+        this.code = ResultCode.SUCCESS.getCode();
+        this.msg = ResultCode.SUCCESS.getMsg();
+    }
+
+    /**
+     * @param <T>
+     * @return
+     */
+    public static <T> Result<T> success() {
+        Result<T> result = new Result<>();
+        result.setCode(ResultCode.SUCCESS.getCode());
+        result.setMsg(ResultCode.SUCCESS.getMsg());
+        return result;
+    }
+
+    /**
+     * @param data
+     * @param <T>
+     * @return
+     */
+    public static <T> Result<T> success(Object data) {
+        Result<T> result = new Result<>();
+        result.setCode(ResultCode.SUCCESS.getCode());
+        result.setMsg(ResultCode.SUCCESS.getMsg());
+        result.setData(data);
+        return result;
+    }
+
+    /**
+     * @param rows
+     * @param <T>
+     * @return
+     */
+    public static <T> Result<T> page(List<T> rows){
+        Result result = new Result();
+        result.setPage(new Page(rows));
+        return result;
+    }
+
+    /**
+     * @param page
+     * @param <T>
+     * @return
+     */
+    public static <T> Result<T> page(Page<T> page){
+        Result result = new Result();
+        result.setPage(page);
+        return result;
+    }
+
+
+    /**
+     * @param iPage
+     * @param <T>
+     * @return
+     */
+    public static <T> Result<T> iPage(IPage<T> iPage){
+        Result result = new Result();
+        if(Objects.nonNull(iPage)){
+            result.setPage(new Page(iPage.getTotal(),iPage.getRecords()));
+        }
+        return result;
+    }
+    /**
+     * @param total
+     * @param rows
+     * @param <T>
+     * @return
+     */
+    public static <T> Result<T> page(Long total,List<T> rows) {
+        Result result = new Result();
+        result.setPage(new Page(total,rows));
+        return result;
+    }
+
+    /**
+     * @param total
+     * @return
+     */
+    public Result<T> setTotal(Long total){
+        this.page = Optional.ofNullable(this.page).orElse(new Page<T>());
+        this.page.setTotal(total);
+        return this;
+    }
+
+    /**
+     * @return
+     */
+    public static Result error(){
+        Result result = new Result<>();
+        result.setCode(ResultCode.FAIL.getCode());
+        result.setMsg(ResultCode.FAIL.getMsg());
+        return result;
+    }
+
+    /**
+     * @param msg
+     * @return
+     */
+    public static Result error(String msg){
+        Result result = new Result<>();
+        result.setCode(ResultCode.FAIL.getCode());
+        result.setMsg(msg);
+        return result;
+    }
+
+    /**
+     * @param code
+     * @param msg
+     * @return
+     */
+    public static Result error(Integer code, String msg){
+        Result result = new Result<>();
+        result.setCode(code);
+        result.setMsg(msg);
+        return result;
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    public Result setData(Object data){
+        this.data = data;
+        return this;
+    }
+
+}

+ 73 - 0
py-base/src/main/java/com/poyee/base/dto/UserInfo.java

@@ -0,0 +1,73 @@
+package com.poyee.base.dto;
+
+import com.poyee.common.enums.DataAuth;
+import com.poyee.common.enums.Roles;
+import lombok.Data;
+import lombok.ToString;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 用户信息
+ */
+@Data
+@ToString
+public class UserInfo implements Serializable {
+
+    //id
+    private Integer id;
+    //用户id
+    private String userId;
+    //用户头像
+    private String avatar;
+    //角色编码
+    private String roleCode;
+    //角色编码
+    private String role;
+    //请求时间
+    private Long iat;
+    //过期时间
+    private Long exp;
+    //名称
+    private String displayName;
+
+    private String sub;
+
+    private String iss;
+    //来源
+    private String aud;
+
+    private String nbf;
+
+    private String isFaceVerify;
+
+    private List<String> scope;
+    //商家id
+    private Integer merchantId;
+    //商家头像
+    private String merchantAvatar;
+    //商家名称
+    private String merchantName;
+    /**
+     * 数据权限
+     */
+    private DataAuth dataAuth;
+
+    /**
+     * 是否是商家
+     */
+    private boolean merchant;
+    private String nickname;
+
+    private List<String> partnerRoles;
+
+    public boolean hasPartnerRoleAuth(Roles role){
+        if(CollectionUtils.isEmpty(partnerRoles)){
+            return false;
+        }
+        return partnerRoles.contains(role.getRoleCode());
+    }
+
+}

+ 43 - 0
py-base/src/main/java/com/poyee/base/entity/BaseEntity.java

@@ -0,0 +1,43 @@
+package com.poyee.base.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Data
+public class BaseEntity  {
+
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    public static List<String> types = Arrays.asList("pageNo", "pageSize", "sidx", "sord", "orderBy", "limit", "desensit");
+    @ApiModelProperty(value = "页码,查询时使用", notes = "查询时使用", reference = "1")
+    @TableField(exist = false)
+    private Integer pageNo = 1;
+    @ApiModelProperty(value = "每页数量,查询时使用", notes = "查询时使用", reference = "10")
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+    @ApiModelProperty(value = "排序字段,查询时使用", notes = "查询时使用", reference = " createTime ")
+    @TableField(exist = false)
+    private String sidx;
+    @ApiModelProperty(value = "排序规则,查询时使用", notes = "查询时使用", reference = " ase ")
+    @TableField(exist = false)
+    private String sord;
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private String orderBy;
+    @ApiModelProperty(hidden = true)
+    @TableField(exist = false)
+    private Integer limit;
+
+    public static boolean notIn(String type) {
+        return !types.contains(type);
+    }
+
+    public Integer getLimit() {
+        return (pageNo - 1) * pageSize;
+    }
+
+}

+ 14 - 0
py-base/src/main/java/com/poyee/base/mapper/IBaseMapper.java

@@ -0,0 +1,14 @@
+package com.poyee.base.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.github.yulichang.base.MPJBaseMapper;
+import com.poyee.base.dto.BaseDto;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ */
+@Mapper
+public interface IBaseMapper<D extends BaseDto> extends BaseMapper<D>, MPJBaseMapper<D> {
+
+}

+ 201 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/BaseProvider.java

@@ -0,0 +1,201 @@
+package com.poyee.base.mapper.provider;
+
+import com.poyee.base.dto.BaseDto;
+import com.poyee.base.dto.BaseReq;
+import com.poyee.base.mapper.provider.domain.LeftJoinInfo;
+import com.poyee.base.mapper.provider.domain.MPage;
+import com.poyee.base.mapper.provider.domain.TableInfo;
+import com.poyee.base.mapper.provider.domain.WhereInfo;
+import com.poyee.base.mapper.provider.util.InsertUtil;
+import com.poyee.base.mapper.provider.util.SelectUtil;
+import com.poyee.base.mapper.provider.util.SqlUtil;
+import com.poyee.base.mapper.provider.util.UpdateUtil;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.enums.DbMethodEnums;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * 提供基础的 SQL 构建功能,包括左连接和 WHERE 条件的处理。
+ */
+@Slf4j
+public abstract class BaseProvider {
+
+    // 数据库操作类型
+    public DbMethodEnums dbMethod;
+    // 表计数器
+    public int tableCount = 0;
+    //
+    public boolean isPage = false;
+    // 存储 insert
+    public Object insert;
+    // 记录 UPDATE
+    public Object update;
+    // 记录 SELECT
+    public Object select;
+    // 主表信息
+    public TableInfo superTable;
+    //分页查询
+    public MPage mPage;
+    //标记是否前端传 排序字段
+    public boolean isFrontSort = false;
+    //分页查询
+    public String pageCountSql;
+    // 记录 INSERT key-value(线程安全)
+    public final Map<String, Object> insertMap = new HashMap<>();
+    // 记录 UPDATE key-value(线程安全)
+    public final Map<String, Object> updateMap = new HashMap<>();
+    // 存储表信息的映射(线程安全)
+    public final Map<String, TableInfo> tableInfos = new ConcurrentHashMap<>();
+    // 记录 WHERE 条件(线程安全)
+    public final List<WhereInfo> whereInfos = new CopyOnWriteArrayList<>();
+    // 记录 SELECT 列(线程安全)
+    public final List<String> selectColumns = new CopyOnWriteArrayList<>();
+    // 记录 LEFT JOIN 信息(线程安全)
+    public final List<LeftJoinInfo> leftJoinInfos = new CopyOnWriteArrayList<>();
+    // 记录 ORDER BY 信息(线程安全)
+    public final Map<Integer,String> orderByMap = new HashMap<>();
+
+    /**
+     * @param req
+     * @param clazz
+     * @param <T>
+     * @param <R>
+     * @return
+     */
+    public <T extends BaseReq,R extends BaseDto> MPage<R> mPage(T req, Class<R> clazz){
+        MPage mPage = new MPage();
+        Map<String,Object> map = new HashMap<>();
+        map.put("req",req);
+        map.put("clazz",clazz);
+        String pageSql = pageWrapper(map);
+        mPage.setCountSql(pageCountSql);
+        mPage.setPageSql(pageSql);
+        return mPage;
+    }
+
+    /**
+     * @param object
+     * @return
+     */
+    public String selectById(Object object) {
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.SELECT_BY_ID;
+            // 组装 SELECT 语句
+            SelectUtil.init(this).checkSelectBean(object).initSelect(select);
+            // 生成最终的 SQL 字符串
+            return SqlUtil.init(this).buildOne().toSql();
+        }
+        throw new ServiceException("传入对象为空!");
+    }
+
+    /**
+     * @param object
+     * @return
+     */
+    public String selectOne(Object object) {
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.SELECT;
+            // 组装 SELECT 语句
+            SelectUtil.init(this).checkSelectBean(object).initSelect(select);
+            // 生成最终的 SQL 字符串
+            return SqlUtil.init(this).buildOne().toSql();
+        }
+        throw new ServiceException("传入对象为空!");
+    }
+
+    /**
+     * 处理查询对象 生成 SQL 字符串。
+     * @param object
+     * @return
+     */
+    public String selectSum(Object object){
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.SELECT;
+            // 组装 SELECT 语句
+            SelectUtil.init(this).checkSelectBean(object).initSelect(select);
+            // 生成最终的 SQL 字符串
+            return SqlUtil.init(this).buildSum().toSql();
+        }
+        throw new ServiceException("传入对象为空!");
+    }
+
+    /**
+     * 处理新增对象 生成 SQL 字符串。
+     * @param object
+     * @return
+     */
+    public String save(Object object) {
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.INSERT;
+            InsertUtil.init(this).checkInsertBean(object).initInsert();
+            // 生成最终的 SQL 字符串
+            String sql = SqlUtil.init(this).toSql();
+            log.info("SQL:{}",sql);
+            return sql;
+        }
+        // 如果传入的对象为空,则返回 null
+        throw new ServiceException("传入的对象为空!");
+    }
+
+    public String update(Object object) {
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.UPDATE;
+            UpdateUtil.init(this).checkUpdateBean(object).initUpdate();
+            // 生成最终的 SQL 字符串
+            return SqlUtil.init(this).toSql();
+        }
+        throw new ServiceException("传入的对象为空!");
+    }
+
+    /**
+     *
+     * @param object
+     * @return
+     */
+    public String pageWrapper(Object object) {
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.SELECT;
+            isPage = true;
+            // 组装 SELECT 语句
+            SelectUtil.init(this).checkSelectBean(object).initSelect(select);
+            // 生成最终的 SQL 字符串
+            return SqlUtil.init(this).buildPage().toSql();
+        }
+        // 如果传入的对象为空,则返回 null
+        throw new ServiceException("传入的对象为空!");
+    }
+
+    /**
+     * 处理对象的左连接和 WHERE 条件,生成 SQL 字符串。
+     *
+     * @param object 需要处理的对象
+     * @return 生成的 SQL 字符串
+     */
+    public String leftJoinWrapper(Object object) {
+        // 检查传入的对象是否非空
+        if (Objects.nonNull(object)) {
+            dbMethod = DbMethodEnums.SELECT;
+            // 组装 SELECT 语句
+            SelectUtil.init(this).checkSelectBean(object).initSelect(select);
+            // 生成最终的 SQL 字符串
+            return SqlUtil.init(this).toSql();
+        }
+        // 如果传入的对象为空,则返回 null
+        throw new ServiceException("传入的对象为空!");
+    }
+
+
+}

+ 29 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/ErrMessageContent.java

@@ -0,0 +1,29 @@
+package com.poyee.base.mapper.provider;
+
+public class ErrMessageContent {
+    //"对象不能为空"
+    public static final String ERROR_MESSAGE_OBJECT_NULL = "{0}对象不能为空";
+    //"构建{0}SQL时发生错误"
+    public static final String ERROR_MESSAGE_TABLE_NAME_MISSING = "{0}对象必须包含@TableName注解";
+    //"构建{0}SQL时发生错误"
+    public static final String ERROR_MESSAGE_BUILD_SQL = "构建{0}SQL时发生错误";
+    //"没有有效的{0}字段"
+    public static final String ERROR_MESSAGE_BEAN_NULL = "{0}对象或映射为空";
+    //"没有有效的{0}字段"
+    public static final String ERROR_MESSAGE_OPERA_NULL = "没有有效的{0}字段";
+    //"访问字段时发生错误"
+    public static final String ERROR_MESSAGE_FIELD_ACCESS = "访问字段时发生错误";
+    //非法参数错误
+    public static final String ERROR_MESSAGE_ILLEGAL_ARGUMENT = "非法参数";
+    //"传入的对象为空!"
+    public static final String ERROR_MESSAGE_OBJECT_IS_NULL = "传入的对象为空!";
+    //"表名注解缺失!"
+    public static final String ERROR_MESSAGE_TABLE_NAME_MISSING_2 = "表名注解缺失!";
+    //"表名不能为空!"
+    public static final String ERROR_MESSAGE_TABLE_NAME_IS_NULL = "表名不能为空!";
+
+    public static String getErrorMessage(String message, Object... args) {
+        return String.format(message, args);
+    }
+
+}

+ 151 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/IBaseProvider.java

@@ -0,0 +1,151 @@
+package com.poyee.base.mapper.provider;
+
+import com.poyee.base.dto.BaseDto;
+import com.poyee.base.dto.BaseReq;
+import com.poyee.base.dto.Page;
+import com.poyee.base.mapper.provider.domain.MPage;
+import com.poyee.util.ObjectUtil;
+import org.apache.ibatis.annotations.*;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @param <T>
+ * @param <R>
+ */
+public interface IBaseProvider<T extends BaseReq, R extends BaseDto> {
+    @SelectProvider(type = FinalProviderSql.class, method = "leftJoinWrapper")
+    List<Map<String, Object>> selectListMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+
+    @SelectProvider(type = FinalProviderSql.class, method = "leftJoinWrapper")
+    List<Map<String, Object>> selectListMapByReq(@Param("req") T req, @Param("clazz") Class<T> clazz);
+
+    @SelectProvider(type = FinalProviderSql.class, method = "pageCountSql")
+    long pageCount(String sql);
+
+    @SelectProvider(type = FinalProviderSql.class, method = "pageSql")
+    List<Map<String, Object>> page(String sql);
+
+    @SelectProvider(type = FinalProviderSql.class, method = "selectOne")
+    Map<String, Object> selectOneMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+
+    @SelectProvider(type = FinalProviderSql.class, method = "selectById")
+    Map<String, Object> selectByIdMap(@Param("id") Serializable id, @Param("clazz") Class<R> clazz);
+
+    @SelectProvider(type = FinalProviderSql.class, method = "selectSum")
+    Map<String, Object> selectSumMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
+
+    @InsertProvider(type = FinalProviderSql.class, method = "save")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+//    @SelectKey(statement = "select max(id) from promotion_share_info", keyProperty = "id", before = false, resultType = Long.class)
+    int save(T req);
+
+    @UpdateProvider(type = FinalProviderSql.class, method = "update")
+    int update(T req);
+
+    /**
+     * @param id
+     * @return
+     */
+    default R selectByPrimaryKey(Serializable id, Class<R> clazz) {
+        Map<String, Object> resultMaps = selectByIdMap(id, clazz);
+        if (resultMaps != null && !resultMaps.isEmpty()) {
+            return ObjectUtil.mapToBeanProvider(resultMaps, clazz);
+        }
+        try {
+            return clazz.newInstance();
+        } catch (InstantiationException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+    /**
+     * @param clazz
+     * @param req
+     * @return
+     */
+    default R selectOne(T req, Class<R> clazz) {
+        Map<String, Object> resultMaps = selectOneMap(req, clazz);
+        if (resultMaps != null && !resultMaps.isEmpty()) {
+            return ObjectUtil.mapToBeanProvider(resultMaps, clazz);
+        }
+        try {
+            return clazz.newInstance();
+        } catch (InstantiationException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 求和
+     * @param req
+     * @param clazz
+     * @return
+     */
+    default R sum(T req, Class<R> clazz){
+        Map<String, Object> resultMaps = selectSumMap(req, clazz);
+        if (resultMaps != null && !resultMaps.isEmpty()) {
+            return ObjectUtil.mapToBeanProvider(resultMaps, clazz);
+        }
+        try {
+            return clazz.newInstance();
+        } catch (InstantiationException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * @param req
+     * @param clazz
+     * @return
+     */
+    default List<R> selectList(T req, Class<R> clazz) {
+        List<Map<String, Object>> resultMaps = selectListMap(req, clazz);
+        if (resultMaps != null && resultMaps.size() > 0) {
+            return ObjectUtil.mapToListProvider(resultMaps, clazz);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * @param req
+     * @param clazz
+     * @return
+     */
+    default Page<R> selectPage(T req, Class<R> clazz) {
+        MPage<R> mPage = new FinalProviderSql().mPage(req, clazz);
+        long total = pageCount(mPage.getCountSql());
+        List<R> records = null;
+        if (total > 0) {
+            List<Map<String, Object>> pageMaps = page(mPage.getPageSql());
+            records = ObjectUtil.mapToListProvider(pageMaps, clazz);
+        }
+        return new Page<>(total, records).setPage((long) req.getPageNo());
+    }
+
+    /**
+     * 拼接sql
+     */
+    class FinalProviderSql extends BaseProvider {
+        /**
+         * @param sql
+         * @return
+         */
+        public String pageCountSql(String sql) {
+            return sql;
+        }
+
+        /**
+         * @param sql
+         * @return
+         */
+        public String pageSql(String sql) {
+            return sql;
+        }
+    }
+}

+ 27 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/domain/LeftJoinInfo.java

@@ -0,0 +1,27 @@
+package com.poyee.base.mapper.provider.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class LeftJoinInfo {
+
+    public LeftJoinInfo(TableInfo tableInfo, TableInfo leftTableInfo) {
+        this.tableInfo = tableInfo;
+        this.leftTableInfo = leftTableInfo;
+    }
+    // 表信息
+    private TableInfo tableInfo;
+    // 关联表信息
+    private TableInfo leftTableInfo;
+    //关联条件
+    private String fieldName;
+    // 关联字段
+    private String leftFieldName;
+    // 别名
+    private String alias;
+
+}

+ 120 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/domain/MPage.java

@@ -0,0 +1,120 @@
+package com.poyee.base.mapper.provider.domain;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Data
+public class MPage<T> implements IPage<T> {
+
+    private static final long serialVersionUID = 1L;
+    protected List<T> records;
+    protected long total;
+    protected long size;
+    protected long current;
+    protected List<OrderItem> orders;
+    protected boolean optimizeCountSql;
+    protected boolean searchCount;
+    protected boolean optimizeJoinOfCountSql;
+    protected String countId;
+    protected Long maxLimit;
+
+    protected String countSql;
+
+    protected String pageSql;
+
+    public MPage() {
+        this.records = Collections.emptyList();
+        this.total = 0L;
+        this.size = 10L;
+        this.current = 1L;
+        this.orders = new ArrayList();
+        this.optimizeCountSql = true;
+        this.searchCount = true;
+        this.optimizeJoinOfCountSql = true;
+    }
+    public MPage(long current, long size) {
+        this(current, size, 0L);
+    }
+
+    public MPage(long current, long size, long total) {
+        this(current, size, total, true);
+    }
+
+    public MPage(long current, long size, boolean searchCount) {
+        this(current, size, 0L, searchCount);
+    }
+
+    public MPage(long current, long size, long total, boolean searchCount) {
+        this.records = Collections.emptyList();
+        this.total = 0L;
+        this.size = 10L;
+        this.current = 1L;
+        this.orders = new ArrayList();
+        this.optimizeCountSql = true;
+        this.searchCount = true;
+        this.optimizeJoinOfCountSql = true;
+        if (current > 1L) {
+            this.current = current;
+        }
+
+        this.size = size;
+        this.total = total;
+        this.searchCount = searchCount;
+    }
+
+    @Override
+    public List<OrderItem> orders() {
+        return this.orders;
+    }
+
+    @Override
+    public List<T> getRecords() {
+        return this.records;
+    }
+
+    @Override
+    public IPage<T> setRecords(List<T> records) {
+        this.records = records;
+        return this;
+    }
+
+    @Override
+    public long getTotal() {
+        return this.total;
+    }
+
+    @Override
+    public IPage<T> setTotal(long total) {
+        this.total = total;
+        return this;
+    }
+
+    @Override
+    public long getSize() {
+        return this.size;
+    }
+
+    @Override
+    public IPage<T> setSize(long size) {
+        this.size = size;
+        return this;
+    }
+
+    @Override
+    public long getCurrent() {
+        return this.current;
+    }
+
+    @Override
+    public IPage<T> setCurrent(long current) {
+        this.current = current;
+        return this;
+    }
+
+
+}

+ 16 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/domain/OrderByInfo.java

@@ -0,0 +1,16 @@
+package com.poyee.base.mapper.provider.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderByInfo {
+
+    private Integer index;
+    private String column;
+    private String sortType;
+
+}

+ 24 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/domain/TableInfo.java

@@ -0,0 +1,24 @@
+package com.poyee.base.mapper.provider.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TableInfo {
+    // 实体类名
+    private String className;
+    // 实体类
+    private Class<?> clazz;
+    // 表名
+    private String tableName;
+    // 别名
+    private String alias;
+    // 索引
+    private Integer idx;
+    // 主键
+    private String primaryKey;
+
+}

+ 99 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/domain/WhereInfo.java

@@ -0,0 +1,99 @@
+package com.poyee.base.mapper.provider.domain;
+
+import com.poyee.enums.FieldOperator;
+import com.poyee.enums.FieldType;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class WhereInfo {
+
+    public WhereInfo(TableInfo tableInfo){
+        this.tableInfo = tableInfo;
+    }
+    // 表信息
+    private TableInfo tableInfo;
+    // 字段
+    private String column;
+    // or 条件
+    private List<String> orColumn;
+    // 值
+    private Object value;
+    // 结束值 (between 时使用)
+    private Object endValue;
+    /**
+     * 操作符
+     * @see FieldOperator
+     */
+    private FieldOperator operator;
+    /**
+     * 字段类型
+     * @see FieldType
+     */
+    private FieldType fieldType;
+    // 是否自定义条件
+    private Boolean isCustom;
+    // 是否是 or 条件
+    private boolean isCustomOr;
+    // 自定义条件
+    private List<CustomConditions> customConditions;
+
+
+    /**
+     *  自定义条件集合
+     */
+    @Data
+    public static class CustomConditions{
+        List<CustomCondition> customConditionList;
+        public void add(CustomCondition customCondition){
+            if(Objects.isNull(customConditionList)){
+                customConditionList = new ArrayList<>();
+            }
+            customConditionList.add(customCondition);
+        }
+    }
+
+    /**
+     *  自定义条件
+     */
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class CustomCondition{
+
+        public CustomCondition(String column, String defaultValue, FieldOperator operator, FieldType fieldType) {
+            this.column = column;
+            this.defaultValue = defaultValue;
+            this.operator = operator;
+            this.fieldType = fieldType;
+        }
+        // 字段
+        private String column;
+        // or 条件
+        private List<String> orColumn;
+        // 值
+        private Object value;
+        // 结束值 (between 时使用)
+        private Object endValue;
+        /**
+         * 操作符
+         * @see FieldOperator
+         */
+        private FieldOperator operator;
+        /**
+         * 字段类型
+         * @see FieldType
+         */
+        private FieldType fieldType;
+        // 默认值
+        private String defaultValue;
+    }
+
+}

+ 154 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/FormatValueUtil.java

@@ -0,0 +1,154 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.poyee.base.mapper.provider.domain.WhereInfo;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+import static com.poyee.annotation.MpjWapper.Operator.ISNOTNULL;
+
+/**
+ *  格式化值工具类
+ */
+@Slf4j
+public class FormatValueUtil {
+
+    /**
+     * 构建 IN 或 NOT IN 子句。
+     *
+     * @param valueObj WHERE 信息
+     * @return 构建的 IN 或 NOT IN 子句
+     */
+    private static String buildInClause(Object valueObj) {
+        // 如果值为 null,则返回 "NULL"
+        if (valueObj == null) {
+            return "NULL";
+        }
+        StringBuilder inClause = new StringBuilder("(");
+        // 如果值是字符串类型,则按逗号分割成多个值
+        if (valueObj instanceof String) {
+            String[] values = ((String) valueObj).split(",");
+            for (String value : values) {
+                // 对每个值进行空值和空字符串检查,并构建 IN 子句
+                if (value != null && !value.trim().isEmpty()) {
+                    inClause.append("'").append(escapeValue(value.trim())).append("',");
+                }
+            }
+        } else if (valueObj instanceof List) {
+            // 如果值是 List 类型,则遍历列表中的每个值
+            for (Object value : (List<?>) valueObj) {
+                // 对每个值进行空值检查,并构建 IN 子句
+                if (value != null) {
+                    inClause.append("'").append(escapeValue(value)).append("',");
+                }
+            }
+        } else if (valueObj instanceof Object[]) {
+            // 如果值是数组类型,则遍历数组中的每个值
+            for (Object value : (Object[]) valueObj) {
+                // 对每个值进行空值检查,并构建 IN 子句
+                if (value != null) {
+                    inClause.append("'").append(escapeValue(value)).append("',");
+                }
+            }
+        }
+        // 如果 IN 子句长度大于 1,则移除最后的逗号
+        if (inClause.length() > 1) {
+            inClause.setLength(inClause.length() - 1);
+        }
+        inClause.append(")");
+        return inClause.toString();
+    }
+
+    /**
+     * @param value
+     * @param endValue
+     * @return
+     */
+    private static String buildBetweenClause(Object value, Object endValue) {
+        return "'" + escapeValue(value) + "' AND '" + escapeValue(endValue) + "'";
+    }
+    /**
+     * 根据操作符格式化字符串值。
+     *
+     * @param whereInfo WHERE 信息
+     * @return 格式化后的字符串值
+     */
+    public static String formatStringValue(WhereInfo whereInfo) {
+        // 根据不同的操作符格式化字符串值
+        switch (whereInfo.getOperator()) {
+            case LIKE:
+                // 对于LIKE操作符,前后添加百分号并转义值
+                return "'%" + escapeValue(whereInfo.getValue()) + "%'";
+            case ILIKE:
+                // 对于ILIKE操作符,前后添加百分号并转义值
+                return "'%" + escapeValue(whereInfo.getValue()) + "%'";
+            case LLIKE:
+                // 对于LLIKE操作符,前面添加百分号并转义值
+                return "'%" + escapeValue(whereInfo.getValue()) + "'";
+            case RLIKE:
+                // 对于RLIKE操作符,后面添加百分号并转义值
+                return "'" + escapeValue(whereInfo.getValue()) + "%'";
+            case IN:
+            case NOTIN:
+                // 对于IN和NOTIN操作符,调用buildInClause方法构建IN子句
+                return buildInClause(whereInfo.getValue());
+            case BETWEEN:
+            case NOTBETWEEN:
+                // 对于IN和NOTIN操作符,调用buildInClause方法构建IN子句
+                return buildBetweenClause(whereInfo.getValue(),whereInfo.getEndValue());
+            default:
+                // 对于其他操作符,直接转义值并添加单引号
+                return "'" + escapeValue(whereInfo.getValue()) + "'";
+        }
+    }
+    /**
+     * 根据操作符格式化字符串值。
+     *
+     * @param condition WHERE 信息
+     * @return 格式化后的字符串值
+     */
+    public static String formatStringValue(WhereInfo.CustomCondition condition) {
+        // 根据不同的操作符格式化字符串值
+        switch (condition.getOperator()) {
+            case LIKE:
+                // 对于LIKE操作符,前后添加百分号并转义值
+                return "'%" + escapeValue(condition.getValue()) + "%'";
+            case ILIKE:
+                // 对于ILIKE操作符,前后添加百分号并转义值
+                return "'%" + escapeValue(condition.getValue()) + "%'";
+            case LLIKE:
+                // 对于LLIKE操作符,前面添加百分号并转义值
+                return "'%" + escapeValue(condition.getValue()) + "'";
+            case RLIKE:
+                // 对于RLIKE操作符,后面添加百分号并转义值
+                return "'" + escapeValue(condition.getValue()) + "%'";
+            case IN:
+            case NOTIN:
+                // 对于IN和NOTIN操作符,调用buildInClause方法构建IN子句
+                return buildInClause(condition.getValue());
+            case BETWEEN:
+            case NOTBETWEEN:
+                // 对于IN和NOTIN操作符,调用buildInClause方法构建IN子句
+                return buildBetweenClause(condition.getValue(), condition.getEndValue());
+            case ISNOTNULL:
+            case ISNULL:
+                return "";
+            default:
+                // 对于其他操作符,直接转义值并添加单引号
+                return "'" + escapeValue(condition.getValue()) + "'";
+        }
+    }
+    /**
+     * 转义特殊字符以防止 SQL 注入。
+     *
+     * @param value 值
+     * @return 转义后的值
+     */
+    public static String escapeValue(Object value) {
+        if (value == null) {
+            return "NULL";
+        }
+        return String.valueOf(value).replace("'", "''");
+    }
+
+}

+ 224 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/InsertUtil.java

@@ -0,0 +1,224 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.enums.DbMethodEnums;
+import com.poyee.util.DateUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.poyee.base.mapper.provider.ErrMessageContent.*;
+
+@Slf4j
+public class InsertUtil {
+
+    public static final Integer maxBaseCount = 200;
+
+    private BaseProvider parent;
+
+    private InsertUtil(BaseProvider parent) {
+        this.parent = parent;
+    }
+
+    public static InsertUtil init(BaseProvider parent) {
+        return new InsertUtil(parent);
+    }
+
+   /**
+      * 生成批量插入SQL
+      * @param clazz 实体类
+      * @param blist 实体对象列表
+      * @param b 是否忽略空字段(可根据实际需求调整)
+      * @param <T> 泛型
+      * @return 批量插入SQL
+      */
+     public static <T> String insertAll(Class<T> clazz, List<T> blist, boolean b) {
+         StringBuilder sql = new StringBuilder();
+         if (blist == null || blist.isEmpty()) {
+             log.warn("批量插入数据不能为空");
+             throw new ServiceException("批量插入数据不能为空");
+         }
+
+         // 获取表名
+         TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
+         if (tableNameAnnotation == null || tableNameAnnotation.value().trim().isEmpty()) {
+             throw new ServiceException("实体类缺少@TableName注解或表名为空");
+         }
+         String tableName = tableNameAnnotation.value();
+
+         // 获取所有字段
+         Field[] fields = clazz.getDeclaredFields();
+         StringBuilder columns = new StringBuilder();
+         List<String> columnList = new java.util.ArrayList<>();
+         for (Field field : fields) {
+             if (field.isAnnotationPresent(TableField.class)) {
+                 TableField tableField = field.getAnnotation(TableField.class);
+                 if (tableField.exist()) {
+                     columnList.add(tableField.value());
+                 }
+             }
+         }
+         if (columnList.isEmpty()) {
+             throw new ServiceException("未找到可插入的字段");
+         }
+         columns.append(String.join(", ", columnList));
+
+         sql.append("INSERT INTO ").append(tableName)
+                 .append(" (").append(columns).append(") VALUES ");
+
+         for (int i = 0; i < blist.size(); i++) {
+             T obj = blist.get(i);
+             sql.append("(");
+             for (int j = 0; j < columnList.size(); j++) {
+                 String col = columnList.get(j);
+                 try {
+                     Field field = null;
+                     for (Field f : fields) {
+                         if (f.isAnnotationPresent(TableField.class)) {
+                             TableField tf = f.getAnnotation(TableField.class);
+                             if (tf.value().equals(col)) {
+                                 field = f;
+                                 break;
+                             }
+                         }
+                     }
+                     if (field == null) {
+                         sql.append("NULL");
+                     } else {
+                         field.setAccessible(true);
+                         Object value = field.get(obj);
+                         if (value == null) {
+                             sql.append("NULL");
+                         } else if (value instanceof String || value instanceof java.sql.Date || value instanceof Date) {
+                             sql.append("'").append(value.toString().replace("'", "''")).append("'");
+                         } else {
+                             sql.append(value);
+                         }
+                     }
+                 } catch (IllegalAccessException e) {
+                     throw new ServiceException("字段访问异常", e);
+                 }
+                 if (j < columnList.size() - 1) {
+                     sql.append(", ");
+                 }
+             }
+             sql.append(")");
+             if (i < blist.size() - 1) {
+                 sql.append(", ");
+             }
+         }
+         return sql.toString();
+     }
+
+    /**
+     * 检查插入对象是否合法
+     *
+     * @param object 插入对象
+     * @return 自身实例
+     */
+    public InsertUtil checkInsertBean(Object object) {
+        if (object == null) {
+            log.warn("对象不能为空");
+            throw new ServiceException(ERROR_MESSAGE_OBJECT_NULL);
+        }
+        Class<?> aClass = object.getClass();
+        if (!aClass.isAnnotationPresent(TableName.class)) {
+            log.warn("对象必须包含@TableName注解");
+            throw new ServiceException(ERROR_MESSAGE_TABLE_NAME_MISSING);
+        }
+        TableName tableNameAnnotation = aClass.getAnnotation(TableName.class);
+        String tableName = tableNameAnnotation.value();
+        if (tableName == null || tableName.trim().isEmpty()) {
+            log.warn("表名不能为空");
+            throw new ServiceException(ERROR_MESSAGE_TABLE_NAME_IS_NULL);
+        }
+
+        // 初始化表信息
+        TableInfoUtil.init(parent).initTableInfo(aClass);
+        parent.superTable = parent.tableInfos.get(aClass.getName());
+
+        parent.insert = object;
+        return this;
+    }
+
+    /**
+     * 初始化插入操作
+     */
+    public void initInsert() {
+        Object insert = parent.insert;
+        if (insert == null || parent.insertMap == null) {
+            log.warn("插入对象或映射为空,无法生成SQL。对象: {}", insert);
+            throw new ServiceException(getErrorMessage(ERROR_MESSAGE_BEAN_NULL,"插入"));
+        }
+
+        try {
+            Map<String, Object> insertMap = parent.insertMap;
+            insertMap.clear(); // 清空旧数据
+            processFields(insert, insertMap);
+
+            if (insertMap.isEmpty()) {
+                log.warn("没有有效的插入字段,对象: {}", insert);
+                throw new ServiceException(getErrorMessage(ERROR_MESSAGE_OPERA_NULL,"插入"));
+            }
+        } catch (IllegalAccessException e) {
+            log.error("访问字段时发生错误", e);
+            throw new ServiceException(ERROR_MESSAGE_FIELD_ACCESS);
+        } catch (IllegalArgumentException e) {
+            log.error("非法参数错误", e);
+            throw new ServiceException(ERROR_MESSAGE_ILLEGAL_ARGUMENT);
+        } catch (Exception e) {
+            log.error(ERROR_MESSAGE_BUILD_SQL, e);
+            throw new ServiceException(ERROR_MESSAGE_BUILD_SQL);
+        }
+    }
+
+    /**
+     * 处理字段并填充到映射中
+     *
+     * @param object    对象
+     * @param insertMap 插入映射
+     * @throws IllegalAccessException 如果字段访问失败
+     */
+    private void processFields(Object object, Map<String, Object> insertMap) throws IllegalAccessException {
+        Class<?> aClass = object.getClass();
+        Field[] declaredFields = aClass.getDeclaredFields();
+
+        for (Field field : declaredFields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableField.class)) {
+                TableField tableField = field.getAnnotation(TableField.class);
+                if (tableField.exist()) {
+                    Object value = field.get(object);
+                    if ( Objects.nonNull(value)) {
+                        if(value instanceof Date){
+                            insertMap.put(tableField.value(), DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, (Date) value));
+                        }else{
+                            insertMap.put(tableField.value(), value);
+                        }
+                    }
+                }
+                //判断是否需要设置 创建,更新时间
+                if ( Objects.equals(parent.dbMethod, DbMethodEnums.INSERT)
+                        && Objects.equals(tableField.fill() , FieldFill.INSERT_UPDATE)
+                        && Objects.equals(field.getName(), "updateTime")
+                ) {
+                    insertMap.put(tableField.value(), new Date());
+                }
+                if ( Objects.equals(parent.dbMethod, DbMethodEnums.INSERT)
+                        && Objects.equals(tableField.fill() , FieldFill.INSERT)
+                        && Objects.equals(field.getName(), "createTime")
+                ) {
+                    insertMap.put(tableField.value(), new Date());
+                }
+            }
+        }
+    }
+}

+ 76 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/JoinUtil.java

@@ -0,0 +1,76 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.poyee.annotation.db.LeftJoin;
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.base.mapper.provider.domain.LeftJoinInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+public class JoinUtil {
+
+    public static JoinUtil init(BaseProvider baseProvider){
+        return new JoinUtil(baseProvider);
+    }
+
+    private JoinUtil(BaseProvider baseProvider){
+        this.parent = baseProvider;
+    }
+    private BaseProvider parent;
+
+
+    /**
+     * 初始化左连接信息
+     *
+     * @param aClass 实体类类对象,用于获取表信息
+     * @param leftJoinInfos 左连接信息列表,存储处理后的左连接信息
+     * @param declaredField 声明字段,检查是否包含LeftJoin注解
+     */
+    public void initLeftJoin(Class<?> aClass, List<LeftJoinInfo> leftJoinInfos, Field declaredField) {
+        // 检查字段是否包含LeftJoin注解
+        if (declaredField.isAnnotationPresent(LeftJoin.class)) {
+            // 获取字段上的LeftJoin注解
+            LeftJoin leftJoin = declaredField.getAnnotation(LeftJoin.class);
+            // 创建一个新的左连接信息对象
+            LeftJoinInfo leftJoinInfo = new LeftJoinInfo();
+
+            // 获取注解中指定的连接表类
+            Class<?> table = leftJoin.table();
+            // 如果父对象的表信息映射中不包含此表,初始化该表的信息
+            if (!parent.tableInfos.containsKey(table.getName())) {
+                TableInfoUtil.init(parent).initTableInfo(table);
+            }
+            // 设置左连接信息中的表信息和别名
+            leftJoinInfo.setTableInfo(parent.tableInfos.get(table.getName()));
+            leftJoinInfo.setAlias(parent.tableInfos.get(table.getName()).getAlias());
+            //判断 左连接 表中是否已存在 leftJoinInfos
+            if (CollectionUtils.isNotEmpty(leftJoinInfos)) {
+                boolean b = leftJoinInfos.stream()
+                                         .anyMatch(item -> Objects.equals(item.getTableInfo().getAlias(), leftJoinInfo.getTableInfo().getAlias()));
+                if(b){ //如果已存在则返回
+                    return ;
+                }
+            }
+            // 确定左表,如果注解中未指定则使用传入的实体类
+            Class<?> leftTable = leftJoin.leftTable() != void.class ? leftJoin.leftTable() : aClass;
+            // 如果父对象的表信息映射中不包含左表,初始化左表的信息
+            if (!parent.tableInfos.containsKey(leftTable.getName())) {
+                TableInfoUtil.init(parent).initTableInfo(leftTable);
+            }
+            // 设置左连接信息中的左表信息
+            leftJoinInfo.setLeftTableInfo(parent.tableInfos.get(leftTable.getName()));
+
+            // 设置左连接信息中的字段名和左表字段名
+            leftJoinInfo.setFieldName(leftJoin.fieldName());
+            leftJoinInfo.setLeftFieldName(leftJoin.leftFieldName());
+
+            // 将处理后的左连接信息添加到列表中
+            leftJoinInfos.add(leftJoinInfo);
+        }
+    }
+
+}

+ 363 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/SelectUtil.java

@@ -0,0 +1,363 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.poyee.annotation.I18n;
+import com.poyee.annotation.db.*;
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.base.mapper.provider.domain.MPage;
+import com.poyee.base.mapper.provider.domain.TableInfo;
+import com.poyee.base.mapper.provider.domain.WhereInfo;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.enums.FieldOperator;
+import com.poyee.util.Convert;
+import com.poyee.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.poyee.enums.DbMethodEnums.SELECT_BY_ID;
+
+@Slf4j
+public class SelectUtil {
+
+    private BaseProvider parent;
+
+    private SelectUtil(BaseProvider baseProvider) {
+        this.parent = baseProvider;
+    }
+
+    public static SelectUtil init(BaseProvider baseProvider) {
+        return new SelectUtil(baseProvider);
+    }
+    /**
+     * @param object
+     * @return
+     */
+    @NotNull
+    public SelectUtil checkSelectBean(Object object) {
+        Object param;
+        Object select = null; // return class
+        // 判断 object 是否是 Map
+        if (object instanceof Map) {
+            Map<String, Object> map = (Map<String, Object>) object;
+            select = map.get("clazz");
+            if(Objects.equals(parent.dbMethod , SELECT_BY_ID)){
+                param = map.get("id");
+            }else{
+                param = map.get("req");
+            }
+        } else {
+            param = object;
+        }
+        // 如果无法定位返回值,则抛出异常
+        if (Objects.isNull(select)) {
+            throw new ServiceException("无法定位返回值!");
+        }
+        if(Objects.equals(parent.dbMethod , SELECT_BY_ID)){
+            //组装 select by id
+            return this.checkSelectOneBean(param, select);
+        }
+        // 获取参数对象的类信息
+        Class<?> aClass = param.getClass();
+        // 初始化表信息
+        TableInfoUtil.init(parent).initTableInfo(aClass);
+        parent.superTable = parent.tableInfos.get(aClass.getName());
+        // 获取超类的所有字段处理分页信息
+        if(parent.isPage) {
+            long page = 0;
+            long size = 10;
+            Class<?> superclass = aClass.getSuperclass();
+            try {
+                Field pageNoFields = superclass.getDeclaredField("pageNo");
+                pageNoFields.setAccessible(true);
+                page = Optional.ofNullable(Convert.toLong(pageNoFields.get(param)))
+                               .orElse(0L);
+                Field pageSizeFields = superclass.getDeclaredField("pageSize");
+                pageSizeFields.setAccessible(true);
+                size = Optional.ofNullable(Convert.toLong(pageSizeFields.get(param)))
+                               .orElse(10L);
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+            //设置 分页信息
+            parent.mPage = new MPage(page, size);
+            try {
+                //处理排序字段
+                Field sidxField = superclass.getDeclaredField("sidx");
+                sidxField.setAccessible(true);
+                Object sidxObj = sidxField.get(param);
+                if(Objects.nonNull(sidxObj) && StringUtils.isNotBlank(String.valueOf(sidxObj))){
+                    parent.isFrontSort = true;
+                    Field sortField = superclass.getDeclaredField("sord");
+                    sortField.setAccessible(true);
+                    String sort = "asc";
+                    if(Objects.nonNull(sortField.get(param))){
+                        sort = sortField.get(param).toString();
+                    }
+                    String alias = parent.superTable.getAlias();
+                    parent.orderByMap.put(Objects.equals(sort,"desc") ? -1 : 0 ,alias +"."+ String.valueOf(sidxObj));
+                }
+            }catch (NoSuchFieldException | IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        //设置 排序信息
+        Field[] declaredFields = aClass.getDeclaredFields();
+        for (Field declaredField : declaredFields) {
+            declaredField.setAccessible(true);
+            // 构建 WHERE 条件
+            WhereUtil.init(parent).build(param, aClass, parent.whereInfos, declaredField);
+            // 构建 LEFT JOIN 信息
+            JoinUtil.init(parent).initLeftJoin(aClass, parent.leftJoinInfos, declaredField);
+        }
+        parent.select = select;
+        return this;
+    }
+
+    /**
+     * 检查选择对象
+     * @param param
+     * @return
+     */
+    private SelectUtil checkSelectOneBean(Object param, Object select) {
+        // 获取参数对象的类信息
+        Class<?> aClass;
+        if(select instanceof Class<?>){
+            aClass = (Class<?>) select;
+        }else{
+            aClass = select.getClass();
+        }
+        // 初始化表信息
+        TableInfoUtil.init(parent).initTableInfo(aClass);
+        parent.superTable = parent.tableInfos.get(aClass.getName());
+        parent.select = select;
+        //获取 主键
+        String column = "id";
+        Field[] declaredFields = aClass.getDeclaredFields();
+        for (Field declaredField : declaredFields) {
+            declaredField.setAccessible(true);
+            if(declaredField.isAnnotationPresent(TableId.class)){
+                TableId tableId = declaredField.getAnnotation(TableId.class);
+                column = tableId.value();
+            }else{
+                // 构建 LEFT JOIN 信息
+                JoinUtil.init(parent).initLeftJoin(aClass, parent.leftJoinInfos, declaredField);
+            }
+        }
+        //设置条件
+        WhereInfo whereInfo = new WhereInfo(parent.superTable);
+        whereInfo.setColumn(column);
+        whereInfo.setValue(param);
+        whereInfo.setOperator(FieldOperator.EQ);
+        parent.whereInfos.add(whereInfo);
+        return this;
+    }
+
+    /**
+     * 初始化选择对象的方法
+     * 该方法用于处理传入的选择对象,提取其字段信息,并根据注解确定查询列
+     *
+     * @param select 选择对象,可以是任何类,但通常用于具有@TableField和@Select注解的类
+     */
+    public void initSelect(Object select) {
+        // 检查选择对象是否非空
+        if (Objects.isNull(select)) {
+            log.warn("初始化选择对象失败,传入的对象为 null");
+            throw new ServiceException("初始化选择对象失败,传入的对象为 null");
+        }
+
+        try {
+            // 获取选择对象的实际类信息
+            Class<?> clazz = (Class<?>) select;
+            // 获取类的所有声明的字段
+            Field[] declaredFields = clazz.getDeclaredFields();
+            // 遍历字段
+            for (Field field : declaredFields) {
+                // 设置字段可访问,以处理私有字段
+                field.setAccessible(true);
+                //根据国际语言判断字段是否进行查询
+                if (field.isAnnotationPresent(I18n.class) && !ServletUtils.isI18nSupport()) {
+                    continue;
+                }
+                if (!field.isAnnotationPresent(Select.class)
+                        && (field.isAnnotationPresent(TableField.class) || field.isAnnotationPresent(TableId.class))) {
+                    // 检查字段是否包含TableField注解
+                    processTableFieldAnnotation(field);
+                } else if (field.isAnnotationPresent(Select.class)) {
+                    processSelectAnnotation(field);
+                }
+            }
+        } catch (Exception e) {
+            log.error("初始化选择对象时发生异常", e);
+        }
+    }
+
+    /**
+     * 处理 TableField 注解的逻辑
+     *
+     * @param field 字段对象
+     */
+    private void processTableFieldAnnotation(Field field) {
+        try {
+            boolean isDistinct = field.isAnnotationPresent(Distinct.class);
+            TableInfo superTable = parent.superTable;
+            TableId tableId = field.getAnnotation(TableId.class);
+            if (Objects.nonNull(tableId)) {
+                String selectSql = "";
+                if(isDistinct){
+                    selectSql = "DISTINCT " + superTable.getAlias() + "." + tableId.value() + " AS " + field.getName();
+                }else{
+                    selectSql = superTable.getAlias() + "." + tableId.value() + " AS " + field.getName();
+                }
+                checkSelectSql(selectSql, field, superTable, tableId.value());
+            }
+            TableField tableField = field.getAnnotation(TableField.class);
+            if (Objects.nonNull(tableField) && tableField.exist()) {
+                String selectSql;
+                if(field.isAnnotationPresent(Sum.class)){
+                    Sum sum = field.getAnnotation(Sum.class);
+                    CaseWhen[] caseWhens = sum.caseWhen();
+                    String sumFieldSql = "";
+                    if(caseWhens.length > 0){
+                        sumFieldSql = buildCaseWhen( superTable, caseWhens) ;
+                    } else {
+                        sumFieldSql = superTable.getAlias() + "." + tableField.value();
+                    }
+                    selectSql = "COALESCE(SUM(" + sumFieldSql + "),0)" + " AS " + field.getName();
+                } else if(field.isAnnotationPresent(Count.class)) {
+                    Count count = field.getAnnotation(Count.class);
+                    CaseWhen[] caseWhens = count.caseWhen();
+                    String countFieldSql = "";
+                    if(caseWhens.length > 0){
+                        countFieldSql = (count.distinct()?"DISTINCT":"")+" "+buildCaseWhen( superTable, caseWhens);
+                    }else{
+                        countFieldSql = (count.distinct()?"DISTINCT":"")+" "+superTable.getAlias() + "." + tableField.value();
+                    }
+                    selectSql = "COALESCE(COUNT( "+countFieldSql+"),0)" + " AS " + field.getName();
+                } else {
+                    if(isDistinct){
+                        selectSql = "DISTINCT " + superTable.getAlias() + "." + tableField.value() + " AS " + field.getName();
+                    }else{
+                        selectSql = superTable.getAlias() + "." + tableField.value() + " AS " + field.getName();
+                    }
+//                    selectSql = superTable.getAlias() + "." + tableField.value() + " AS " + field.getName();
+                }
+                checkSelectSql(selectSql, field, superTable, tableField.value());
+            }
+        } catch (Exception e) {
+            log.error("处理 TableField 注解时发生异常", e);
+        }
+    }
+
+    /**
+     * @param superTable
+     * @param caseWhens
+     * @return
+     */
+    private String buildCaseWhen(TableInfo superTable, CaseWhen[] caseWhens) {
+        StringBuilder caseSql = new StringBuilder();
+        StringBuilder elseStr = new StringBuilder();
+        AtomicBoolean isFirst = new AtomicBoolean(true);
+        Arrays.asList(caseWhens).forEach(caseWhen -> {
+            if (caseWhen.isElse()) {
+                elseStr.append(" ELSE ").append(caseWhen.then());
+            }else{
+                // 判断是否是第一个条件
+                if(isFirst.get()){
+                    caseSql.append(" CASE WHEN ");
+                    isFirst.set(false);
+                } else {
+                    caseSql.append(" WHEN ");
+                }
+                //  WHEN 条件
+                // 判断条件 - key
+                caseSql.append(superTable.getAlias() + "." + caseWhen.when());
+                //  THEN 值
+                caseSql.append(" THEN ");
+                // 判断值类型
+                switch (caseWhen.valueType()){
+                    case TABLE_COLUMN:
+                        caseSql.append(superTable.getAlias() + "." + caseWhen.then());
+                        break;
+                    case CONSTANT:
+                    case SQL_EXPRESSION:
+                        caseSql.append(caseWhen.then());
+                        break;
+                }
+            }
+        });
+        caseSql.append(elseStr.append(" END "));
+        String selectSql = caseSql.toString();
+        log.info("构建CASE WHEN SQL: {}", caseSql);
+        return selectSql;
+    }
+
+    /**
+     * 检查选择SQL
+     * @param selectSql
+     * @param field
+     * @param tableInfo
+     * @param fieldName
+     */
+    private void checkSelectSql(String selectSql, Field field, TableInfo tableInfo, String fieldName) {
+        parent.selectColumns.add(selectSql);
+        if (!parent.isFrontSort && field.isAnnotationPresent(OrderBy.class)) {
+            OrderBy orderBy = field.getAnnotation(OrderBy.class);
+            parent.orderByMap.put(orderBy.index(), tableInfo.getAlias() + "." + fieldName + " " + orderBy.sortType().getValue());
+        }
+        if (parent.isFrontSort && (parent.orderByMap.containsKey(-1) || parent.orderByMap.containsKey(0))) {
+            int kk = parent.orderByMap.containsKey(-1) ? -1 : 0;
+            if (Objects.equals(field.getName(), parent.orderByMap.get(kk))
+                    || Objects.equals(tableInfo.getAlias() + "." + field.getName(), parent.orderByMap.get(kk))) {
+                parent.orderByMap.put(kk, tableInfo.getAlias() + "." + fieldName + " " + (kk == -1 ? "desc" : "asc"));
+            }
+        }
+    }
+
+    /**
+     * 处理 Select 注解的逻辑
+     *
+     * @param field 字段对象
+     */
+    private void processSelectAnnotation(Field field) {
+        try {
+            boolean isDistinct = field.isAnnotationPresent(Distinct.class);
+
+            Select selectAnnotation = field.getAnnotation(Select.class);
+            if (Objects.nonNull(selectAnnotation.table())) {
+                String tableName = selectAnnotation.table()
+                                                   .getName();
+                TableInfo tableInfo = parent.tableInfos.get(tableName);
+                if(Objects.isNull(tableInfo)){
+                    TableInfoUtil.init(parent).initTableInfo(selectAnnotation.table());
+                    tableInfo = parent.tableInfos.get(tableName);
+                }
+                if (Objects.nonNull(tableInfo)) {
+                    String selectSql = "";
+                    //判断 distinct
+                    if(isDistinct){
+                        selectSql = "DISTINCT " + tableInfo.getAlias() + "." + selectAnnotation.fieldName() + " AS " + field.getName();
+                    }else{
+                        selectSql = tableInfo.getAlias() + "." + selectAnnotation.fieldName() + " AS " + field.getName();
+                    }
+                    checkSelectSql(selectSql, field, tableInfo, selectAnnotation.fieldName());
+                    parent.selectColumns.add(selectSql);
+                } else {
+                    log.warn("未找到表名 {} 对应的 TableInfo 对象", tableName);
+                }
+            }
+        } catch (Exception e) {
+            log.error("处理 Select 注解时发生异常", e);
+        }
+    }
+
+}

+ 638 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/SqlUtil.java

@@ -0,0 +1,638 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.base.mapper.provider.domain.LeftJoinInfo;
+import com.poyee.base.mapper.provider.domain.WhereInfo;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.enums.DbMethodEnums;
+import com.poyee.enums.FieldOperator;
+import com.poyee.enums.FieldType;
+import com.poyee.enums.SqlEnums;
+import com.poyee.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ *  SQL 生成工具类
+ */
+@Slf4j
+public class SqlUtil {
+    // SQL 类型
+    private SqlEnums type = SqlEnums.all;
+    // 父对象
+    private BaseProvider parent;
+
+    public static SqlUtil init(BaseProvider baseProvider){
+        return new SqlUtil( baseProvider);
+    }
+
+    /**
+     * @return
+     */
+    public SqlUtil buildPage(){
+        this.type = SqlEnums.page;
+        return this;
+    }
+
+    /**
+     * @param baseProvider
+     */
+    private SqlUtil(BaseProvider baseProvider){
+        this.parent = baseProvider;
+    }
+
+    /**
+     * 根据已收集的信息生成最终的 SQL 字符串。
+     *
+     * @return 生成的 SQL 字符串
+     */
+    public String toSql() {
+        String sqlString = "";
+        switch (parent.dbMethod){
+            case SELECT_BY_ID:
+            case SELECT:
+                sqlString = buildSelectSql();
+                break;
+            case INSERT:
+                sqlString = buildInsertSql();
+                break;
+            case UPDATE:
+                sqlString = buildUpdateSql();
+                break;
+            default:
+                log.warn("No select columns or parent is null, returning empty SQL.");
+                throw new ServiceException("No select columns or parent is null.");
+        }
+        return sqlString;
+    }
+
+    /**
+     * 构建插入 SQL 语句
+     *
+     * @return 插入 SQL 语句
+     */
+    private String buildInsertSql() {
+        // 非空检查
+        if (parent == null || parent.insertMap == null || parent.insertMap.isEmpty()) {
+            log.warn("Parent or insert map is null or empty, returning empty SQL.");
+            throw new ServiceException("Parent or insert map is null or empty.");
+        }
+
+        if (parent.superTable == null || parent.superTable.getTableName() == null || parent.superTable.getTableName().isEmpty()) {
+            log.warn("Super table or table name is null or empty, returning empty SQL.");
+            throw new ServiceException("Super table or table name is null or empty.");
+        }
+
+        // 获取键和值列表并进行安全检查
+        List<String> columns = new ArrayList<>(parent.insertMap.keySet());
+        List<Object> values = new ArrayList<>(parent.insertMap.values());
+
+        if (columns.isEmpty() || values.isEmpty()) {
+            log.warn("Insert map contains no valid columns or values, returning empty SQL.");
+            throw new ServiceException("Insert map contains no valid columns or values.");
+        }
+
+        // 校验列名和值是否合法
+        for (String column : columns) {
+            if (column == null || column.isEmpty()) {
+                log.warn("Invalid column name: {}", column);
+                throw new ServiceException("Invalid column name in insert map.");
+            }
+        }
+
+        try {
+            // 构建 SQL 字符串
+            StringBuilder sql = new StringBuilder();
+            sql.append("INSERT INTO ")
+               .append(parent.superTable.getTableName())
+               .append(" (")
+               .append(columns.stream().map(ObjectUtil::escapeIdentifier).collect(Collectors.joining(", "))) // 转义列名
+               .append(") VALUES (")
+               .append(ObjectUtil.join(values)) // 转义值
+               .append(")");
+
+            // 格式化并记录日志
+            String sqlString = sql.toString().trim();
+            log.info("Generated SQL: {}", sqlString); // 记录生成的 SQL
+            return sqlString;
+
+        } catch (Exception e) {
+            log.error("Error building insert SQL. Parent: {}, Columns: {}, Values: {}", parent, columns, values, e);
+            throw new ServiceException("Error building insert SQL.", e);
+        }
+    }
+
+    /**
+     * 构建更新 SQL 语句
+     * @return
+     */
+    @NotNull
+    private String buildUpdateSql(){
+        // 非空检查
+        if (parent == null || parent.updateMap == null || parent.updateMap.isEmpty()) {
+            log.warn("Parent or update map is null or empty, returning empty SQL.");
+            throw new ServiceException("Parent or update map is null or empty.");
+        }
+
+        if (parent.superTable == null || parent.superTable.getTableName() == null || parent.superTable.getTableName().isEmpty()) {
+            log.warn("Super table or table name is null or empty, returning empty SQL.");
+            throw new ServiceException("Super table or table name is null or empty.");
+        }
+        //取出 id
+        Object value = parent.updateMap.get("id");
+        Object idValue = parent.updateMap.remove("id");
+
+        // 获取键和值列表并进行安全检查
+        List<String> columns = new ArrayList<>(parent.updateMap.keySet());
+        List<Object> values = new ArrayList<>(parent.updateMap.values());
+
+        if (columns.isEmpty() || values.isEmpty()) {
+            log.warn("Update map contains no valid columns or values, returning empty SQL.");
+            throw new ServiceException("Update map contains no valid columns or values.");
+        }
+
+        for (String column : columns) {
+            if (column == null || column.isEmpty()) {
+                log.warn("Invalid column name: {}", column);
+                throw new ServiceException("Invalid column name in update map.");
+            }
+        }
+
+        try {
+            // 构建 SQL 字符串
+            StringBuilder sql = new StringBuilder();
+            sql.append("UPDATE ")
+               .append(parent.superTable.getTableName())
+               .append(" SET ");
+            for (int i = 0; i < columns.size(); i++) {
+                sql.append(columns.get(i)).append(" = ");
+                if((values.get(i) instanceof Date)){
+                    sql.append("'").append(ObjectUtil.escapeValue(values.get(i))).append("'");
+                }else{
+                    sql.append(ObjectUtil.escapeValue(values.get(i))); // 转义值
+                }
+                 sql.append(i < columns.size() - 1 ? ", " : " ");
+            }
+            //判断where 是否有值
+            if (!parent.whereInfos.isEmpty()) {
+                appendWhereClauses(sql);
+            } else {
+                sql.append("WHERE ")
+                   .append(parent.superTable.getPrimaryKey())
+                   .append(" = ")
+                   .append(ObjectUtil.escapeValue(value));
+            }
+            //判断是否有条件
+            if(!sql.toString().contains(" WHERE ")){
+                log.warn("No where condition found.");
+                throw new ServiceException("No where condition found.");
+            }
+            return sql.toString();
+        } catch (Exception e) {
+            log.error("Error building update SQL. Parent: {}, Columns: {}, Values: {}", parent, columns, values, e);
+            throw new ServiceException("Error building update SQL.", e);
+        }
+    }
+
+    /**
+     * 构建 SELECT 语句。
+     * @return
+     */
+    @NotNull
+    private String buildSelectSql() {
+
+        // 检查是否有选择的列和父对象是否存在,如果不存在,则记录警告并抛出异常
+        if (parent == null || CollectionUtils.isEmpty(parent.selectColumns)) {
+            log.warn("No select columns or parent is null, returning empty SQL.");
+            throw new ServiceException("No select columns or parent is null.");
+        }
+        // 开始构建 SQL 语句的 SELECT 部分
+        StringBuilder sql = new StringBuilder();
+        sql.append("SELECT ")
+           .append(String.join(", ", parent.selectColumns))
+           .append(" ");
+
+        // 依次调用方法以补充 SQL 语句的 FROM、JOIN 和 WHERE 子句
+        appendFromClause(sql);
+        appendJoins(sql);
+        appendWhereClauses(sql);
+
+        // 将构建好的 SQL 字符串进行格式化并记录日志
+        String sqlString = sql.toString().trim();
+        switch (type) {
+            case page:
+                //生成count sql
+                String[] split = sqlString.split("FROM");
+                //获取 表字段 中 distinct
+                String countCount = "0";
+                if(CollectionUtils.isNotEmpty(parent.selectColumns)){
+                    List<String> distinctColumns = parent.selectColumns.stream()
+                                                                       .filter(column -> column.contains("DISTINCT"))
+                                                                       //移除 单字段 as 及之后的字符
+                                                                       .map(column -> column.replaceAll("\\s+AS\\s+.*", ""))
+                                                                       .collect(Collectors.toList());
+                    if(CollectionUtils.isNotEmpty(distinctColumns)){
+                        // 使用逗号分隔
+                        countCount = String.join(",", distinctColumns);
+                        //末位 为逗号 则删除
+                        if(countCount.endsWith(",")){
+                            countCount = countCount.substring(0, countCount.length() - 1);
+                        }
+                    }
+                }
+                //生成count sql
+                parent.pageCountSql = "select count("+countCount+") from " + split[1];
+                //拼接排序
+                if (!parent.orderByMap.isEmpty()) {
+                    sql.append(" ORDER BY ");
+                    List<Integer> list = new ArrayList<>(parent.orderByMap.keySet());
+                    Collections.sort(list);
+                    for (Integer integer : list) {
+                        sql.append(parent.orderByMap.get(integer)).append(",");
+                    }
+                    sql.deleteCharAt(sql.length() - 1);
+                }
+                //生成分页sql
+                sqlString = sql.append(" LIMIT ")
+                               .append(parent.mPage.getSize())
+                               .append(" OFFSET ")
+                               .append((parent.mPage.getCurrent() - 1) * parent.mPage.getSize())
+                               .append(" ")
+                               .toString();
+                break;
+            case selectOne:
+                sqlString = sql.append(" LIMIT 1 ").toString();
+                break;
+            default:
+                break;
+        }
+        // 返回构建好的 SQL 字符串
+        //sql 格式化打印
+        log.info("SQL: {}", sqlString);
+        return sqlString;
+    }
+
+    /**
+     * 拼接 FROM 子句。
+     *
+     * @param sql SQL 字符串构建器
+     */
+    private void appendFromClause(StringBuilder sql) {
+        if (Objects.nonNull(parent.superTable)) {
+            sql.append("FROM ")
+               .append(parent.superTable.getTableName())
+               .append(" ")
+               .append(parent.superTable.getAlias())
+               .append(" ");
+        }
+    }
+    /**
+     * 将 LEFT JOIN 信息拼接到 SQL 字符串中。
+     *
+     * @param sql SQL 字符串构建器
+     */
+    private void appendJoins(StringBuilder sql) {
+        // 检查是否有左连接信息需要添加
+        if (CollectionUtils.isNotEmpty(parent.leftJoinInfos)) {
+            for (LeftJoinInfo leftJoinInfo : parent.leftJoinInfos) {
+                // 跳过空的 LeftJoinInfo
+                if (leftJoinInfo == null) {
+                    continue;
+                }
+                // 拼接 LEFT JOIN 子句
+                sql.append("LEFT JOIN ")
+                   .append(leftJoinInfo.getTableInfo().getTableName())
+                   .append(" ")
+                   .append(leftJoinInfo.getTableInfo().getAlias())
+                   .append(" ON ")
+                   .append(leftJoinInfo.getLeftTableInfo().getAlias())
+                   .append(".")
+                   .append(leftJoinInfo.getLeftFieldName())
+                   .append(" = ")
+                   .append(leftJoinInfo.getTableInfo().getAlias())
+                   .append(".")
+                   .append(leftJoinInfo.getFieldName())
+                   .append(" ");
+            }
+        }
+    }
+
+    /**
+     * 将 WHERE 条件拼接到 SQL 字符串中。
+     *
+     * @param sql SQL 字符串构建器
+     */
+    private void appendWhereClauses(StringBuilder sql) {
+        // 检查是否有 WHERE 信息需要添加
+        if (CollectionUtils.isNotEmpty(parent.whereInfos)) {
+            StringBuilder whereSql = new StringBuilder("WHERE ");
+            boolean first = true;
+            // 遍历所有 WHERE 信息并构建 WHERE 子句
+            for (WhereInfo whereInfo : parent.whereInfos) {
+                // 除第一个 WHERE 条件外,其他条件前添加 "AND"
+                if (!first) {
+                    whereSql.append("AND ");
+                }
+                // 构建并添加 WHERE 条件
+                String formatSql = buildWhereClause(whereInfo);
+                if(first && StringUtils.isNotBlank(formatSql)){
+                    first = false;
+                }
+                whereSql.append(formatSql+" ");
+            }
+            //如果最后语句为 where 则删除
+            if(whereSql.toString().endsWith("AND ")){
+                whereSql.delete(whereSql.length()-4,whereSql.length());
+            }
+            //如果最后语句为 where 则删除
+            if(whereSql.toString().endsWith("WHERE ")){
+                whereSql.delete(whereSql.length()-6,whereSql.length());
+            }
+            // 将构建好的 WHERE 子句添加到 SQL 字符串中
+            sql.append(whereSql);
+        }
+    }
+
+    /**
+     * 根据WhereInfo对象构建SQL查询的WHERE子句
+     * 此方法用于根据提供的条件信息生成SQL语句中的WHERE部分它检查WhereInfo对象及其属性的有效性,
+     * 然后根据这些属性构建WHERE子句字符串
+     *
+     * @param whereInfo 包含WHERE子句构建所需信息的对象,包括表信息、列名、操作符和值等
+     * @return 返回构建的WHERE子句字符串如果提供的信息不足以构建WHERE子句,则返回空字符串
+     */
+    private String buildWhereClause(WhereInfo whereInfo) {
+        // 检查whereInfo及其关键属性是否存在,如果任一关键属性为空,则返回空字符串
+        if (Objects.isNull(whereInfo) || Objects.isNull(whereInfo.getTableInfo())
+                || (Objects.isNull(whereInfo.getColumn()) && Objects.isNull(whereInfo.getOrColumn())
+                            && Objects.isNull(whereInfo.getIsCustom()))
+                || (Objects.nonNull(whereInfo.getIsCustom()) && CollectionUtils.isEmpty(whereInfo.getCustomConditions()))
+        ) {
+            return "";
+        }
+
+        if(Objects.isNull(whereInfo.getFieldType()) && !FieldOperator.checkEmptyValue(whereInfo.getOperator())
+                && (Objects.isNull(whereInfo.getValue()) || Objects.equals("", whereInfo.getValue()))
+                && (Objects.nonNull(whereInfo.getIsCustom()) && CollectionUtils.isEmpty(whereInfo.getCustomConditions()))){
+            return "";
+        }
+        if(Objects.isNull(whereInfo.getFieldType()) && !FieldOperator.checkEmptyValue(whereInfo.getOperator())){
+            whereInfo.setFieldType(FieldType.STRING);
+        }
+        //判断是否是自定义条件  如果是自定义条件 则 拼接自定义条件 并返回
+        if(Objects.nonNull(whereInfo.getIsCustom()) && whereInfo.getIsCustom()) {
+            return buildCustomConditionsWhere(whereInfo);
+        }
+        // 使用StringBuilder来构建WHERE子句
+        StringBuilder clause = new StringBuilder();
+        // 添加表别名和列名到WHERE子句中
+        //如果是更新 则不加别名
+        if(parent.dbMethod == DbMethodEnums.UPDATE){
+            clause.append(whereInfo.getColumn())
+                  .append(" ")
+                  .append(whereInfo.getOperator().getOperator())
+                  .append(" ")
+            ;
+            extractedValue(whereInfo, clause);
+        } else {
+            //判断 column 或 orColumn
+            String alias = whereInfo.getTableInfo().getAlias();
+            if (CollectionUtils.isNotEmpty(whereInfo.getOrColumn())) {
+                clause.append("(");
+                whereInfo.getOrColumn().forEach(column -> {
+                    clause.append(alias)
+                          .append(".")
+                          .append(column)
+                          .append(" ")
+                          .append(whereInfo.getOperator().getOperator())
+                          .append(" ")
+                    ;
+                    // 添加操作符到WHERE子句中
+                    extractedValue(whereInfo, clause);
+                    // 判断是否是最后一个列
+                    clause.append(whereInfo.getOrColumn().indexOf(column) == whereInfo.getOrColumn().size()-1 ? ")" : " OR ");
+                });
+            }else {
+                clause.append(alias)
+                      .append(".")
+                      .append(whereInfo.getColumn())
+                      .append(" ")
+                      // 添加操作符到WHERE子句中
+                      .append(whereInfo.getOperator().getOperator())
+                      .append(" ");
+                extractedValue(whereInfo, clause);
+            }
+        }
+        // 返回构建完成的WHERE子句
+        return clause.toString();
+    }
+
+    // 如果为 true,则 customConditions 中的条件将使用 OR 连接,而不是默认的 AND 连接
+    // 注意:如果 isCustomOr 为 true,则 customConditions 中的条件必须是独立的查询条件,不能依赖于其他条件
+    // 例如:
+    // @Where(table = User.class, isCustomOr = true,
+    //         customConditions = {
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "active"),
+    //             @CustomCondition(field = "role", operator = FieldOperator.IN, orFields = {"admin", "user"})
+    //         },{
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "logOff")
+    //         })
+    // 上述条件将生成 SQL 语句:WHERE (status = 'active' AND role IN ('admin', 'user')) OR (status = 'logOff')
+    // 如果为 false,则 customConditions 中的条件将使用 AND 连接
+    // 例如:
+    // @Where(table = User.class, isCustomOr = false,
+    //         customConditions = {
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "active"),
+    //             @CustomCondition(field = "role", operator = FieldOperator.IN, orFields = {"admin", "user"})
+    //         },{
+    //             @CustomCondition(field = "status", operator = FieldOperator.EQ, defaultValue = "logOff")
+    //         })
+    // 上述条件将生成 SQL 语句:WHERE status = 'active' AND role IN ('admin', 'user') AND status = 'logOff'
+    // 注意:如果 isCustomOr 为 false,则 customConditions 中的条件必须是可以同时成立的条件,否则将导致查询结果为空
+    // 注意:customConditions 中的条件每组条件之间是 AND 关系,而组与组之间 根据 isCustomOr 的值决定是 AND 还是 OR 关系
+    // 默认值为 false,表示使用 AND 连接
+    // 如果需要使用 OR 连接,则需要显式设置 isCustomOr 为 true
+    // 检查参数有效性
+    private String buildCustomConditionsWhere(WhereInfo whereInfo) {
+        if (whereInfo == null || CollectionUtils.isEmpty(whereInfo.getCustomConditions())) {
+            return "";
+        }
+        List<WhereInfo.CustomConditions> customConditions = whereInfo.getCustomConditions();
+        boolean isCustomOr = whereInfo.isCustomOr(); // 获取 isCustomOr 设置
+
+        StringBuilder result = new StringBuilder();
+        String connector = isCustomOr ? " OR " : " AND ";
+        String groupWrapperStart = isCustomOr ? "(" : "";
+        String groupWrapperEnd = isCustomOr ? ")" : "";
+
+        for (int i = 0; i < customConditions.size(); i++) {
+            WhereInfo.CustomConditions conditionsGroup = customConditions.get(i);
+            List<WhereInfo.CustomCondition> conditions = conditionsGroup.getCustomConditionList();
+
+            if (CollectionUtils.isEmpty(conditions)) {
+                continue;
+            }
+
+            // 构建当前组的条件
+            StringBuilder groupClause = new StringBuilder();
+            for (int j = 0; j < conditions.size(); j++) {
+                WhereInfo.CustomCondition condition = conditions.get(j);
+                // 构建单个条件
+                String conditionClause = buildSingleCustomCondition(whereInfo, condition);
+
+                if (StringUtils.isNotBlank(conditionClause)) {
+                    groupClause.append(conditionClause);
+                    if (j < conditions.size() - 1) {
+                        groupClause.append(" AND ");
+                    }
+                }
+            }
+
+            if (groupClause.length() > 0) {
+                if (i > 0) {
+                    result.append(connector);
+                }
+                result.append(groupWrapperStart).append(groupClause).append(groupWrapperEnd);
+            }
+        }
+
+        return result.length() > 0 ? "("+ result +")" : "";
+    }
+
+    /**
+     * 构建单个自定义条件的 SQL 子句。
+     *
+     * @param whereInfo 用于获取表别名等上下文信息
+     * @param condition 自定义条件对象
+     * @return 单个条件的 SQL 字符串
+     */
+    private String buildSingleCustomCondition(WhereInfo whereInfo, WhereInfo.CustomCondition condition) {
+        if (condition == null || StringUtils.isBlank(condition.getColumn())) {
+            return "";
+        }
+
+        StringBuilder clause = new StringBuilder();
+        String alias = whereInfo.getTableInfo().getAlias();
+
+        String field = condition.getColumn();
+        String operator = condition.getOperator().getOperator();
+        String defaultValue = condition.getDefaultValue();
+
+        // 获取值列表,用于替换占位符
+        Object value = whereInfo.getValue();
+        String valStr = String.valueOf(value);
+        String[] values = valStr.split(",");
+
+        if (StringUtils.isNotBlank(defaultValue)) {
+            // 使用正则表达式匹配 #{0}, #{1} 等形式的占位符
+            java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("#\\{(\\d+)\\}");
+            java.util.regex.Matcher matcher = pattern.matcher(defaultValue);
+
+            StringBuffer sb = new StringBuffer();
+
+            while (matcher.find()) {
+                String indexStr = matcher.group(1);
+                int index = Integer.parseInt(indexStr);
+
+                // 替换占位符为实际值,若索引越界则保留原值
+                if (index >= 0 && index < values.length) {
+                    String replacement = values[index].trim();
+                    matcher.appendReplacement(sb, replacement);
+                } else {
+                    matcher.appendReplacement(sb, matcher.group(0)); // 保留原占位符
+                }
+            }
+            matcher.appendTail(sb);
+
+            // 使用替换后的值
+            defaultValue = sb.toString();
+            condition.setValue(defaultValue);
+        }else{
+            condition.setValue("");
+        }
+
+        clause.append(alias).append(".").append(field).append(" ").append(operator).append(" ");
+        //拼接值
+        extractedValue(condition,clause);
+
+        return clause.toString();
+    }
+
+    /**
+     * 提取值
+     * @param whereInfo
+     * @param clause
+     */
+    private static void extractedValue(WhereInfo whereInfo, StringBuilder clause) {
+        //特殊处理 如果是between 或 not between
+        if (whereInfo.getOperator() == FieldOperator.BETWEEN || whereInfo.getOperator() == FieldOperator.NOTBETWEEN) {
+            // 如果是between 或 not between,则添加起始值和结束值到WHERE子句中
+            clause.append(FormatValueUtil.formatStringValue(whereInfo));
+        }else {
+            // 根据字段类型添加相应的值到WHERE子句中
+            if (whereInfo.getFieldType() == FieldType.STRING) {
+                // 对字符串类型进行特殊格式化处理
+                clause.append(FormatValueUtil.formatStringValue(whereInfo));
+            } else if (whereInfo.getFieldType() == FieldType.NUMBER) {
+                // 直接添加数值类型到WHERE子句中,无需额外格式化
+                clause.append(whereInfo.getValue());
+            } else if (whereInfo.getFieldType() == FieldType.DATE) {
+                // 对日期类型进行转义处理,防止SQL注入
+                clause.append("'").append(FormatValueUtil.escapeValue(whereInfo.getValue())).append("'");
+            } else {
+                // 直接添加到WHERE子句中,无需额外格式化
+                clause.append(whereInfo.getValue());
+            }
+        }
+    }
+
+    /**
+     * 提取值
+     * @param condition
+     * @param clause
+     */
+    private static void extractedValue(WhereInfo.CustomCondition condition, StringBuilder clause) {
+        //特殊处理 如果是between 或 not between
+        if (condition.getOperator() == FieldOperator.BETWEEN || condition.getOperator() == FieldOperator.NOTBETWEEN) {
+            // 如果是between 或 not between,则添加起始值和结束值到WHERE子句中
+            clause.append(FormatValueUtil.formatStringValue(condition));
+        }else {
+            // 根据字段类型添加相应的值到WHERE子句中
+            if (condition.getFieldType() == FieldType.STRING) {
+                // 对字符串类型进行特殊格式化处理
+                clause.append(FormatValueUtil.formatStringValue(condition));
+            } else if (condition.getFieldType() == FieldType.NUMBER) {
+                // 直接添加数值类型到WHERE子句中,无需额外格式化
+                clause.append(condition.getValue());
+            } else if (condition.getFieldType() == FieldType.DATE) {
+                // 对日期类型进行转义处理,防止SQL注入
+                clause.append("'").append(FormatValueUtil.escapeValue(condition.getValue())).append("'");
+            } else {
+                // 直接添加到WHERE子句中,无需额外格式化
+                clause.append(condition.getValue());
+            }
+        }
+    }
+
+    /**
+     * @return
+     */
+    public SqlUtil buildOne() {
+        this.type = SqlEnums.selectOne;
+        return this;
+    }
+
+    /**
+     * @return
+     */
+    public SqlUtil buildSum() {
+        this.type = SqlEnums.sum;
+        return this;
+    }
+}
+

+ 63 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/TableInfoUtil.java

@@ -0,0 +1,63 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.base.mapper.provider.domain.TableInfo;
+import com.poyee.common.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class TableInfoUtil {
+
+    public static TableInfoUtil init(BaseProvider baseProvider) {
+        return new TableInfoUtil(baseProvider);
+    }
+
+    private TableInfoUtil(BaseProvider baseProvider) {
+        this.parent = baseProvider;
+    }
+
+    private final BaseProvider parent;
+
+    /**
+     * 初始化表信息
+     * 该方法用于根据给定的类获取其对应的数据库表名,并生成一个TableInfo对象,最终将该对象存入parent的tableInfos中
+     * 主要功能包括:
+     * 1. 检查给定类是否使用了@TableName注解,如果没有,则抛出异常
+     * 2. 获取@TableName注解的值,作为表名
+     * 3. 根据parent.tableCount的值生成表的别名
+     * 4. 创建TableInfo对象,并将其存入parent.tableInfos中
+     * 5. 增加parent.tableCount的值,为下一个表生成别名做准备
+     *
+     * @param aClass 要初始化表信息的类,该类应使用@TableName注解来指定表名
+     */
+    public TableInfo initTableInfo(Class<?> aClass) {
+        // 检查给定类是否使用了@TableName注解,如果没有,则抛出异常
+        if (!aClass.isAnnotationPresent(TableName.class)) {
+            throw new ServiceException(aClass.getName() + "未设置表名");
+        }
+        // 获取@TableName注解的值,作为表名
+        TableName tableName = aClass.getAnnotation(TableName.class);
+        String firstTableName = tableName.value();
+        //判断是否已经存在表
+        if(parent.tableInfos.containsKey(aClass.getName())){
+            return parent.tableInfos.get(aClass.getName());
+        }
+        // 根据parent.tableCount的值生成表的别名
+        String thisAlisa = "_" + parent.tableCount;
+        if (parent.tableCount == 0) {
+            thisAlisa = "";
+        }
+        String primaryKey = "id";
+        //获取主键
+        if(aClass.isAnnotationPresent(TableId.class)){
+            primaryKey = aClass.getAnnotation(TableId.class).value();
+        }
+        // 创建TableInfo对象,并将其存入parent.tableInfos中
+        parent.tableInfos.put(aClass.getName(), new TableInfo(aClass.getName(), aClass, firstTableName, "t" + thisAlisa, parent.tableCount, primaryKey));
+        // 增加parent.tableCount的值,为下一个表生成别名做准备
+        parent.tableCount++;
+        return parent.tableInfos.get(aClass.getName());
+    }
+}

+ 167 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/UpdateUtil.java

@@ -0,0 +1,167 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.alibaba.excel.util.StringUtils;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.poyee.annotation.db.Where;
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.base.mapper.provider.domain.TableInfo;
+import com.poyee.base.mapper.provider.domain.WhereInfo;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.enums.DbMethodEnums;
+import com.poyee.enums.FieldOperator;
+import com.poyee.util.DateUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.poyee.base.mapper.provider.ErrMessageContent.*;
+
+@Slf4j
+public class UpdateUtil {
+
+    private BaseProvider parent;
+
+    private UpdateUtil(BaseProvider parent) {
+        this.parent = parent;
+    }
+
+    public static UpdateUtil init(BaseProvider parent) {
+        return new UpdateUtil(parent);
+    }
+
+    public UpdateUtil checkUpdateBean(Object object) {
+        if(Objects.isNull(object)){
+            log.warn("传入的对象为空!");
+            throw new ServiceException(ERROR_MESSAGE_OBJECT_IS_NULL);
+        }
+        Class<?> aClass = object.getClass();
+        if(!aClass.isAnnotationPresent(TableName.class)){
+            log.warn("对象必须包含@TableName注解");
+            throw new ServiceException(ERROR_MESSAGE_TABLE_NAME_MISSING_2);
+        }
+        TableName tableNameAnnotation = aClass.getAnnotation(TableName.class);
+        if(Objects.isNull(tableNameAnnotation.value()) || tableNameAnnotation.value().trim().isEmpty()){
+            log.warn("表名不能为空");
+            throw new ServiceException(ERROR_MESSAGE_TABLE_NAME_IS_NULL);
+        }
+
+        // 初始化表信息
+        TableInfo tableInfo = TableInfoUtil.init(parent)
+                                           .initTableInfo(aClass);
+        parent.superTable = tableInfo;
+//        parent.superTable = parent.tableInfos.get(aClass.getName());
+        parent.update = object;
+        return this;
+    }
+
+    /**
+     * 初始化更新条件
+     */
+    public void initUpdate() {
+        Object update = parent.update;
+        if (update == null || parent.updateMap == null) {
+            log.warn("更新对象或映射为空,无法生成SQL。对象: {}", update);
+            throw new ServiceException(getErrorMessage(ERROR_MESSAGE_BEAN_NULL,"更新"));
+        }
+
+        try {
+            Map<String, Object> updateMap = parent.updateMap;
+            updateMap.clear(); // 清空旧数据
+            processFields(update, updateMap);
+
+            if (updateMap.isEmpty()) {
+                log.warn("没有有效的更新字段,对象: {}", update);
+                throw new ServiceException(getErrorMessage(ERROR_MESSAGE_OPERA_NULL,"更新"));
+            }
+        } catch (IllegalAccessException e) {
+            log.error("访问字段时发生错误", e);
+            throw new ServiceException(ERROR_MESSAGE_FIELD_ACCESS);
+        } catch (IllegalArgumentException e) {
+            log.error("非法参数错误", e);
+            throw new ServiceException(ERROR_MESSAGE_ILLEGAL_ARGUMENT);
+        } catch (Exception e) {
+            log.error(getErrorMessage(ERROR_MESSAGE_BUILD_SQL,"更新"), e);
+            throw new ServiceException(getErrorMessage(ERROR_MESSAGE_BUILD_SQL,"更新"));
+        }
+    }
+
+
+    /**
+     * 处理字段并填充到映射中
+     *
+     * @param object    对象
+     * @param updateMap 插入映射
+     * @throws IllegalAccessException 如果字段访问失败
+     */
+    private void processFields(Object object, Map<String, Object> updateMap) throws IllegalAccessException {
+        Class<?> aClass = object.getClass();
+        Field[] declaredFields = aClass.getDeclaredFields();
+
+        for (Field field : declaredFields) {
+            field.setAccessible(true);
+            // 获取字段注解
+            if(!field.isAnnotationPresent(Where.class)) {
+                if (field.isAnnotationPresent(TableField.class)) {
+                    TableField tableField = field.getAnnotation(TableField.class);
+                    if (tableField.exist()) {
+                        Object value = field.get(object);
+                        if (Objects.nonNull(value)) {
+                            if (value instanceof Date) {
+                                updateMap.put(tableField.value(), DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, (Date) value));
+                            } else {
+                                updateMap.put(tableField.value(), value);
+                            }
+                        }
+                    }
+                    //判断是否需要设置 创建,更新时间
+                    if ( Objects.equals(parent.dbMethod, DbMethodEnums.UPDATE)
+                        && Objects.equals(tableField.fill() , FieldFill.INSERT_UPDATE)
+                        && Objects.equals(field.getName(), "updateTime")
+                        ) {
+                        updateMap.put(tableField.value(), new Date());
+                    }
+                }else if (field.isAnnotationPresent(TableId.class)) {
+                    TableId tableId = field.getAnnotation(TableId.class);
+                    Object value = field.get(object);
+                    if (Objects.nonNull(value)) {
+                        if (value instanceof Date) {
+                            updateMap.put(tableId.value(), DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, (Date) value));
+                        } else {
+                            updateMap.put(tableId.value(), value);
+                        }
+                    }
+                }
+            }else{
+                //设置where 条件
+                Where where = field.getAnnotation(Where.class);
+                try {
+                    Object value = field.get(object);
+                    // 根据 Where 注解配置和字段值,决定是否创建 WHERE 信息
+                    if ( (value instanceof String && StringUtils.isNotBlank(String.valueOf(value)))
+                            || (!(value instanceof String) && Objects.nonNull(value))
+                            || FieldOperator.checkEmptyValue(where)) {
+                        Class<?> table = where.table();
+                        // 确保表信息已初始化
+                        if (!parent.tableInfos.containsKey(table.getName())) {
+                            TableInfoUtil.init(parent).initTableInfo(table);
+                        }
+                        WhereInfo whereInfo = new WhereInfo(parent.tableInfos.get(table.getName()));
+                        whereInfo.setColumn(where.field());
+                        whereInfo.setValue(value);
+                        whereInfo.setOperator(where.operator());
+                        whereInfo.setFieldType(where.columnType());
+                        parent.whereInfos.add(whereInfo);
+                    }
+                } catch (IllegalAccessException e) {
+                    throw new ServiceException("无法访问字段:" + field.getName(), e);
+                }
+            }
+        }
+    }
+}

+ 184 - 0
py-base/src/main/java/com/poyee/base/mapper/provider/util/WhereUtil.java

@@ -0,0 +1,184 @@
+package com.poyee.base.mapper.provider.util;
+
+import com.alibaba.excel.util.StringUtils;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.poyee.annotation.db.Where;
+import com.poyee.base.mapper.provider.BaseProvider;
+import com.poyee.base.mapper.provider.domain.WhereInfo;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.enums.FieldOperator;
+import com.poyee.enums.FieldType;
+import com.poyee.util.DateUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+@Slf4j
+public class WhereUtil {
+
+    public static WhereUtil init(BaseProvider baseProvider){
+        return new WhereUtil(baseProvider);
+    }
+
+    private WhereUtil(BaseProvider baseProvider) {
+        this.parent = baseProvider;
+    }
+    private BaseProvider parent;
+
+    /**
+     * 处理 WHERE 条件。
+     *
+     * @param object 对象实例
+     * @param aClass 类对象
+     * @param whereInfos WHERE 信息列表
+     * @param field 字段对象
+     */
+    public void build(Object object, Class<?> aClass, List<WhereInfo> whereInfos, Field field) {
+        // 检查字段是否用 Where 注解标记
+        if (field.isAnnotationPresent(Where.class)) {
+            Where where = field.getAnnotation(Where.class);
+            try {
+                Object value = field.get(object);
+                // 根据 Where 注解配置和字段值,决定是否创建 WHERE 信息
+                if ( (value instanceof String && StringUtils.isNotBlank(String.valueOf(value)))
+                        || (!(value instanceof String) && Objects.nonNull(value))
+                        || FieldOperator.checkEmptyValue(where)) {
+                    Class<?> table = where.table();
+                    // 确保表信息已初始化
+                    if (!parent.tableInfos.containsKey(table.getName())) {
+                        TableInfoUtil.init(parent).initTableInfo(table);
+                    }
+                    WhereInfo whereInfo = new WhereInfo(parent.tableInfos.get(table.getName()));
+                    // 处理自定义查询条件
+                    if (checkCustomConditions(where, whereInfo,value)){
+                        whereInfos.add(whereInfo);
+                        return;
+                    }
+                    //如果 where orField 有值
+                    String[] orFields = where.orFields();
+                    if(Objects.nonNull(orFields) && orFields.length > 0){
+                        // 数组转集合
+                        whereInfo.setOrColumn(Arrays.asList(orFields));
+                    }else {
+                        if ((Objects.equals(where.operator(), FieldOperator.BETWEEN) || Objects.equals(where.operator(), FieldOperator.NOTBETWEEN)) && where.isEnd()) {
+                            whereInfo = whereInfos.stream()
+                                                  .filter(whereInfo1 -> Objects.equals(whereInfo1.getColumn(), where.field()))
+                                                  .findFirst()
+                                                  .orElse(whereInfo);
+                        }
+                        whereInfo.setColumn(where.field());
+                    }
+                    if((Objects.equals(where.operator(),FieldOperator.BETWEEN)
+                            || Objects.equals(where.operator(),FieldOperator.NOTBETWEEN)) && where.isEnd() ){
+                        whereInfo.setEndValue(valueCase(value,field));
+                    } else {
+                        whereInfo.setValue(valueCase(value,field));
+                    }
+                    whereInfo.setOperator(where.operator());
+                    whereInfo.setFieldType(where.columnType());
+                    whereInfos.add(whereInfo);
+                }
+            } catch (IllegalAccessException e) {
+                throw new ServiceException("无法访问字段:" + field.getName(), e);
+            }
+        }else {
+            // 检查字段是否用 TableField 注解标记
+            if (field.isAnnotationPresent(TableField.class)) {
+                TableField tableField = field.getAnnotation(TableField.class);
+                // 如果字段在数据库中存在,则尝试构建 WHERE 信息
+                if (tableField.exist()) {
+                    try {
+                        Object value = field.get(object);
+                        // 如果字段值非空,则创建 WHERE 信息并添加到列表中
+                        if ((value instanceof String && StringUtils.isNotBlank(String.valueOf(value))) || (!(value instanceof String) && Objects.nonNull(value))) {
+                            WhereInfo whereInfo = new WhereInfo(parent.tableInfos.get(aClass.getName()));
+                            whereInfo.setColumn(tableField.value());
+                            whereInfo.setValue(valueCase(value,field));
+                            whereInfo.setOperator(FieldOperator.EQ);
+                            whereInfo.setFieldType(valueCaseType(value));
+                            whereInfos.add(whereInfo);
+                        }
+                    } catch (IllegalAccessException e) {
+                        throw new ServiceException("无法访问字段:" + field.getName(), e);
+                    }
+                }
+            }else if(  field.isAnnotationPresent(TableId.class)){
+                TableId tableId = field.getAnnotation(TableId.class);
+                try {
+                    Object value = field.get(object);
+                    // 如果字段值非空,则创建 WHERE 信息并添加到列表中
+                    if ((value instanceof String && StringUtils.isNotBlank(String.valueOf(value))) || (!(value instanceof String) && Objects.nonNull(value))) {
+                        WhereInfo whereInfo = new WhereInfo(parent.tableInfos.get(aClass.getName()));
+                        whereInfo.setColumn(tableId.value());
+                        whereInfo.setValue(valueCase(value,field));
+                        whereInfo.setOperator(FieldOperator.EQ);
+                        whereInfo.setFieldType(valueCaseType(value));
+                        whereInfos.add(whereInfo);
+                    }
+                } catch (IllegalAccessException e) {
+                    throw new ServiceException("无法访问字段:" + field.getName(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 检查自定义条件。
+     * @param where
+     * @param whereInfo
+     * @return
+     */
+    private static boolean checkCustomConditions(Where where, WhereInfo whereInfo, Object value) {
+        List<WhereInfo.CustomConditions> customConditionList = new ArrayList<>();
+        //判断customConditions 是否有 自定义
+        Where.CustomConditions[] customConditions = where.customConditions();
+        if (CollectionUtils.isNotEmpty(Arrays.asList(customConditions))) {
+            whereInfo.setIsCustom(true);
+            whereInfo.setCustomOr(where.isCustomOr());
+            whereInfo.setValue(value);
+            Arrays.asList(customConditions)
+                  .forEach(items -> {
+                      WhereInfo.CustomConditions customCondition = new WhereInfo.CustomConditions();
+                      Arrays.asList(items.value())
+                            .forEach(item -> {
+                                customCondition.add(new WhereInfo.CustomCondition(item.field(), item.defaultValue(), item.operator(), item.columnType()));
+                            });
+                      customConditionList.add(customCondition);
+                  });
+            whereInfo.setCustomConditions(customConditionList);
+            return true;
+        }
+        return false;
+    }
+
+    private FieldType valueCaseType(Object value) {
+        if (value instanceof String) {
+            return FieldType.STRING;
+        } else if (value instanceof Integer) {
+            return FieldType.NUMBER;
+        }else if (value instanceof Boolean) {
+            return FieldType.BOOLEAN;
+        }else if (value instanceof Date) {
+            return FieldType.DATE;
+        }
+        return FieldType.STRING;
+    }
+
+    /**
+     * @param value
+     * @return
+     */
+    private Object valueCase(Object value,Field field) {
+        //如果 是 时间类型  转正格式化的 字符串
+        if (value instanceof Date && field.isAnnotationPresent(DateTimeFormat.class)) {
+            DateTimeFormat dateFormat = field.getAnnotation(DateTimeFormat.class);
+            return DateUtils.parseDateToStr(dateFormat.pattern(), (Date) value);
+        }
+        return value;
+    }
+
+}

+ 14 - 0
py-base/src/main/java/com/poyee/base/service/BaseService.java

@@ -0,0 +1,14 @@
+package com.poyee.base.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.poyee.base.dto.BaseDto;
+import com.poyee.base.dto.BaseReq;
+import com.poyee.base.dto.UserInfo;
+
+public interface BaseService<R extends BaseReq, D extends BaseDto> extends IService<D> {
+    UserInfo getUserInfo();
+    public MPJLambdaWrapper<D> mpjWrapper(Object req);
+    public MPJLambdaWrapper<D> mpjWrapper(MPJLambdaWrapper<D> mpjWrapper,Object req,String alias);
+
+}

+ 295 - 0
py-base/src/main/java/com/poyee/base/service/impl/BaseServiceImpl.java

@@ -0,0 +1,295 @@
+package com.poyee.base.service.impl;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.poyee.base.dto.UserInfo;
+import com.poyee.common.exception.AuthException;
+import com.poyee.annotation.MpjWapper;
+import com.poyee.base.dto.BaseDto;
+import com.poyee.base.dto.BaseReq;
+import com.poyee.base.mapper.IBaseMapper;
+import com.poyee.base.service.BaseService;
+import com.poyee.framework.domain.MpjWrapper;
+import com.poyee.common.exception.ServiceException;
+import com.poyee.i18n.I18nUtils;
+import com.poyee.redis.util.RedisUtils;
+import com.poyee.util.DateUtils;
+import com.poyee.util.ObjectUtil;
+import com.poyee.util.ServletUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+import static com.poyee.i18n.I18nMessageEnums.*;
+
+/**
+ * @param <E>
+ * @param <R>
+ * @param <D>
+ */
+@Service
+public abstract class BaseServiceImpl<E extends IBaseMapper<D>, R extends BaseReq, D extends BaseDto> extends ServiceImpl<E, D> implements BaseService<R, D> {
+
+    @Autowired
+    protected RedisUtils redisUtils;
+
+    /**
+     * 获取当前用户ID
+     * @return
+     */
+    public Long getUserId(){
+        UserInfo userInfo = ServletUtils.getUserInfo();
+        return StringUtils.isNotBlank(userInfo.getUserId())?Long.parseLong(userInfo.getUserId()): Long.valueOf(userInfo.getId());
+    }
+    /**
+     * 获取当前用户ID
+     * @return
+     */
+    public String getLoginName(){
+        try {
+            UserInfo userInfo = ServletUtils.getUserInfo();
+            return StringUtils.isNotBlank(userInfo.getDisplayName()) ? userInfo.getDisplayName() : userInfo.getUserId();
+        }catch (Exception e) {
+            log.error("获取用户信息失败",e);
+//            throw new AuthException(I18nUtils.get(GET_USER_INFO_ERROR));
+        }
+        return "游客";
+    }
+    /**
+     * 检查用户权限
+     * @param p
+     * @param <P>
+     */
+    public <P extends BaseReq> void checkAndSetUserId(P p){
+        try {
+            UserInfo userInfo = ServletUtils.getUserInfo();
+            //解析 p 中的user_id
+            if (Objects.nonNull(p)) {
+                Field userId = p.getClass().getDeclaredField("userId");
+                if (Objects.nonNull(userId)) {
+                    userId.setAccessible(true);
+                    String typeName = userId.getType().getName();
+                    if(Objects.equals("java.lang.Long",typeName)){
+                        userId.set(p, Long.valueOf(userInfo.getId()));
+                    }else if(Objects.equals("java.lang.Integer",typeName)){
+                        userId.set(p, userInfo.getId());
+                    }
+                }
+            }
+        } catch (NoSuchFieldException ne) {
+            log.error("请求参数中无userId",ne);
+            throw new AuthException(402, I18nUtils.get(NO_PERMISSION, "【需指定用户】") );
+        } catch (Exception e) {
+            log.error("获取用户信息失败",e);
+            throw new AuthException(I18nUtils.get(GET_USER_INFO_ERROR));
+        }
+    }
+    /**
+     * 检查商户权限
+     * @param p
+     * @param <P>
+     */
+    public <P extends BaseReq> void checkAndSetMerchantId(P p){
+        try {
+            if(p.isDoRedis()){
+                log.debug(DateUtils.dateTime()+" 定时任务【"+p.getServiceMethodName()+"】缓存");
+                return ;
+            }
+            UserInfo userInfo = ServletUtils.getUserInfo();
+            if(userInfo.isMerchant()) {
+                //解析 p 中的user_id
+                if (Objects.nonNull(p)) {
+                    Field merchantId = p.getClass().getDeclaredField("merchantId");
+                    if (Objects.nonNull(merchantId)) {
+                        merchantId.setAccessible(true);
+                        String typeName = merchantId.getType().getName();
+                        if (Objects.equals("java.lang.Long", typeName)) {
+                            merchantId.set(p, Long.valueOf(userInfo.getMerchantId()));
+                        } else if (Objects.equals("java.lang.Integer", typeName)) {
+                            merchantId.set(p, userInfo.getMerchantId());
+                        }
+                    }
+                }
+            }
+        } catch (NoSuchFieldException ne) {
+            log.error("请求参数中无userId",ne);
+            throw new AuthException(402, I18nUtils.get(NO_PERMISSION,"【权限不足】"));
+        } catch (Exception e) {
+            log.error("获取用户信息失败",e);
+            throw new AuthException(I18nUtils.get(GET_USER_INFO_ERROR));
+        }
+    }
+    /**
+     * 检查商户权限
+     * @param p
+     * @param <P>
+     */
+    public <P extends BaseReq> void checkAndPutMerchantId(P p){
+        try {
+            UserInfo userInfo = ServletUtils.getUserInfo();
+            if(userInfo.isMerchant()) {
+                //解析 p 中的merchantId
+                if (Objects.nonNull(p)) {
+                    Field merchantId = p.getClass().getDeclaredField("merchantId");
+                    if (Objects.nonNull(merchantId)) {
+                        merchantId.setAccessible(true);
+                        String typeName = merchantId.getType().getName();
+                        if (Objects.equals("java.lang.Long", typeName)) {
+                            merchantId.set(p, Long.valueOf(userInfo.getMerchantId()));
+                        } else if (Objects.equals("java.lang.Integer", typeName)) {
+                            merchantId.set(p, userInfo.getMerchantId());
+                        }
+                    }
+                }
+            }
+        } catch (NoSuchFieldException | IllegalAccessException ne) {
+            log.error("请求参数中无merchantId",ne);
+            throw new AuthException(402, I18nUtils.get(NO_PERMISSION, "【权限不足】"));
+        } catch (AuthException e) {
+            log.error("获取用户信息失败",e);
+        }
+    }
+    /**
+     * 获取请求用户的信息
+     *
+     * @return
+     */
+    @Transactional
+    @Override
+    public UserInfo getUserInfo() {
+        return ServletUtils.getUserInfo();
+    }
+
+    /**
+     * @param req
+     * @return
+     */
+    public MPJLambdaWrapper<D> mpjWrapper(Object req) {
+        return mpjWrapper(new MPJLambdaWrapper<>(),req,"t");
+    }
+
+    /**
+     * @param req
+     * @return
+     */
+    public MPJLambdaWrapper<D> mpjWrapper(MPJLambdaWrapper<D> mpjLambdaWrapper,Object req,String alias) {
+        try {
+            if (null != req) {
+                Field[] declaredFields = req.getClass().getDeclaredFields();
+                Map<String, MpjWrapper> mpjWapperMap = new HashMap<>();
+                for (Field field : declaredFields) {
+                    field.setAccessible(true);
+                    String name = field.getName();
+                    //判断是否有注解 MpjWapper
+                    MpjWapper mpjWapper = field.getAnnotation(MpjWapper.class);
+                    if (Objects.nonNull(mpjWapper)) {
+                        if (Objects.nonNull(mpjWapper.value()) ) {
+                            MpjWrapper mpjWrapper = Optional.ofNullable(mpjWapperMap.get(mpjWapper.value()))
+                                                            .orElse(new MpjWrapper());
+                            if(Objects.nonNull(field.get(req))) {
+                                mpjWrapper = mpjWrapper.builder(mpjWapper,alias, field.get(req));
+                                mpjWapperMap.put(mpjWapper.value(), mpjWrapper);
+                            }
+                        }
+                    }
+                    //判断非使用属性
+                    TableField tableField = field.getAnnotation(TableField.class);
+                    if (Objects.isNull(tableField) || ( !Objects.isNull(tableField) && !tableField.exist()) ) {
+                        continue;
+                    }
+                    Object value = field.get(req);
+                    if(Objects.isNull(value)){
+                        continue;
+                    }
+                    String column = alias+"."+ (Objects.isNull(tableField) ? ObjectUtil.toUnderScoreCase(name) : tableField.value());
+                    if (Objects.equals("java.lang.String",field.getType().getName())
+                            && StringUtils.isNotBlank((String) value)) {
+                        mpjLambdaWrapper = mpjLambdaWrapper.like(column, value);
+                    } else if ( Objects.equals("java.lang.Integer",field.getType() .getName())
+                            || Objects.equals("java.lang.Long",field.getType() .getName())
+                    ) {
+                        mpjLambdaWrapper = mpjLambdaWrapper.eq(column, value);
+                    }
+                }
+                if( mpjWapperMap.size() > 0){
+                    //处理 非空条件
+                    MPJLambdaWrapper<D> finalMpjLambdaWrapper = mpjLambdaWrapper;
+                    mpjWapperMap.forEach((key, value) -> checkMpjWrapper(finalMpjLambdaWrapper, value));
+                }
+            }
+        }catch (Exception ex ){
+            ex.printStackTrace();
+        }
+        return mpjLambdaWrapper;
+    }
+    /**
+     * @param mpjWrapper
+     * @param wrapper
+     */
+    private void checkMpjWrapper(MPJLambdaWrapper<D> mpjWrapper ,MpjWrapper wrapper  ){
+        try {
+            MpjWapper field = wrapper.getField();
+            String alias = wrapper.getAlias();
+            String value =(StringUtils.isNotBlank(alias)?alias+".":"")+ field.value();
+            String data = wrapper.getData();
+            if(StringUtils.isBlank(data) || data.replaceAll(",","").length() == 0){
+                return ;
+            }
+            switch (field.operator()) {
+                case EQ:
+                    Object eqVal = checkColumnType(wrapper, data);
+                    mpjWrapper = mpjWrapper.eq(value, eqVal);
+                    break;
+                case NE:
+                    Object neVal = checkColumnType(wrapper, data);
+                    mpjWrapper = mpjWrapper.ne(value, neVal);
+                    break;
+                case BETWEEN:
+                    String[] split = data.split(",");
+                    if (split.length != 2) {
+                        throw new ServiceException("参数错误");
+                    }
+                    Object val1 = checkColumnType(wrapper, split[0]);
+                    Object val2 = checkColumnType(wrapper, split[1]);
+                    mpjWrapper = mpjWrapper.between(value, val1, val2);
+                    break;
+            }
+        }catch (Exception ex){
+            throw new ServiceException("参数错误");
+        }
+    }
+
+    /**
+     * @param wrapper
+     * @param data
+     * @return
+     */
+    private Object checkColumnType(MpjWrapper wrapper ,Object data){
+        Object obj = data;
+        boolean isSame = data.getClass().getName().contains(wrapper.getField().columnType().name());
+        if(isSame){
+            return obj;
+        }
+        switch (wrapper.getField().columnType()) {
+            case STRING:
+                obj = data.toString();
+                break;
+            case NUMBER:
+                obj = Double.parseDouble(data.toString());
+                break;
+            case DATE:
+                obj = DateUtils.parseDate(data.toString());
+                break;
+            case BOOLEAN:
+                obj = Boolean.parseBoolean(data.toString());
+                break;
+        }
+        return obj;
+    }
+
+}

+ 13 - 0
py-base/src/main/java/com/poyee/common/CanonicalHostNamePropertyDefiner.java

@@ -0,0 +1,13 @@
+package com.poyee.common;
+
+import ch.qos.logback.core.PropertyDefinerBase;
+import com.poyee.util.IpUtils;
+
+public class CanonicalHostNamePropertyDefiner extends PropertyDefinerBase {
+
+    @Override
+    public String getPropertyValue() {
+        return IpUtils.getHostName();
+    }
+
+}

+ 64 - 0
py-base/src/main/java/com/poyee/common/Result.java

@@ -0,0 +1,64 @@
+package com.poyee.common;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * result
+ */
+@Data
+@ToString
+@NoArgsConstructor
+public class Result implements Serializable {
+
+    private Integer code = 0;
+
+    private String msg = "SUCCESS";
+
+    private boolean success = true;
+
+    private Map<String, Object> data = new HashMap<>();
+
+    public static Result ok() {
+        return new Result();
+    }
+
+    public static Result ok(String msg) {
+        Result result = new Result();
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static Result ok(Integer code, String msg) {
+        Result result = new Result();
+        result.setCode(code);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static Result error(Integer code, String msg) {
+        Result result = new Result();
+        result.setCode(code);
+        result.setMsg(msg);
+        result.setSuccess(false);
+        return result;
+    }
+
+    public Result put(String key, Object value) {
+        if (null == this.data) this.data = new HashMap<>();
+        this.data.put(key, value);
+        return this;
+    }
+
+    public Object get(String key) {
+        if (null == this.data) return null;
+        return this.data.get(key);
+    }
+
+
+}

+ 97 - 0
py-base/src/main/java/com/poyee/common/auth/UserRoleUtil.java

@@ -0,0 +1,97 @@
+package com.poyee.common.auth;
+
+import com.alibaba.fastjson.JSONObject;
+import com.poyee.base.dto.UserInfo;
+import com.poyee.common.enums.DataAuth;
+import com.poyee.common.enums.Roles;
+import com.poyee.common.exception.AuthException;
+import com.poyee.util.ServletUtils;
+import com.poyee.util.StringUtils;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @Author: poy
+ * @Description:
+ */
+@Slf4j
+@Data
+public class UserRoleUtil {
+
+    private String whitelistId;
+
+    public static UserRoleUtil builder(String whitelistId){
+        UserRoleUtil userRoleUtil = new UserRoleUtil();
+        userRoleUtil.whitelistId = whitelistId;
+        return userRoleUtil;
+    }
+
+    /**
+     * 权限判断
+     * @param pass_aud
+     * @param methodRoles
+     * @return
+     */
+    public boolean checkRole(String pass_aud, List<Roles> methodRoles){
+        UserInfo userInfo = ServletUtils.getUserInfo();
+        if (null == userInfo || null == userInfo.getId()) {
+            throw new AuthException(405, "请重新登录[405]!");
+        }
+        if(checkUserForWhiteList(userInfo.getId())){
+            return true;
+        }
+        //判断用户是否属于非验证来源
+        if(StringUtils.isNotBlank(pass_aud)){
+            if(Objects.equals(pass_aud, userInfo.getAud())){
+                return true;
+            }
+        }
+        String roleCode = userInfo.getRoleCode();
+        if(StringUtils.isBlank(roleCode) && StringUtils.isBlank(userInfo.getRole())){
+            throw new AuthException(401,"无权操作[401]");
+        }
+        roleCode = StringUtils.isBlank(roleCode) ? userInfo.getRole() : roleCode;
+        //用户角色
+        List<Roles> myRoles = Roles.checkMyRoles(roleCode);
+        //符合角色
+        List<Roles> hasRole = null;
+        if (myRoles != null) {
+            hasRole = methodRoles.stream().filter(myRoles::contains).collect(Collectors.toList());
+        }
+        log.info(" 当前用户存在的角色[{}] ",hasRole);
+        if (hasRole != null && hasRole.contains(Roles.ADMIN)) {
+            userInfo.setDataAuth(DataAuth.ALL);
+            return CollectionUtils.isNotEmpty(hasRole);
+        }
+        if (hasRole != null && hasRole.contains(Roles.SHIPPING)) {
+            //如果有商家权限 则设置 商家id
+            userInfo.setDataAuth(DataAuth.DEPT);
+            userInfo.setMerchant(true);
+            String merchantName = StringUtils.isBlank(userInfo.getMerchantName()) ? userInfo.getDisplayName() : userInfo.getMerchantName();
+            merchantName = StringUtils.isNotBlank(merchantName) ? merchantName : userInfo.getNickname();
+            userInfo.setMerchantName(merchantName);
+        }
+        ServletUtils.getSession().setAttribute("userInfo", JSONObject.toJSONString(userInfo));
+        return CollectionUtils.isNotEmpty(hasRole);
+    }
+
+    /**
+     * 检查用户是否在白名单中
+     * @param id
+     * @return
+     */
+    private boolean checkUserForWhiteList(@NonNull Integer id ){
+        if(StringUtils.isNotBlank(whitelistId)){
+            List<String> whitelistIds = Arrays.asList(whitelistId.split(","));
+            return whitelistIds.contains(id.toString());
+        }
+        return false;
+    }
+}

+ 18 - 0
py-base/src/main/java/com/poyee/common/enums/BusinessStatus.java

@@ -0,0 +1,18 @@
+package com.poyee.common.enums;
+
+/**
+ * 操作状态
+ *
+ * @author zheng
+ */
+public enum BusinessStatus {
+    /**
+     * 成功
+     */
+    SUCCESS,
+
+    /**
+     * 失败
+     */
+    FAIL,
+}

+ 68 - 0
py-base/src/main/java/com/poyee/common/enums/BusinessType.java

@@ -0,0 +1,68 @@
+package com.poyee.common.enums;
+
+/**
+ * 业务操作类型
+ *
+ * @author zheng
+ */
+public enum BusinessType {
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 新增
+     */
+    INSERT,
+
+    /**
+     * 修改
+     */
+    UPDATE,
+
+    /**
+     * 删除
+     */
+    DELETE,
+
+    /**
+     * 授权
+     */
+    GRANT,
+
+    /**
+     * 导出
+     */
+    EXPORT,
+
+    /**
+     * 导入
+     */
+    IMPORT,
+
+    /**
+     * 强退
+     */
+    FORCE,
+
+    /**
+     * 生成代码
+     */
+    GENCODE,
+
+    /**
+     * 清空
+     */
+    CLEAN,
+    /**
+     * 查询
+     */
+    SEARCH,
+    /**
+     * 调用接口
+     */
+    INTERFACE,
+
+
+}

+ 19 - 0
py-base/src/main/java/com/poyee/common/enums/CheckAuthTypeEnums.java

@@ -0,0 +1,19 @@
+package com.poyee.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum CheckAuthTypeEnums {
+
+    //authorization、private(内部访问时使用密钥对验证)
+    AUTHORIZATION("authorization"),
+    PRIVATE("private");
+
+    private String value;
+
+    CheckAuthTypeEnums(String value) {
+        this.value = value;
+    }
+
+
+}

+ 23 - 0
py-base/src/main/java/com/poyee/common/enums/DataAuth.java

@@ -0,0 +1,23 @@
+package com.poyee.common.enums;
+
+/**
+ * 数据权限
+ */
+public enum DataAuth {
+    /**
+     * 所有
+     */
+    ALL,
+
+    /**
+     * 个人
+     */
+    PERSON,
+
+    /**
+     * 部门
+     */
+    DEPT
+
+
+}

+ 12 - 0
py-base/src/main/java/com/poyee/common/enums/DesensitType.java

@@ -0,0 +1,12 @@
+package com.poyee.common.enums;
+
+//脱敏类型
+public enum DesensitType {
+    //默认
+    DEFAULT,
+    //置空
+    NULL,
+    //MD5
+    MD5
+
+}

+ 25 - 0
py-base/src/main/java/com/poyee/common/enums/I18nFormat.java

@@ -0,0 +1,25 @@
+package com.poyee.common.enums;
+
+import lombok.Getter;
+
+/**
+ *
+ */
+@Getter
+public enum I18nFormat {
+
+    INSERT("新增"),
+    UPDATE("编辑"),
+    DELETE("删除"),
+    SEARCH("查询"),
+    EXPORT("导出"),
+    IMPORT("导入")
+    ;
+
+    private  String msg;
+
+    I18nFormat(String msg) {
+        this.msg = msg;
+    }
+
+}

+ 69 - 0
py-base/src/main/java/com/poyee/common/enums/Language.java

@@ -0,0 +1,69 @@
+package com.poyee.common.enums;
+
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 国际化 语言枚举
+ */
+@Getter
+public enum Language {
+    /**
+     * -- en:英语 (English)
+     * -- zh:中文 (Chinese)
+     * -- zh-hk:繁体中文(香港)
+     * -- fr:法语 (French)
+     * -- es:西班牙语 (Spanish)
+     * -- de:德语 (German)
+     * -- ja:日语 (Japanese)
+     * -- ko:韩语 (Korean)
+     * -- ru:俄语 (Russian)
+     * -- pt:葡萄牙语 (Portuguese)
+     * -- it:意大利语 (Italian)
+     * -- ar:阿拉伯语 (Arabic)
+     * -- hi:印地语 (Hindi)
+     * -- nl:荷兰语 (Dutch)
+     */
+    ENGLISH("en", "English", "英语"),
+    CHINESE("zh", "Chinese", "中文"),
+    HONGKONG("zh-hk", "Chinese(HongKong)",  "繁体中文(香港)"),
+    FRENCH("fr", "French", "法语"),
+    SPANISH("es", "Spanish", "西班牙语"),
+    GERMAN("de", "German", "德语"),
+    JAPANESE("ja", "Japanese" , "日语"),
+    KOREAN("ko", "Korean", "韩语"),
+    RUSSIAN("ru", "Russian", "俄语"),
+    PORTUGUESE("pt", "Portuguese", "葡萄牙语"),
+    ITALIAN("it", "Italian", "意大利语"),
+    ARABIC("ar", "Arabic", "阿拉伯语"),
+    HINDI("hi", "Hindi", "印地语"),
+    DUTCH("nl", "Dutch", "荷兰语");
+
+    String code;
+    String name;
+    String zhName;
+
+    Language(String code, String name,String zhName) {
+        this.code = code;
+        this.name = name;
+        this.zhName = zhName;
+    }
+
+
+    public static Language getByCode(String code) {
+       return Language.valueOf(code);
+    }
+
+    public static Map<String,String> getLangEnMap(){
+        //枚举转map<String,String>
+        return Arrays.stream(Language.values()).collect(Collectors.toMap(Language::getCode, Language::getName));
+    }
+
+    public static Map<String,String> getLangZhMap(){
+        //枚举转map<String,String>
+        return Arrays.stream(Language.values()).collect(Collectors.toMap(Language::getCode, Language::getZhName));
+    }
+}

+ 28 - 0
py-base/src/main/java/com/poyee/common/enums/OperatorType.java

@@ -0,0 +1,28 @@
+package com.poyee.common.enums;
+
+/**
+ * 操作人类别
+ *
+ * @author zheng
+ */
+public enum OperatorType {
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 后台用户
+     */
+    MANAGE,
+
+    /**
+     * 手机端用户
+     */
+    MOBILE,
+
+    /**
+     * 第三方用户
+     */
+    THIRD
+}

+ 70 - 0
py-base/src/main/java/com/poyee/common/enums/Roles.java

@@ -0,0 +1,70 @@
+package com.poyee.common.enums;
+
+import com.poyee.util.StringUtils;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 角色
+ */
+@Getter
+public enum Roles {
+    /**
+     * admin
+     */
+    //admin
+    ADMIN( "admin","管理员"),
+    /**
+     * 客服
+     */
+    CUSTOMER("customer","客服"),
+    /**
+     * checklist服务
+     */
+    CHECKLIST_HS("checklist-hs","checkist服务"),
+
+    /**
+     * 商家
+     */
+    SHIPPING("shipping","商家"),
+    /**
+     * 无权限
+     */
+    NOAUTH("noauth", "无权限"),
+    /**
+     * 商家免审核上架拼团list team权限
+     */
+    SHIPPING_REVIEW_TEAM("shipping-review-team","商家免审核上架拼团list-球队"),
+
+    /**
+     * 数据清洗
+     */
+    SYNC_DATA("syncData","数据清洗")
+
+    ;
+
+
+    private final String roleCode;
+    private final String roleName;
+
+    Roles(String roleCode, String roleName) {
+        this.roleCode = roleCode;
+        this.roleName = roleName;
+    }
+
+    public static List<Roles> checkMyRoles(String roleStr){
+        if(StringUtils.isNotBlank(roleStr)){
+            List<String> myRoles = Arrays.asList(roleStr.split(","));
+            List<Roles> enumRoles = Arrays.asList(Roles.values());
+            return enumRoles.stream().filter(roles -> myRoles.contains(roles.getRoleCode().toLowerCase())).collect(Collectors.toList());
+        }else{
+            return null;
+        }
+    }
+
+
+
+}

+ 39 - 0
py-base/src/main/java/com/poyee/common/exception/AuthException.java

@@ -0,0 +1,39 @@
+package com.poyee.common.exception;
+
+import com.poyee.constant.ResultCode;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 权限异常
+ */
+@Slf4j
+public class AuthException extends RuntimeException {
+
+    protected String message;
+    protected int code = ResultCode.UNAUTHORIZED.getCode();
+
+    public AuthException(String message) {
+        this.message = message;
+    }
+
+    public AuthException(int code, String message) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public AuthException(String message, Throwable e) {
+        super(message, e);
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public int getCode() {
+        return this.code;
+    }
+
+
+}

+ 152 - 0
py-base/src/main/java/com/poyee/common/exception/BaseException.java

@@ -0,0 +1,152 @@
+package com.poyee.common.exception;
+
+import com.poyee.common.util.MessageUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 基础异常类,用于处理项目中的自定义异常
+ * 它提供了异常的模块、错误码、错误消息等信息的封装
+ *
+ * @author zheng
+ */
+public class BaseException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块,用于标识异常所属的业务模块
+     */
+    private final String module;
+
+    /**
+     * 错误码,用于标识异常的类型
+     */
+    private final String code;
+
+    /**
+     * 错误码对应的参数,用于格式化错误消息
+     */
+    private final Object[] args;
+
+    /**
+     * 错误消息,当错误码无法解析时使用
+     */
+    private final String defaultMessage;
+
+    /**
+     * 构造函数,用于创建异常对象
+     *
+     * @param module       异常所属的模块
+     * @param code         错误码
+     * @param args         错误码对应的参数
+     * @param defaultMessage 默认错误消息
+     */
+    public BaseException(String module, String code, Object[] args, String defaultMessage) {
+        this.module = module;
+        this.code = code;
+        this.args = args;
+        this.defaultMessage = defaultMessage;
+    }
+
+    /**
+     * 构造函数,用于创建只有错误码和默认错误消息的异常对象
+     *
+     * @param code         错误码
+     * @param defaultMessage 默认错误消息
+     */
+    public BaseException(Integer code, String defaultMessage) {
+        this(null, String.valueOf(code), null, defaultMessage);
+    }
+
+    /**
+     * 构造函数,用于创建带有模块、错误码和参数的异常对象
+     *
+     * @param module       异常所属的模块
+     * @param code         错误码
+     * @param args         错误码对应的参数
+     */
+    public BaseException(String module, String code, Object[] args) {
+        this(module, code, args, null);
+    }
+
+    /**
+     * 构造函数,用于创建只有模块和默认错误消息的异常对象
+     *
+     * @param module       异常所属的模块
+     * @param defaultMessage 默认错误消息
+     */
+    public BaseException(String module, String defaultMessage) {
+        this(module, null, null, defaultMessage);
+    }
+
+    /**
+     * 构造函数,用于创建带有错误码和参数的异常对象
+     *
+     * @param code         错误码
+     * @param args         错误码对应的参数
+     */
+    public BaseException(String code, Object[] args) {
+        this(null, code, args, null);
+    }
+
+    /**
+     * 构造函数,用于创建只有默认错误消息的异常对象
+     *
+     * @param defaultMessage 默认错误消息
+     */
+    public BaseException(String defaultMessage) {
+        this(null, null, null, defaultMessage);
+    }
+
+    /**
+     * 重写getMessage方法,用于获取异常的错误消息
+     *
+     * @return 错误消息
+     */
+    @Override
+    public String getMessage() {
+        String message = null;
+        if (!StringUtils.isEmpty(code)) {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null) {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+    /**
+     * 获取异常所属的模块
+     *
+     * @return 异常所属的模块
+     */
+    public String getModule() {
+        return module;
+    }
+
+    /**
+     * 获取异常的错误码
+     *
+     * @return 错误码
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 获取错误码对应的参数
+     *
+     * @return 错误码对应的参数
+     */
+    public Object[] getArgs() {
+        return args;
+    }
+
+    /**
+     * 获取默认的错误消息
+     *
+     * @return 默认的错误消息
+     */
+    public String getDefaultMessage() {
+        return defaultMessage;
+    }
+}

+ 68 - 0
py-base/src/main/java/com/poyee/common/exception/BusinessException.java

@@ -0,0 +1,68 @@
+package com.poyee.common.exception;
+
+/**
+ * 业务异常
+ * 此类用于表示在业务逻辑处理过程中发生的异常它扩展了RuntimeException,因此是unchecked exception,
+ * 不需要在方法签名中显式声明它可以用于处理各种业务场景下的异常情况,提供更丰富的错误信息
+ *
+ * @author zheng
+ */
+public class BusinessException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    // 业务异常的消息内容
+    protected final String message;
+
+    // 业务异常的错误代码,默认为-1
+    protected Integer code = -1;
+
+    /**
+     * 构造函数,用于创建具有指定错误代码和消息的业务异常
+     *
+     * @param code    错误代码
+     * @param message 异常消息
+     */
+    public BusinessException(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    /**
+     * 构造函数,用于创建具有指定消息的业务异常错误代码使用默认值
+     *
+     * @param message 异常消息
+     */
+    public BusinessException(String message) {
+        this.message = message;
+    }
+
+    /**
+     * 构造函数,用于创建具有指定消息和原因的业务异常错误代码使用默认值
+     *
+     * @param message 异常消息
+     * @param e       异常原因
+     */
+    public BusinessException(String message, Throwable e) {
+        super(message, e);
+        this.message = message;
+    }
+
+    /**
+     * 重写getMessage方法,返回业务异常的消息内容
+     *
+     * @return 异常消息
+     */
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * 获取业务异常的错误代码
+     *
+     * @return 错误代码
+     */
+    public Integer getCode() {
+        return code;
+    }
+}

+ 64 - 0
py-base/src/main/java/com/poyee/common/exception/DataException.java

@@ -0,0 +1,64 @@
+package com.poyee.common.exception;
+
+/**
+ * 数据异常
+ */
+import lombok.Getter;
+
+public class DataException extends RuntimeException{
+    private static final long serialVersionUID = 1L;
+
+    // 错误消息
+    protected final String message;
+    /**
+     * -- GETTER --
+     *  获取错误代码
+     *
+     * @return 错误代码
+     */
+    // 错误代码,默认为0
+    @Getter
+    protected int code = 0;
+
+    /**
+     * 构造一个仅包含错误消息
+     *
+     * @param message 错误消息
+     */
+    public DataException(String message) {
+        this.message = message;
+    }
+
+    /**
+     * 构造一个包含错误代码和错误消息
+     *
+     * @param code    错误代码
+     * @param message 错误消息
+     */
+    public DataException(int code, String message) {
+        this.message = message;
+        this.code = code;
+    }
+
+    /**
+     * 构造一个包含错误消息和原因
+     *
+     * @param message 错误消息
+     * @param e       异常原因
+     */
+    public DataException(String message, Throwable e) {
+        super(message, e);
+        this.message = message;
+    }
+
+    /**
+     * 获取错误消息
+     *
+     * @return 错误消息
+     */
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+}

+ 146 - 0
py-base/src/main/java/com/poyee/common/exception/GlobalExceptionHandler.java

@@ -0,0 +1,146 @@
+package com.poyee.common.exception;
+
+import com.poyee.base.dto.Result;
+import com.poyee.i18n.I18nUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.BindException;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static com.poyee.i18n.I18nMessageEnums.*;
+
+/**
+ *
+ */
+@RestControllerAdvice
+@Configuration
+public class GlobalExceptionHandler extends RuntimeException {
+    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+    /**
+     * 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
+     */
+    /*@ExceptionHandler(AuthorizationException.class)
+    public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e)
+    {
+        log.error(e.getMessage(), e);
+        if (ServletUtils.isAjaxRequest(request))
+        {
+            return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
+        }
+        else
+        {
+            ModelAndView modelAndView = new ModelAndView();
+            modelAndView.setViewName("error/unauth");
+            return modelAndView;
+        }
+    }*/
+
+    /**
+     * 请求方式不支持
+     */
+    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
+    public Result handleException(HttpRequestMethodNotSupportedException e) {
+        log.error("运行异常:{}", e);
+        return Result.error(I18nUtils.get(REQUEST_METHOD_NOT_SUPPORTED,e.getMethod()));
+    }
+
+    /**
+     * 拦截未知的运行时异常
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public Object notFount(RuntimeException e) {
+        log.error("运行时异常:{}", e);
+        if (e.getCause() instanceof SQLException) {
+            return Result.error( I18nUtils.get(SQL_RUNTIME_ERROR));
+        }
+        return Result.error(I18nUtils.get(SYSTEM_ERROR));
+    }
+
+    @ExceptionHandler(ServiceException.class)
+    public Result serviceErr(ServiceException e) {
+        log.error("运行时异常:{}", e);
+        if (e.getCode() == 0) {
+            e.code = -1;
+        }
+        return Result.error(e.getCode(), e.getMessage());
+    }
+
+    /**
+     * 系统异常
+     */
+    @ExceptionHandler(Exception.class)
+    public Result handleException(Exception e) {
+        log.error("运行异常:{}", e);
+        return Result.error(I18nUtils.get(SERVER_ERROR));
+    }
+
+    @ExceptionHandler(DataException.class)
+    public Result dataException(DataException e) {
+        log.error("运行时异常:{}", e);
+        if (e.getCode() == 0) {
+            e.code = -1;
+        }
+        return Result.error(e.getCode(), e.getMessage());
+    }
+    @ExceptionHandler(AuthException.class)
+    public Result authException(AuthException e) {
+        log.error("运行时异常:{}", e);
+        if (e.getCode() == 0) {
+            e.code = -1;
+        }
+        return Result.error(e.getCode(), e.getMessage());
+    }
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(BaseException.class)
+    public Object baseException(BaseException e) {
+        log.error(e.getMessage(), e);
+        String errMsg = e.getMessage();
+        return Result.error(errMsg);
+    }
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(BusinessException.class)
+    public Object businessException(BusinessException e) {
+        log.error(e.getMessage(), e);
+        String errMsg = e.getMessage();
+        Integer code = e.getCode();
+        return Result.error(code, errMsg);
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(BindException.class)
+    public Result validatedBindException(BindException e) {
+        log.error(e.getMessage(), e);
+        String message = e.getAllErrors().get(0).getDefaultMessage();
+        return Result.error(message);
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        StringBuilder msg = new StringBuilder();
+        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
+        for (ObjectError allError : allErrors) {
+            msg.append(allError.getDefaultMessage()).append(",");
+        }
+        if (null != msg && msg.length() > 1) {
+            return Result.error(msg.substring(0, msg.length() - 1));
+        }
+        return Result.error(msg.toString());
+    }
+}

+ 40 - 0
py-base/src/main/java/com/poyee/common/exception/ServiceException.java

@@ -0,0 +1,40 @@
+package com.poyee.common.exception;
+
+import com.poyee.constant.ResultCode;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 业务异常
+ *
+ * @author lsz
+ */
+@Slf4j
+public class ServiceException extends RuntimeException {
+
+    protected String message;
+    protected int code = ResultCode.FAIL.getCode();
+
+    public ServiceException(String message) {
+        this.message = message;
+    }
+
+    public ServiceException(int code, String message) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public ServiceException(String message, Throwable e) {
+        super(message, e);
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public int getCode() {
+        return this.code;
+    }
+
+}

+ 66 - 0
py-base/src/main/java/com/poyee/common/knife4j/Knife4jConfig.java

@@ -0,0 +1,66 @@
+package com.poyee.common.knife4j;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.ApiKey;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+@Configuration
+@EnableSwagger2WebMvc
+public class Knife4jConfig {
+
+    private static final String API_BASE_PACKAGE = "com.poyee";
+    private static final String API_TITLE = "poyee-dashboard";
+    private static final String API_DESCRIPTION = "poyee-dashboard";
+    private static final String API_CONTACT_NAME = "poyee";
+    private static final String API_VERSION = "1.0.0";
+    private static final String API_TERMS_OF_SERVICE = "";
+    private static final String API_LICENSE = "Apache 2.0";
+    private static final String API_LICENSE_URL = "";
+    /**
+     * 请求头token键
+     */
+    private final static String ACCESS_TOKEN = "Authorization";
+    public final static String Accept_Language = "Accept-Language";
+
+    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
+                                                      .select()
+                                                      .apis(RequestHandlerSelectors.basePackage(API_BASE_PACKAGE))
+                                                      .paths(PathSelectors.any())
+                                                      .build()
+                                                      .securitySchemes(this.securitySchemes());
+    }
+
+    private ApiInfo apiInfo() {
+        return new ApiInfoBuilder().title(API_TITLE)
+                                   .description(API_DESCRIPTION)
+                                   .contact(new Contact(API_CONTACT_NAME, API_TERMS_OF_SERVICE, API_LICENSE_URL))
+                                   .version(API_VERSION)
+                                   .build();
+    }
+
+    private List<ApiKey> securitySchemes() {
+        List<ApiKey> list = new ArrayList<>();
+        list.add(new ApiKey(ACCESS_TOKEN, ACCESS_TOKEN, "header"));
+        list.add(new ApiKey(Accept_Language, Accept_Language, "header"));
+        return list;
+    }
+
+
+
+}

+ 29 - 0
py-base/src/main/java/com/poyee/common/swagger/ArrayRefProperty.java

@@ -0,0 +1,29 @@
+package com.poyee.common.swagger;/*
+package com.poyee.common.swagger;
+
+
+import io.swagger.models.properties.ArrayProperty;
+import io.swagger.models.properties.RefProperty;
+import io.swagger.models.refs.GenericRef;
+import io.swagger.models.refs.RefType;
+*/
+/**
+ * 同时拥有ArrayProperty和RefProperty的特性
+ * @author ChenZhiPing 2018年10月30日 下午6:08:57
+ *//*
+
+public class ArrayRefProperty extends ArrayProperty {
+    private GenericRef genericRef;
+    public String get$ref() {
+        return genericRef.getRef();
+    }
+    public void set$ref(String ref) {
+        this.genericRef = new GenericRef(RefType.DEFINITION, ref);
+// $ref
+        RefProperty items = new RefProperty();
+        items.setType(ref);
+        items.set$ref(ref);
+        this.items(items);
+    }
+}
+*/

+ 335 - 0
py-base/src/main/java/com/poyee/common/swagger/SwaggerConfig.java

@@ -0,0 +1,335 @@
+package com.poyee.common.swagger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.poyee.config.Global;
+import com.poyee.util.SpringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import springfox.documentation.RequestHandler;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+import java.util.*;
+
+import static com.poyee.common.knife4j.Knife4jConfig.Accept_Language;
+
+/**
+ * Swagger2的接口配置
+ */
+
+
+@Slf4j
+@Configuration
+@EnableSwagger2WebMvc
+public class SwaggerConfig {
+    /**
+     * 指定要扫描的路径
+     */
+    private final static String[] BASE_PACKAGES = new String[]{"com.poyee"};
+    /**
+     * 支持协议
+     */
+    private final static String[] protocols = new String[]{"http,https"};
+    /**
+     * 请求头token键
+     */
+    private final static String ACCESS_TOKEN = "Authorization";
+    /**
+     * 是否开启swagger
+     */
+    @Value("${swagger.enabled:false}")
+    private boolean enabled;
+
+    /*
+     *
+     * 重写basePackage方法,使能够实现自定义多包扫描 * * @param basePackages ${@link String[]} * @return Predicate<RequestHandler> ${@link Predicate < RequestHandler >} * @author zxiaozhou * @date 2020-07-06 18:01
+     */
+    public static Predicate<RequestHandler> basePackage(final String[] basePackages) {
+        return input -> declaringClass(input).transform(handlerPackage(basePackages))
+                                             .or(true);
+    }
+
+    private static Function<Class<?>, Boolean> handlerPackage(final String[] basePackages) {
+        return input -> {
+            for (String basePackage : basePackages) {
+                boolean isMatch = input.getPackage()
+                                       .getName()
+                                       .startsWith(basePackage);
+                if (isMatch) {
+                    return true;
+                }
+            }
+            return false;
+        };
+    }
+
+    private static com.google.common.base.Optional<? extends Class<?>> declaringClass(RequestHandler input) {
+        return com.google.common.base.Optional.fromNullable(input.declaringClass());
+    }
+
+    /**
+     * 匹配swagger版本 * *
+     *
+     * @param input   ${@link String} 注解解析 *
+     * @param version ${@link String} 版本信息 *
+     * @return String[] ${@link String[]} *
+     * @author zxiaozhou *
+     * @date 2020-07-06 18:49
+     */
+    protected static boolean matchVersion(RequestHandler input, String version) {
+        if (StringUtils.isBlank(version) && !isControllerHidden(input)) {
+            return true;
+        }
+        String[] versions = getVersions(input);
+        for (String v : versions) {
+            if (version.equalsIgnoreCase(v)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取版本信息 * *
+     *
+     * @param input ${@link RequestHandler} *
+     * @return String[] ${@link String[]} 获取的接口信息 *
+     * @author zxiaozhou * @date 2020-07-06 18:11
+     */
+
+    protected static String[] getVersions(RequestHandler input) {
+        if (Objects.isNull(input) || isControllerHidden(input)) {
+            return new String[]{};
+        }
+        Optional<ApiOperation> methodAnnotation = input.findAnnotation(ApiOperation.class);
+        if (methodAnnotation.isPresent()) {
+            ApiOperation apiOperation = methodAnnotation.get();
+            return getVersions(apiOperation.value());
+        }
+        return new String[]{};
+    }
+
+    /**
+     * 解析版本信息 * * @param version ${@link String} 匹配版本 * @return String[] ${@link String[]} 匹配后的接口 * @author zxiaozhou * @date 2020-07-06 18:02
+     */
+    private static String[] getVersions(String... version) {
+        String reg = "(.)*(@[\\((].*[\\))])(.)*";
+       /* if (version.matches(reg)) {
+            version = version.replaceAll(reg, "$2");
+            reg = "(@[\\((])(.*)([\\))])";
+            version = version.replaceAll(reg, "$2");
+            return version.split("[,,]");
+        }*/
+        return new String[]{};
+    }
+
+    /**
+     * 解析版本信息 * * @param version ${@link String} 匹配版本 * @return String[] ${@link String[]} 匹配后的接口 * @author zxiaozhou * @date 2020-07-06 18:02
+     */
+    private static String[] getVersions(String version) {
+        String reg = "(.)*(@[\\((].*[\\))])(.)*";
+        if (version.matches(reg)) {
+            version = version.replaceAll(reg, "$2");
+            reg = "(@[\\((])(.*)([\\))])";
+            version = version.replaceAll(reg, "$2");
+            return version.split("[,,]");
+        }
+        return new String[]{};
+    }
+
+    /**
+     * 查询controller是否隐藏 * * @param input ${@link RequestHandler} * @author zxiaozhou * @date 2020-07-20 12:48
+     */
+
+    protected static boolean isControllerHidden(RequestHandler input) {
+        Optional<Api> controllerAnnotation = input.findControllerAnnotation(Api.class);
+        if (controllerAnnotation.isPresent()) {
+            Api api = controllerAnnotation.get();
+            return api.hidden();
+        }
+        return false;
+    }
+
+    /*
+     *
+     * api标题信息 * * @param version ${@link String} 版本 * @return ApiInfo ${@link ApiInfo} api标题信息 * @author zxiaozhou * @date 2020-07-08 18:45
+     */
+    protected static ApiInfo apiInfo(String version) {
+        return new ApiInfoBuilder().title("Swagger API配置")
+                                   .description("接口详细说明")
+                                   .contact(new Contact("poyee", "", null))
+                                   .version(version)
+                                   .build();
+    }
+
+    /**
+     * 创建API
+     */
+//    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                .apis(RequestHandlerSelectors.basePackage("com.poyee"))
+                // 扫描所有
+                .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+                ;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo() {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:拆卡报告合集_接口文档")
+                // 描述
+                .description("描述:接口详细说明")
+                // 作者信息
+                .contact(new Contact(Global.getName(), null, null))
+                // 版本
+                .version("版本号:" + Global.getVersion())
+                .build();
+    }
+
+    /**
+     * 所有api分组信息 * * @return Docket ${@link Docket} * @author zxiaozhou * @date 2020-07-06 18:52
+     */
+
+    @Bean
+    public Docket allGroup() {
+//        Set<String> setProtocols = new HashSet<>(Arrays.asList(protocols));
+        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo("1.0"))
+                                                      .groupName("1.0")
+                                                      .select()
+                                                      // 扫描所有有注解的api,用这种方式更灵活
+                                                      .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+//                .apis(basePackage(BASE_PACKAGES))
+                                                      // 扫描所有
+                                                      .apis(RequestHandlerSelectors.any())
+                                                      .apis(input -> matchVersion(input, "1.0"))
+                                                      .paths(PathSelectors.any())
+                                                      .build()
+//                .protocols(setProtocols)
+                                                      .securitySchemes(this.securitySchemes())
+                                                      .securityContexts(this.securityContexts());
+    }
+    /*
+     *
+     * 其他api分组信息 * * @author zxiaozhou * @date 2020-07-06 18:52
+     */
+    @Bean
+    public void otherGroups() {
+        Map<String, String> versions = new HashMap<>(4);
+        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap =
+                SpringUtils.getBean(RequestMappingHandlerMapping.class)
+                           .getHandlerMethods();
+        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
+            HandlerMethod handlerMethod = infoEntry.getValue();
+            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
+            if (Objects.nonNull(apiOperation)) {
+                String[] tags = apiOperation.tags();
+                if (StringUtils.isNotBlank(apiOperation.value())) {
+                    String[] versionInfos = getVersions(tags);
+                    if (ArrayUtils.getLength(versionInfos) > 0) {
+                        for (String versionInfo : versionInfos) {
+                            versions.put(versionInfo.replace(".", ""), versionInfo);
+                        }
+                    }
+                }
+                //
+                String value = apiOperation.value();
+                if (StringUtils.isNotBlank(apiOperation.value())) {
+                    String[] versionInfos = getVersions(value);
+                    if (ArrayUtils.getLength(versionInfos) > 0) {
+                        for (String versionInfo : versionInfos) {
+                            versions.put(versionInfo.replace(".", ""), versionInfo);
+                        }
+                    }
+                }
+            }
+        }
+        DefaultListableBeanFactory defaultListableBeanFactory =
+                (DefaultListableBeanFactory) SpringUtils.getAutowireCapableBeanFactory();
+        for (Map.Entry<String, String> version : versions.entrySet()) {
+            Docket docket = createDocket(version.getValue());
+            if (null != docket) {
+                defaultListableBeanFactory.registerSingleton(version.getKey(), docket);
+            }
+        }
+    }
+
+    private List<ApiKey> securitySchemes() {
+        List<ApiKey> list = new ArrayList<>();
+        list.add(new ApiKey(ACCESS_TOKEN, ACCESS_TOKEN, "header"));
+        list.add(new ApiKey(Accept_Language, Accept_Language, "header"));
+        return list;
+    }
+
+    private List<SecurityContext> securityContexts() {
+        List<SecurityContext> list = new ArrayList<>();
+        list.add(SecurityContext.builder()
+                                .securityReferences(this.defaultAuth())
+                                .forPaths(PathSelectors.regex("^(?!auth).*$"))
+                                .build());
+        return list;
+    }
+
+    private List<SecurityReference> defaultAuth() {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> list = new ArrayList<>();
+        list.add(new SecurityReference(ACCESS_TOKEN, authorizationScopes));
+        list.add(new SecurityReference(Accept_Language, authorizationScopes));
+        return list;
+    }
+
+    /**
+     * 构建版本信息 * * @param version ${@link String} 需要构建的版本 * @return Docket ${@link Docket} 版本Docket信息 * @author zxiaozhou * @date 2020-07-08 18:47
+     */
+
+    protected Docket createDocket(String version) {
+        if ("1.0".equals(version)) {
+            return null;
+        }
+        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo(version))
+                                                      .groupName(version)
+                                                      .select()
+                                                      .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+//                .apis(basePackage(BASE_PACKAGES))
+                                                      .apis(input -> matchVersion(input, version))
+                                                      .paths(PathSelectors.any())
+                                                      .build()
+                                                      .securitySchemes(this.securitySchemes())
+                                                      .securityContexts(this.securityContexts())
+                ;
+    }
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor