1
0
Gaokun Wang 1 долоо хоног өмнө
commit
9bef9ef6c7
100 өөрчлөгдсөн 5520 нэмэгдсэн , 0 устгасан
  1. 70 0
      .gitignore
  2. 21 0
      LICENSE
  3. 31 0
      README.md
  4. 43 0
      config/dev/datasource-mysql.yml
  5. 8 0
      config/dev/logging.yml
  6. 39 0
      config/dev/mybatis-flex.yml
  7. 23 0
      config/dev/sa-token.yml
  8. 18 0
      config/dev/security.yml
  9. 43 0
      config/local/datasource-mysql.yml
  10. 8 0
      config/local/logging.yml
  11. 39 0
      config/local/mybatis-flex.yml
  12. 23 0
      config/local/sa-token.yml
  13. 19 0
      config/local/security.yml
  14. 43 0
      config/prod/datasource-mysql.yml
  15. 8 0
      config/prod/logging.yml
  16. 39 0
      config/prod/mybatis-flex.yml
  17. 23 0
      config/prod/sa-token.yml
  18. 18 0
      config/prod/security.yml
  19. 224 0
      eco-bom/pom.xml
  20. 81 0
      eco-common/com-core/pom.xml
  21. 23 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/api/IConfig.java
  22. 72 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/api/IDict.java
  23. 23 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/config/ApplicationConfig.java
  24. 58 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/config/AsyncConfig.java
  25. 37 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/constant/ConfigConstants.java
  26. 68 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/constant/Constants.java
  27. 46 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/DeviceType.java
  28. 46 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/MenuTypeEnum.java
  29. 28 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/UserStatus.java
  30. 45 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/UserType.java
  31. 88 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/BusinessException.java
  32. 32 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/ErrorCode.java
  33. 65 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/ServerException.java
  34. 16 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/enums/BusinessErrorCode.java
  35. 41 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/enums/GlobalErrorCode.java
  36. 39 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/factory/YmlPropertySourceFactory.java
  37. 162 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/CommonResult.java
  38. 143 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/LoginUserStorage.java
  39. 68 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/OrderDomain.java
  40. 65 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/PageResult.java
  41. 16 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/dto/RoleDTO.java
  42. 16 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/dto/UserOnlineDTO.java
  43. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ArrayUtils.java
  44. 35 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/BeanUtils.java
  45. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/BooleanUtils.java
  46. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/CollUtils.java
  47. 33 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ConfigUtils.java
  48. 32 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/DateUtils.java
  49. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ExceptionUtils.java
  50. 56 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/FileDownloadUtils.java
  51. 148 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/FileLocalUtils.java
  52. 60 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/FileUtils.java
  53. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/IdUtils.java
  54. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ImgUtils.java
  55. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/IoUtils.java
  56. 50 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/IpUtils.java
  57. 170 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/JsonUtils.java
  58. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/MapUtils.java
  59. 40 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/MapstructUtils.java
  60. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/NetUtils.java
  61. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/NumberUtils.java
  62. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ObjUtils.java
  63. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/OshiUtils.java
  64. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/RandomUtils.java
  65. 56 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ReflectUtils.java
  66. 87 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/Seqs.java
  67. 41 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ServletUtils.java
  68. 42 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/SpringUtils.java
  69. 50 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/SqlUtils.java
  70. 22 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/StrUtils.java
  71. 268 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/StreamUtils.java
  72. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/SystemUtils.java
  73. 83 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ThreadUtils.java
  74. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/TreeUtils.java
  75. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/URLUtils.java
  76. 18 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/UserAgentUtils.java
  77. 37 0
      eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ValidatorUtils.java
  78. 2 0
      eco-common/com-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  79. 46 0
      eco-common/com-excel/pom.xml
  80. 29 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/annotation/CellMerge.java
  81. 42 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/annotation/ExcelDictFormat.java
  82. 39 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/annotation/ExcelEnumFormat.java
  83. 56 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/convert/ExcelBigNumberConvert.java
  84. 77 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/convert/ExcelDictConvert.java
  85. 18 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/convert/ExcelEnumConvert.java
  86. 119 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/CellMergeStrategy.java
  87. 119 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/DefaultExcelListener.java
  88. 71 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/DefaultExcelResult.java
  89. 154 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/DropDownOptions.java
  90. 380 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/ExcelDownHandler.java
  91. 19 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/ExcelListener.java
  92. 32 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/ExcelResult.java
  93. 82 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/strategy/DefaultCellStyleStrategy.java
  94. 87 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/strategy/DefaultColumnWidthStyleStrategy.java
  95. 268 0
      eco-common/com-excel/src/main/java/org/eco/vip/common/excel/utils/ExcelUtils.java
  96. 23 0
      eco-common/com-log/pom.xml
  97. 29 0
      eco-common/com-log/src/main/java/org/eco/vip/common/log/annotation/Log.java
  98. 108 0
      eco-common/com-log/src/main/java/org/eco/vip/common/log/aspect/LogAspect.java
  99. 55 0
      eco-common/com-log/src/main/java/org/eco/vip/common/log/enums/BusinessTypeEnum.java
  100. 31 0
      eco-common/com-log/src/main/java/org/eco/vip/common/log/enums/ExeStatusEnum.java

+ 70 - 0
.gitignore

@@ -0,0 +1,70 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+*.txt
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
+
+.flattened-pom.xml
+
+DS_Store
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+yarn.lock

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 GaoKunW
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# eco-boot
+
+#### 介绍
+eco-boot 全新Java21 SpringBoot中后台
+
+#### 软件架构
+软件架构说明
+
+
+#### 安装教程
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### 参与贡献
+
+1.  Fork 本仓库
+2.  新建 Feat_xxx 分支
+3.  提交代码
+4.  新建 Pull Request
+
+
+#### 特技
+
+1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
+2.  Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
+3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
+4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
+5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
+6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 43 - 0
config/dev/datasource-mysql.yml

@@ -0,0 +1,43 @@
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+mybatis-flex:
+  # sql审计
+  audit_enable: false
+  # sql打印
+  sql_print: true
+  # 数据源
+  datasource:
+    # 数据源1
+    ds1:
+      type: ${spring.datasource.type}
+      # MySql
+      driver-class-name: com.mysql.cj.jdbc.Driver
+      url: jdbc:mysql://localhost:3306/eco-boot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+      username: root
+      password: root123
+      #DM8数据库
+      #      driver-class-name: dm.jdbc.driver.DmDriver
+      #      url: jdbc:dm://127.0.0.1:5236?schema=eco-boot&useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+      #      username: SYSDBA
+      #      password: SYSdba123
+      # 最大连接池数量
+      maximum-pool-size: 20
+      # 最小空闲线程数量
+      minimum-idle: 10
+      # 配置获取连接等待超时的时间
+      connectionTimeout: 60000
+      # 校验超时时间
+      validationTimeout: 5000
+      # 空闲连接存活最大时间,默认10分钟
+      idleTimeout: 600000
+      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+      maxLifetime: 1800000
+      # 多久检查一次连接的活性
+      keepaliveTime: 30000
+    # 数据源2
+#    ds2:
+#      url: jdbc:mysql://127.0.0.1:3306/eco1
+#      username: root
+#      password: root123

+ 8 - 0
config/dev/logging.yml

@@ -0,0 +1,8 @@
+--- # 日志配置
+logging:
+  level:
+    org.springframework: warn
+    com.zaxxer.hikari.pool.HikariPool: ERROR
+    com.zaxxer.hikari.HikariDataSource: ERROR
+    org.mybatis.spring.mapper: error
+    org.springframework.context.support.PostProcessorRegistrationDelegate: error

+ 39 - 0
config/dev/mybatis-flex.yml

@@ -0,0 +1,39 @@
+# MyBatisFlex公共配置
+mybatis-flex:
+  # 搜索指定包别名
+  type-aliases-package: org.eco.vip.**.domain.**
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级:com.**.**.mapper
+  mapper-package: org.eco.vip.**.mapper
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    map_underscore_to_camel_case: true
+    # MyBatis 自动映射策略
+    auto_mapping_behavior: FULL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    auto_mapping_unknown_column_behavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    #log_impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    cacheEnabled: true
+  global-config:
+    # 逻辑删除存在0
+    normal-value-of-logic-delete: '0'
+    # 逻辑已删除1
+    deleted-value-of-logic-delete: '1'
+    # 默认的逻辑删除字段
+    logic-delete-column: del_flag
+    # 默认的多租户字段
+    tenant-column: tenant_id
+    # 默认的乐观锁字段
+    version-column: version
+    # 是否控制台打印 MyBatis-Flex 的 LOGO 及版本号
+    print-banner: false
+    # 全局的 ID 生成策略配置:雪花算法:snowFlakeId、独创的 FlexID 算法:flexId、UUIDKeyGenerator:uuid
+    key-config:
+      key-type: Generator
+      value: flexId

+ 23 - 0
config/dev/sa-token.yml

@@ -0,0 +1,23 @@
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # token固定超时 设为七天 (必定过期) 单位: 秒
+  timeout: 604800
+  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
+  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
+  active-timeout: 86400
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: false
+  # 是否打印log
+  is-print: false
+  # jwt秘钥
+  jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+  # 同一账号最大登录数量,-1代表不限
+  max-login-count: -1

+ 18 - 0
config/dev/security.yml

@@ -0,0 +1,18 @@
+# security配置
+security:
+  # 排除路径
+  excludes:
+    # 静态资源
+    - /*.html
+    - /**/*.html
+    - /**/*.css
+    - /**/*.js
+    - /profile/**
+    # 公共路径
+    - /favicon.ico
+    - /error
+    # actuator 监控配置
+    - /actuator
+    - /actuator/**
+    # 其它链接
+    - /auth/login

+ 43 - 0
config/local/datasource-mysql.yml

@@ -0,0 +1,43 @@
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+mybatis-flex:
+  # sql审计
+  audit_enable: false
+  # sql打印
+  sql_print: true
+  # 数据源
+  datasource:
+    # 数据源1
+    ds1:
+      type: ${spring.datasource.type}
+      # MySql
+      driver-class-name: com.mysql.cj.jdbc.Driver
+      url: jdbc:mysql://localhost:3306/sat?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+      username: root
+      password: root123
+      #DM8数据库
+      #      driver-class-name: dm.jdbc.driver.DmDriver
+      #      url: jdbc:dm://127.0.0.1:5236?schema=eco-boot&useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+      #      username: SYSDBA
+      #      password: SYSdba123
+      # 最大连接池数量
+      maximum-pool-size: 20
+      # 最小空闲线程数量
+      minimum-idle: 10
+      # 配置获取连接等待超时的时间
+      connectionTimeout: 60000
+      # 校验超时时间
+      validationTimeout: 5000
+      # 空闲连接存活最大时间,默认10分钟
+      idleTimeout: 600000
+      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+      maxLifetime: 1800000
+      # 多久检查一次连接的活性
+      keepaliveTime: 30000
+    # 数据源2
+#    ds2:
+#      url: jdbc:mysql://127.0.0.1:3306/eco1
+#      username: root
+#      password: root123

+ 8 - 0
config/local/logging.yml

@@ -0,0 +1,8 @@
+--- # 日志配置
+logging:
+  level:
+    org.springframework: warn
+    com.zaxxer.hikari.pool.HikariPool: ERROR
+    com.zaxxer.hikari.HikariDataSource: ERROR
+    org.mybatis.spring.mapper: error
+    org.springframework.context.support.PostProcessorRegistrationDelegate: error

+ 39 - 0
config/local/mybatis-flex.yml

@@ -0,0 +1,39 @@
+# MyBatisFlex公共配置
+mybatis-flex:
+  # 搜索指定包别名
+  type-aliases-package: org.eco.vip.**.domain.**
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级:com.**.**.mapper
+  mapper-package: org.eco.vip.**.mapper
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    map_underscore_to_camel_case: true
+    # MyBatis 自动映射策略
+    auto_mapping_behavior: FULL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    auto_mapping_unknown_column_behavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    #log_impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    cacheEnabled: true
+  global-config:
+    # 逻辑删除存在0
+    normal-value-of-logic-delete: '0'
+    # 逻辑已删除1
+    deleted-value-of-logic-delete: '1'
+    # 默认的逻辑删除字段
+    logic-delete-column: del_flag
+    # 默认的多租户字段
+    tenant-column: tenant_id
+    # 默认的乐观锁字段
+    version-column: version
+    # 是否控制台打印 MyBatis-Flex 的 LOGO 及版本号
+    print-banner: false
+    # 全局的 ID 生成策略配置:雪花算法:snowFlakeId、独创的 FlexID 算法:flexId、UUIDKeyGenerator:uuid
+    key-config:
+      key-type: Generator
+      value: flexId

+ 23 - 0
config/local/sa-token.yml

@@ -0,0 +1,23 @@
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # token固定超时 设为七天 (必定过期) 单位: 秒
+  timeout: 604800
+  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
+  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
+  active-timeout: 86400
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: false
+  # 是否打印log
+  is-print: false
+  # jwt秘钥
+  jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+  # 同一账号最大登录数量,-1代表不限
+  max-login-count: -1

+ 19 - 0
config/local/security.yml

@@ -0,0 +1,19 @@
+# security配置
+security:
+  # 排除路径
+  excludes:
+    # 静态资源
+    - /*.html
+    - /**/*.html
+    - /**/*.css
+    - /**/*.js
+    # 公共路径
+    - /favicon.ico
+    - /error
+    # actuator 监控配置
+    - /actuator
+    - /actuator/**
+    # 其它链接
+    - /auth/login
+    - /system/files/download/*
+    - /system/monitor/server

+ 43 - 0
config/prod/datasource-mysql.yml

@@ -0,0 +1,43 @@
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+mybatis-flex:
+  # sql审计
+  audit_enable: false
+  # sql打印
+  sql_print: true
+  # 数据源
+  datasource:
+    # 数据源1
+    ds1:
+      type: ${spring.datasource.type}
+      # MySql
+      driver-class-name: com.mysql.cj.jdbc.Driver
+      url: jdbc:mysql://localhost:3306/eco-boot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+      username: root
+      password: root123
+      #DM8数据库
+      #      driver-class-name: dm.jdbc.driver.DmDriver
+      #      url: jdbc:dm://127.0.0.1:5236?schema=eco-boot&useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+      #      username: SYSDBA
+      #      password: SYSdba123
+      # 最大连接池数量
+      maximum-pool-size: 20
+      # 最小空闲线程数量
+      minimum-idle: 10
+      # 配置获取连接等待超时的时间
+      connectionTimeout: 60000
+      # 校验超时时间
+      validationTimeout: 5000
+      # 空闲连接存活最大时间,默认10分钟
+      idleTimeout: 600000
+      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+      maxLifetime: 1800000
+      # 多久检查一次连接的活性
+      keepaliveTime: 30000
+    # 数据源2
+#    ds2:
+#      url: jdbc:mysql://127.0.0.1:3306/eco1
+#      username: root
+#      password: root123

+ 8 - 0
config/prod/logging.yml

@@ -0,0 +1,8 @@
+--- # 日志配置
+logging:
+  level:
+    org.springframework: warn
+    com.zaxxer.hikari.pool.HikariPool: ERROR
+    com.zaxxer.hikari.HikariDataSource: ERROR
+    org.mybatis.spring.mapper: error
+    org.springframework.context.support.PostProcessorRegistrationDelegate: error

+ 39 - 0
config/prod/mybatis-flex.yml

@@ -0,0 +1,39 @@
+# MyBatisFlex公共配置
+mybatis-flex:
+  # 搜索指定包别名
+  type-aliases-package: org.eco.vip.**.domain.**
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级:com.**.**.mapper
+  mapper-package: org.eco.vip.**.mapper
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    map_underscore_to_camel_case: true
+    # MyBatis 自动映射策略
+    auto_mapping_behavior: FULL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    auto_mapping_unknown_column_behavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    #log_impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    cacheEnabled: true
+  global-config:
+    # 逻辑删除存在0
+    normal-value-of-logic-delete: '0'
+    # 逻辑已删除1
+    deleted-value-of-logic-delete: '1'
+    # 默认的逻辑删除字段
+    logic-delete-column: del_flag
+    # 默认的多租户字段
+    tenant-column: tenant_id
+    # 默认的乐观锁字段
+    version-column: version
+    # 是否控制台打印 MyBatis-Flex 的 LOGO 及版本号
+    print-banner: false
+    # 全局的 ID 生成策略配置:雪花算法:snowFlakeId、独创的 FlexID 算法:flexId、UUIDKeyGenerator:uuid
+    key-config:
+      key-type: Generator
+      value: flexId

+ 23 - 0
config/prod/sa-token.yml

@@ -0,0 +1,23 @@
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # token固定超时 设为七天 (必定过期) 单位: 秒
+  timeout: 604800
+  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
+  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
+  active-timeout: 86400
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: false
+  # 是否打印log
+  is-print: false
+  # jwt秘钥
+  jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+  # 同一账号最大登录数量,-1代表不限
+  max-login-count: -1

+ 18 - 0
config/prod/security.yml

@@ -0,0 +1,18 @@
+# security配置
+security:
+  # 排除路径
+  excludes:
+    # 静态资源
+    - /*.html
+    - /**/*.html
+    - /**/*.css
+    - /**/*.js
+    - /profile/**
+    # 公共路径
+    - /favicon.ico
+    - /error
+    # actuator 监控配置
+    - /actuator
+    - /actuator/**
+    # 其它链接
+    - /auth/login

+ 224 - 0
eco-bom/pom.xml

@@ -0,0 +1,224 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.eco.vip</groupId>
+    <artifactId>eco-bom</artifactId>
+    <packaging>pom</packaging>
+    <name>${project.artifactId}</name>
+    <version>${revision}</version>
+
+    <description>
+        eco-bom 全局依赖项
+    </description>
+
+    <properties>
+        <!-- 系统版本 -->
+        <revision>1.0.0</revision>
+        <spring-boot.version>3.4.3</spring-boot.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+        <lombok.version>1.18.36</lombok.version>
+        <mybatis-flex.version>1.10.9</mybatis-flex.version>
+        <HikariCP.version>6.2.1</HikariCP.version>
+        <hutool-5.version>5.8.36</hutool-5.version>
+        <jackson.version>2.18.3</jackson.version>
+        <jakarta.version>6.1.0</jakarta.version>
+        <mapstruct-plus.version>1.4.8</mapstruct-plus.version>
+        <deepseek4j.version>1.4.5</deepseek4j.version>
+        <milvus.version>2.5.5</milvus.version>
+        <jsqlparser.version>5.1</jsqlparser.version>
+        <DmJdbcDriver18.version>8.1.3.140</DmJdbcDriver18.version>
+        <sa-token.version>1.44.0</sa-token.version>
+        <caffeine.version>3.2.1</caffeine.version>
+        <excel.version>1.2.0</excel.version>
+        <poi.version>5.4.1</poi.version>
+        <oshi.version>6.8.2</oshi.version>
+    </properties>
+
+    <!-- 全局的依赖配置-->
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- spring-boot-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
+            <dependency>
+                <groupId>jakarta.servlet</groupId>
+                <artifactId>jakarta.servlet-api</artifactId>
+                <version>${jakarta.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.github.linpeilie</groupId>
+                <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+                <version>${mapstruct-plus.version}</version>
+            </dependency>
+
+            <!-- com-web -->
+            <dependency>
+                <groupId>org.eco.vip</groupId>
+                <artifactId>com-web</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- com-core -->
+            <dependency>
+                <groupId>org.eco.vip</groupId>
+                <artifactId>com-core</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- com-core -->
+            <dependency>
+                <groupId>org.eco.vip</groupId>
+                <artifactId>com-orm</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- com-security -->
+            <dependency>
+                <groupId>org.eco.vip</groupId>
+                <artifactId>com-security</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- com-excel -->
+            <dependency>
+                <groupId>org.eco.vip</groupId>
+                <artifactId>com-excel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- com-excel -->
+            <dependency>
+                <groupId>org.eco.vip</groupId>
+                <artifactId>com-log</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- lombok -->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <!-- mybatis-flex -->
+            <dependency>
+                <groupId>com.mybatis-flex</groupId>
+                <artifactId>mybatis-flex-spring-boot3-starter</artifactId>
+                <version>${mybatis-flex.version}</version>
+            </dependency>
+
+            <!-- 数据库连接池-->
+            <dependency>
+                <groupId>com.zaxxer</groupId>
+                <artifactId>HikariCP</artifactId>
+                <version>${HikariCP.version}</version>
+            </dependency>
+
+            <!-- hutool 的依赖配置-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool-5.version}</version>
+            </dependency>
+
+            <!-- JSON工具类 -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-databind</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/com.dameng/DmJdbcDriver18 -->
+            <dependency>
+                <groupId>com.dameng</groupId>
+                <artifactId>DmJdbcDriver18</artifactId>
+                <version>${DmJdbcDriver18.version}</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot3-starter -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot3-starter</artifactId>
+                <version>${sa-token.version}</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-jwt -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-jwt</artifactId>
+                <version>${sa-token.version}</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
+            <dependency>
+                <groupId>com.github.ben-manes.caffeine</groupId>
+                <artifactId>caffeine</artifactId>
+                <version>${caffeine.version}</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/cn.idev.excel/fastexcel -->
+            <dependency>
+                <groupId>cn.idev.excel</groupId>
+                <artifactId>fastexcel</artifactId>
+                <version>${excel.version}</version>
+
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
+            <dependency>
+                <groupId>org.apache.poi</groupId>
+                <artifactId>poi-ooxml</artifactId>
+                <version>${poi.version}</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/com.github.oshi/oshi-core -->
+            <dependency>
+                <groupId>com.github.oshi</groupId>
+                <artifactId>oshi-core</artifactId>
+                <version>${oshi.version}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <!-- 统一 revision 版本 -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>flatten-maven-plugin</artifactId>
+                <version>${flatten-maven-plugin.version}</version>
+                <configuration>
+                    <flattenMode>bom</flattenMode>
+                    <updatePomFile>true</updatePomFile>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>flatten</goal>
+                        </goals>
+                        <id>flatten</id>
+                        <phase>process-resources</phase>
+                    </execution>
+                    <execution>
+                        <goals>
+                            <goal>clean</goal>
+                        </goals>
+                        <id>flatten.clean</id>
+                        <phase>clean</phase>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 81 - 0
eco-common/com-core/pom.xml

@@ -0,0 +1,81 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.eco.vip</groupId>
+        <artifactId>eco-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>com-core</artifactId>
+    <name>${project.artifactId}</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+
+        <!-- 数据库连接池-->
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.mybatis-flex</groupId>
+            <artifactId>mybatis-flex-spring-boot3-starter</artifactId>
+        </dependency>
+
+        <!-- aop -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- hutool -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.dameng</groupId>
+            <artifactId>DmJdbcDriver18</artifactId>
+        </dependency>
+
+
+        <!-- JSON工具类 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+
+        <!-- SpringWeb模块 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.github.linpeilie</groupId>
+            <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 23 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/api/IConfig.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.api;
+
+
+/**
+ * @description IConfig
+ *
+ * @author GaoKunW
+ * @date 2025/7/19 01:25
+ */
+public interface IConfig {
+    /**
+     * 根据指定的键获取配置信息的值。
+     *
+     * @param key 配置项的键
+     * @return 配置项的值
+     */
+    String getConfValue(String key);
+}

+ 72 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/api/IDict.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.api;
+
+
+import org.eco.vip.common.core.utils.StrUtils;
+
+import java.util.Map;
+
+/**
+ * @description IDict
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 16:27
+ */
+public interface IDict {
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     *
+     * @param dictType  字典类型
+     * @param dictValue 字典值
+     * @return 字典标签
+     */
+    default String getDictLabel(String dictType, String dictValue) {
+        return getDictLabel(dictType, dictValue, StrUtils.COMMA);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     *
+     * @param dictType  字典类型
+     * @param dictLabel 字典标签
+     * @return 字典值
+     */
+    default String getDictValue(String dictType, String dictLabel) {
+        return getDictValue(dictType, dictLabel, StrUtils.COMMA);
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     *
+     * @param dictType  字典类型
+     * @param dictValue 字典值
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    String getDictLabel(String dictType, String dictValue, String separator);
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     *
+     * @param dictType  字典类型
+     * @param dictLabel 字典标签
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    String getDictValue(String dictType, String dictLabel, String separator);
+
+    /**
+     * 获取字典下所有的字典值与标签
+     *
+     * @param dictType 字典类型
+     * @return dictValue为key,dictLabel为值组成的Map
+     */
+    Map<String, String> getAllDictByDictType(String dictType);
+
+
+}

+ 23 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/config/ApplicationConfig.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.config;
+
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * @description ApplicationConfig
+ *
+ * @author GaoKunW
+ * @date 2025/7/28 23:35
+ */
+@AutoConfiguration
+@EnableAspectJAutoProxy(exposeProxy = true)
+@EnableAsync(proxyTargetClass = true)
+public class ApplicationConfig {
+}

+ 58 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/config/AsyncConfig.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.config;
+
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.eco.vip.common.core.exception.BusinessException;
+import org.eco.vip.common.core.utils.SpringUtils;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * @description AsyncConfig
+ *
+ * @author GaoKunW
+ * @date 2025/7/28 23:15
+ */
+@AutoConfiguration
+@Slf4j
+public class AsyncConfig implements AsyncConfigurer {
+    /**
+     * 自定义 @Async 注解使用系统线程池
+     */
+    @Override
+    public Executor getAsyncExecutor() {
+        // jdk21开启虚拟线程
+        if (SpringUtils.isVirtual()) {
+            return new VirtualThreadTaskExecutor("eco-async-");
+        }
+        return SpringUtils.getBean("scheduledExecutorService");
+    }
+
+    /**
+     * 异步执行异常处理
+     */
+    @Override
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return (throwable, method, objects) -> {
+            log.error("<--异步执行异常处理-->", throwable);
+            StringBuilder sb = new StringBuilder();
+            sb.append("Exception message - ").append(throwable.getMessage())
+                    .append(", Method name - ").append(method.getName());
+            if (ArrayUtil.isNotEmpty(objects)) {
+                sb.append(", Parameter value - ").append(Arrays.toString(objects));
+            }
+            throw new BusinessException(sb.toString());
+        };
+    }
+}

+ 37 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/constant/ConfigConstants.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.constant;
+
+
+/**
+ * @description ConfigConstants
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 16:35
+ */
+public class ConfigConstants {
+
+    /**
+     * 用户默认角色
+     */
+    public final static String INIT_PASSWORD_KEY = "user.init.password";
+
+    /**
+     * 默认文件引擎
+     */
+    public static final String FILE_ENGINE_KE = "default.files.engine";
+
+    /**
+     * 本地文件路径 windows
+     */
+    public static final String LOCAL_FILE_PATH_WINDOWS_KEY = "local.files.path.windows";
+
+    /**
+     * 本地文件路径 unix
+     */
+    public static final String LOCAL_FILE_PATH_UNIX_KEY = "local.files.path.unix";
+
+}

+ 68 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/constant/Constants.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.constant;
+
+
+/**
+ * @description Constants 全局常量
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 11:04
+ */
+public class Constants {
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+
+    /**
+     * 所有顶层ID
+     */
+    public static final String PID = "0";
+
+    /**
+     * 正常状态
+     */
+    public static final String NORMAL = "1";
+
+    /**
+     * 停用状态
+     */
+    public static final String DISABLE = "0";
+
+    /**
+     * 是
+     */
+    public static final String YES = "1";
+
+    /**
+     * 否
+     */
+    public static final String NO = "0";
+
+    /**
+     * 校验是否唯一的返回标识
+     */
+    public final static boolean UNIQUE = true;
+    public final static boolean NOT_UNIQUE = false;
+
+    /**
+     * 超级管理员角色 roleKey
+     */
+    public final static String SUPER_ADMIN_ROLE_KEY = "superAminRole";
+
+    /**
+     * 本地文件路径 unix
+     */
+    public static final String LOCAL_FILE_PATH_KEY = "localFilePath";
+
+    /**
+     * 本地文件默认桶名称
+     */
+    public static final String LOCAL_FILE_BUCKET_KEY = "LocalBucket";
+}

+ 46 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/DeviceType.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.enums;
+
+
+/**
+ * @description DeviceType
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:35
+ */
+public enum DeviceType {
+
+    /**
+     * pc端
+     */
+    PC("pc"),
+
+    /**
+     * app端
+     */
+    APP("app"),
+
+    /**
+     * 小程序端
+     */
+    XCX("xcx"),
+
+    /**
+     * social第三方端
+     */
+    SOCIAL("social");
+
+    private final String device;
+
+    DeviceType(String device) {
+        this.device = device;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+}

+ 46 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/MenuTypeEnum.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.enums;
+
+
+import lombok.Getter;
+import org.eco.vip.common.core.exception.BusinessException;
+
+/**
+ * @description MenuTypeEnum
+ *
+ * @author GaoKunW
+ * @date 2025/7/11 13:44
+ */
+@Getter
+public enum MenuTypeEnum {
+    /** 目录 */
+    CATALOG("C"),
+
+    /** 组件 */
+    MENU("M"),
+
+    /** 内链 */
+    IFRAME("F"),
+
+    /** 外链 */
+    LINK("L");
+
+    private final String value;
+
+    MenuTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public static void validate(String value) {
+        boolean flag = CATALOG.getValue().equals(value) || MENU.getValue().equals(value) || IFRAME.getValue().equals(value) ||
+                LINK.getValue().equals(value);
+        if (!flag) {
+            throw new BusinessException("不支持的菜单类型:{}", value);
+        }
+    }
+
+}

+ 28 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/UserStatus.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.enums;
+
+
+import lombok.Getter;
+
+/**
+ * @description UserStatus
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 17:04
+ */
+@Getter
+public enum UserStatus {
+    NORMAL("1", "正常"), DISABLE("0", "停用"), DELETED("2", "删除");
+
+    private final String code;
+    private final String info;
+
+    UserStatus(String code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+}

+ 45 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/enums/UserType.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.enums;
+
+
+import lombok.Getter;
+import org.eco.vip.common.core.utils.StrUtils;
+
+/**
+ * @description UserType
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:42
+ */
+@Getter
+public enum UserType {
+
+    /**
+     * pc端
+     */
+    PC_USER("pc_user"),
+
+    /**
+     * app端
+     */
+    APP_USER("app_user");
+
+    private final String userType;
+
+    UserType(String userType) {
+        this.userType = userType;
+    }
+
+    public static UserType getUserType(String str) {
+        for (UserType value : values()) {
+            if (StrUtils.contains(str, value.getUserType())) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'UserType' not found By " + str);
+    }
+}

+ 88 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/BusinessException.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.exception;
+
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.eco.vip.common.core.exception.enums.BusinessErrorCode;
+import org.eco.vip.common.core.utils.StrUtils;
+
+/**
+ * @description 业务逻辑异常
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 13:55
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public final class BusinessException extends RuntimeException {
+    /**
+     * 业务错误码
+     *
+     * @see BusinessErrorCode
+     */
+    private Integer code;
+
+    /**
+     * 错误参数
+     */
+    private Object[] msgParams;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public BusinessException() {
+    }
+
+    public BusinessException(String message) {
+        this.message = message;
+    }
+
+
+    public BusinessException(ErrorCode errorCode) {
+        this.code = errorCode.getCode();
+        this.message = errorCode.getMsg();
+    }
+
+    public BusinessException(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public BusinessException(Integer code, String msg, Object... msgParams) {
+        this.message = msg;
+        this.code = code;
+        this.msgParams = msgParams;
+    }
+
+    public BusinessException(String msg, Object... msgParams) {
+        this.message = msg;
+        this.msgParams = msgParams;
+    }
+
+    @Override
+    public String getMessage() {
+        if (StrUtil.isNotBlank(message)) {
+            if (msgParams != null && msgParams.length > 0) {
+                return StrUtils.format(message, msgParams);
+            }
+        }
+        // 如果不传参数,直接返回
+        return message;
+    }
+
+    public BusinessException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+}

+ 32 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/ErrorCode.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.exception;
+
+
+import lombok.Data;
+
+/**
+ * @description ErrorCode
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 13:48
+ */
+@Data
+public class ErrorCode {
+    /**
+     * 错误码
+     */
+    private final Integer code;
+    /**
+     * 错误提示
+     */
+    private final String msg;
+
+    public ErrorCode(Integer code, String message) {
+        this.code = code;
+        this.msg = message;
+    }
+}

+ 65 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/ServerException.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.exception;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.eco.vip.common.core.exception.enums.GlobalErrorCode;
+
+/**
+ * @description ServerException 服务器异常
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 14:03
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ServerException extends RuntimeException {
+
+    /**
+     * 全局错误码
+     *
+     * @see GlobalErrorCode
+     */
+    private Integer code;
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public ServerException() {
+    }
+
+    public ServerException(ErrorCode errorCode) {
+        this.code = errorCode.getCode();
+        this.message = errorCode.getMsg();
+    }
+
+    public ServerException(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public ServerException setCode(Integer code) {
+        this.code = code;
+        return this;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public ServerException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+}

+ 16 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/enums/BusinessErrorCode.java

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.exception.enums;
+
+
+/**
+ * @description BusinessErrorCode
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 13:56
+ */
+public class BusinessErrorCode {
+}

+ 41 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/exception/enums/GlobalErrorCode.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.exception.enums;
+
+
+import org.eco.vip.common.core.exception.ErrorCode;
+
+/**
+ * @description 全局错误码枚举
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 13:53
+ */
+public interface GlobalErrorCode {
+    ErrorCode SUCCESS = new ErrorCode(200, "成功");
+
+    // ========== 客户端错误段 ==========
+    ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
+    ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
+    ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
+    ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
+    ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
+    // 并发请求,不允许
+    ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试");
+    ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
+
+    // ========== 服务端错误段 ==========
+
+    ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
+    ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
+
+    // ========== 自定义错误段 ==========
+    ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试");
+    // 重复请求
+    ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
+
+    ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
+}

+ 39 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/factory/YmlPropertySourceFactory.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.factory;
+
+
+import org.eco.vip.common.core.utils.StrUtils;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * @description YmlPropertySourceFactory
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 15:43
+ */
+public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
+    @Override
+    @SuppressWarnings("NullableProblems")
+    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
+        String sourceName = resource.getResource().getFilename();
+        if (StrUtils.isNotBlank(sourceName) && StrUtils.endWithAny(sourceName, ".yml", ".yaml")) {
+            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+            factory.setResources(resource.getResource());
+            factory.afterPropertiesSet();
+            return new PropertiesPropertySource(sourceName, Objects.requireNonNull(factory.getObject()));
+        }
+        return super.createPropertySource(name, resource);
+    }
+
+}

+ 162 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/CommonResult.java

@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.pojo;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.eco.vip.common.core.exception.BusinessException;
+import org.eco.vip.common.core.exception.ErrorCode;
+import org.eco.vip.common.core.exception.enums.GlobalErrorCode;
+import org.eco.vip.common.core.utils.StrUtils;
+import org.springframework.util.Assert;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @description CommonResult
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 13:47
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@Data
+public class CommonResult<T> implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+    /**
+     * 错误码
+     *
+     * @see ErrorCode#getCode()
+     */
+    private Integer code;
+    /**
+     * 返回数据
+     */
+    private T data;
+    /**
+     * 错误提示,用户可阅读
+     *
+     * @see ErrorCode#getMsg() ()
+     */
+    private String msg;
+
+    /**
+     * 将传入的 result 对象,转换成另外一个泛型结果的对象
+     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
+     *
+     * @param result 传入的 result 对象
+     * @param <T>    返回的泛型
+     * @return 新的 CommonResult 对象
+     */
+    public static <T> CommonResult<T> fail(CommonResult<?> result) {
+        return fail(result.getCode(), result.getMsg());
+    }
+
+    public static <T> CommonResult<T> fail(Integer code, String message) {
+        Assert.isTrue(!GlobalErrorCode.SUCCESS.getCode().equals(code), "code 必须是错误的!");
+        CommonResult<T> result = new CommonResult<>();
+        result.code = code;
+        result.msg = message;
+        return result;
+    }
+    public static <T> CommonResult<T> fail( String message) {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCode.INTERNAL_SERVER_ERROR.getCode();
+        result.msg = message;
+        return result;
+    }
+
+    public static <T> CommonResult<T> fail(String message, Object... msgParams) {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCode.INTERNAL_SERVER_ERROR.getCode();
+        result.msg = StrUtils.format(message, msgParams);
+        return result;
+    }
+
+    public static <T> CommonResult<T> fail(ErrorCode errorCode) {
+        return fail(errorCode.getCode(), errorCode.getMsg());
+    }
+
+    public static <T> CommonResult<T> success(T data) {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCode.SUCCESS.getCode();
+        result.data = data;
+        result.msg = "";
+        return result;
+    }
+
+    public static <T> CommonResult<T> success(String message) {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCode.SUCCESS.getCode();
+        result.data = null;
+        result.msg = message;
+        return result;
+    }
+
+    public static <T> CommonResult<T> success(T data, String message) {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCode.SUCCESS.getCode();
+        result.data = data;
+        result.msg = message;
+        return result;
+    }
+
+    public static <T> CommonResult<T> success() {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCode.SUCCESS.getCode();
+        result.msg = "";
+        return result;
+    }
+
+    public static boolean isSuccess(Integer code) {
+        return Objects.equals(code, GlobalErrorCode.SUCCESS.getCode());
+    }
+
+    @JsonIgnore
+    public boolean isSuccess() {
+        return isSuccess(code);
+    }
+
+    @JsonIgnore
+    public boolean isError() {
+        return !isSuccess();
+    }
+
+    // ========= 和 Exception 异常体系集成 =========
+
+    /**
+     * 判断是否有异常。如果有,则抛出 {@link BusinessException} 异常
+     */
+    public void checkError() throws BusinessException {
+        if (isSuccess()) {
+            return;
+        }
+        // 业务异常
+        throw new BusinessException(code, msg);
+    }
+
+    /**
+     * 判断是否有异常。如果有,则抛出 {@link BusinessException} 异常
+     * 如果没有,则返回 {@link #data} 数据
+     */
+    @JsonIgnore
+    public T getCheckedData() {
+        checkError();
+        return data;
+    }
+
+    public static <T> CommonResult<T> fail(BusinessException businessException) {
+        return fail(businessException.getCode(), businessException.getMessage());
+    }
+}

+ 143 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/LoginUserStorage.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.pojo;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * @description LoginUserStorage
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:13
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class LoginUserStorage implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 用户ID
+     */
+    private String userId;
+
+    /**
+     * 组织ID
+     */
+    private String orgId;
+
+    /**
+     * 组织名
+     */
+    private String orgName;
+
+    /**
+     * token
+     */
+    private String token;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 菜单权限
+     */
+    private Set<String> permissionCodes;
+
+    /**
+     * 角色权限
+     */
+    private Set<String> roleCodes;
+
+    /**
+     * 用户名
+     */
+    private String userName;
+    /**
+     * 用户账号
+     */
+    private String account;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 获取登录id
+     */
+    public String getLoginId() {
+        if (userType == null) {
+            throw new IllegalArgumentException("用户类型不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        return userId;
+    }
+}

+ 68 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/OrderDomain.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.pojo;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import org.eco.vip.common.core.utils.ServletUtils;
+import org.eco.vip.common.core.utils.StrUtils;
+
+/**
+ * @description OrderDomain
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 21:44
+ */
+@Getter
+public class OrderDomain {
+
+    /**
+     * 排序列
+     */
+    public static final String ORDER_BY_COLUMN = "orderName";
+
+    /**
+     * 排序的方向 "desc" 或者 "asc".
+     */
+    public static final String ORDER_BY = "orderBy";
+    /**
+     * 排序列
+     */
+    @Setter
+    private String orderName;
+
+    /**
+     * 排序的方向desc或者asc
+     */
+    private String orderBy = "asc";
+
+    public void setOrderBy(String orderBy) {
+        if (StrUtils.isNotEmpty(orderBy)) {
+            // 兼容前端排序类型
+            if ("ascending".equals(orderBy)) {
+                orderBy = "asc";
+            } else if ("descending".equals(orderBy)) {
+                orderBy = "desc";
+            }
+            this.orderBy = orderBy;
+        }
+    }
+
+    public String getOrderBySql() {
+        if (StrUtils.isEmpty(orderName)) {
+            return "";
+        }
+        return StrUtils.toUnderlineCase(orderName) + " " + orderBy;
+    }
+
+    public static OrderDomain buildOrderRequest() {
+        OrderDomain orderDomain = new OrderDomain();
+        orderDomain.setOrderName(ServletUtils.getParameter(ORDER_BY_COLUMN));
+        orderDomain.setOrderBy(ServletUtils.getParameter(ORDER_BY));
+        return orderDomain;
+    }
+}

+ 65 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/PageResult.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.pojo;
+
+
+import com.mybatisflex.core.paginate.Page;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @description PageResult
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 21:49
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class PageResult<T> implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private List<T> list;
+    private Long total;
+    private Long pageNum;
+    private Long pageSize;
+
+    public PageResult(Long total) {
+        this.list = new ArrayList<>();
+        this.total = total;
+    }
+
+    public PageResult(List<T> list, Long total) {
+        this.list = list;
+        this.total = total;
+    }
+
+    /**
+     * @param page 分页参数
+     * @param <T>  结果泛型信息
+     * @return 分页结果
+     */
+    public static <T> PageResult<T> build(Page<T> page) {
+        PageResult<T> result = new PageResult<>();
+        result.setList(page.getRecords()).setTotal(page.getTotalRow()).setPageNum(page.getPageNumber()).setPageSize(page.getPageSize());
+        return result;
+    }
+
+    public static <T> PageResult<T> empty() {
+        return new PageResult<>(0L);
+    }
+
+    public static <T> PageResult<T> empty(Long total) {
+        return new PageResult<>(total);
+    }
+}

+ 16 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/dto/RoleDTO.java

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.pojo.dto;
+
+
+/**
+ * @description RoleDTO
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:14
+ */
+public class RoleDTO {
+}

+ 16 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/pojo/dto/UserOnlineDTO.java

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.pojo.dto;
+
+
+/**
+ * @description UserOnlineDTO
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:19
+ */
+public class UserOnlineDTO {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ArrayUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.ArrayUtil;
+
+/**
+ * @description ArrayUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/29 10:50
+ */
+public class ArrayUtils extends ArrayUtil {
+}

+ 35 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/BeanUtils.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.bean.BeanUtil;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @description BeanUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/4 01:01
+ */
+public class BeanUtils extends BeanUtil {
+
+    /**
+     * 通用 List 转换方法(基于 Hutool BeanUtil.copyProperties)
+     * @param sourceList 源对象列表
+     * @param targetClass 目标类
+     * @return 转换后的对象列表
+     * @param <S> 源对象类型
+     * @param <T> 目标对象类型
+     */
+    public static <S, T> List<T> convertList(List<S> sourceList, Class<T> targetClass) {
+        return sourceList.stream()
+                .map(source -> BeanUtil.copyProperties(source, targetClass))
+                .collect(Collectors.toList());
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/BooleanUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.BooleanUtil;
+
+/**
+ * @description BooleanUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 18:23
+ */
+public class BooleanUtils extends BooleanUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/CollUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.collection.CollUtil;
+
+/**
+ * @description CollUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/4 00:41
+ */
+public class CollUtils extends CollUtil {
+}

+ 33 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ConfigUtils.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import org.eco.vip.common.core.api.IConfig;
+
+/**
+ * @description ConfigUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/19 01:27
+ */
+public class ConfigUtils {
+    private ConfigUtils() {
+        throw new IllegalStateException("ConfigUtils class Illegal");
+    }
+
+    /**
+     * 根据指定的键获取配置信息的值。
+     *
+     * @param key
+     *            配置项的键
+     * @return 配置项的值
+     */
+    public static String getConfValue(String key) {
+        IConfig config = SpringUtils.getBean(IConfig.class);
+        return config.getConfValue(key);
+    }
+}

+ 32 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/DateUtils.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.date.DateUtil;
+
+import java.util.Date;
+
+/**
+ * @description DateUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/23 14:23
+ */
+public class DateUtils extends DateUtil {
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    /**
+     * 日期路径 年/月/日 如2018/08/08
+     */
+    public static String datePath() {
+        Date now = new Date();
+        return DateUtil.format(now, "yyyy" + FileUtils.FILE_SEPARATOR + "MM" + FileUtils.FILE_SEPARATOR + "dd");
+    }
+    public static String dateTimeNow() {
+        return DateUtil.format(new Date(), YYYYMMDDHHMMSS);
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ExceptionUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+
+/**
+ * @description ExceptionUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/29 10:44
+ */
+public class ExceptionUtils extends ExceptionUtil {
+}

+ 56 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/FileDownloadUtils.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.io.FileUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @description FileDownloadUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 18:24
+ */
+@Slf4j
+public class FileDownloadUtils {
+
+    /**
+     * 下载文件
+     *
+     * @param file 文件
+     * @param response HttpServletResponse
+     */
+    public static void download(File file, HttpServletResponse response) {
+        download(file.getName(), FileUtil.readBytes(file), response);
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param fileName 文件名
+     * @param fileBytes 文件字节数组
+     * @param response HttpServletResponse
+     */
+    public static void download(String fileName, byte[] fileBytes, HttpServletResponse response) {
+        try {
+            String encodeFileName = URLUtils.encode(fileName);
+            response.setHeader("Content-Disposition", "attachment;filename=" + encodeFileName);
+            response.addHeader("Content-Length", "" + fileBytes.length);
+            response.setHeader("Access-Control-Allow-Origin", "*");
+            response.setHeader("download-filename", encodeFileName);
+            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            IoUtils.write(response.getOutputStream(), true, fileBytes);
+        } catch (IOException e) {
+            log.error(">>> 文件下载异常:", e);
+        }
+    }
+}

+ 148 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/FileLocalUtils.java

@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.Getter;
+import org.eco.vip.common.core.api.IConfig;
+import org.eco.vip.common.core.constant.ConfigConstants;
+import org.eco.vip.common.core.constant.Constants;
+import org.eco.vip.common.core.exception.BusinessException;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @description FileLocalUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 16:23
+ */
+public class FileLocalUtils {
+
+
+    /**
+     *  本地文件客户端
+     */
+    @Getter
+    private static JSONObject client;
+
+    /**
+     * 初始化本地文件客户端
+     */
+    private static void initClient() {
+        String uploadPath;
+        IConfig config = SpringUtils.getBean(IConfig.class);
+        if (SystemUtils.getOsInfo().isWindows()) {
+            String windowsPath = config.getConfValue(ConfigConstants.LOCAL_FILE_PATH_WINDOWS_KEY);
+            if (StrUtils.isBlank(windowsPath)) {
+                throw new BusinessException("系统配置管理(本地文件路径 windows未配置: local.files.path.windows)");
+            }
+            uploadPath = windowsPath;
+        } else {
+            String unixPath = config.getConfValue(ConfigConstants.LOCAL_FILE_PATH_UNIX_KEY);
+            if (StrUtils.isBlank(unixPath)) {
+                throw new BusinessException("系统配置管理(本地文件路径 unix未配置: local.files.path.unix)");
+            }
+            uploadPath = unixPath;
+        }
+        if (!FileUtils.exist(uploadPath)) {
+            FileUtils.mkdir(uploadPath);
+        }
+        client = JSONUtil.createObj();
+        client.set(Constants.LOCAL_FILE_PATH_KEY, uploadPath);
+    }
+
+    /**
+     * 存储文件,不返回地址
+     *
+     * @param bucketName  桶名称
+     * @param path         文件唯一名
+     * @param inputStream 文件流
+     */
+    public static void storageFile(String bucketName, String path, InputStream inputStream) {
+        initClient();
+        FileUtils.writeFromStream(inputStream, getUploadFilePath() + genUploadFileDir(bucketName, path));
+    }
+
+    /**
+     * 获取上传地址
+     *
+     * @return 上传地址
+     */
+    public static String getUploadFilePath() {
+        return client.getStr(Constants.LOCAL_FILE_PATH_KEY);
+    }
+
+    /**
+     * 存储文件,不返回地址
+     *
+     * @param bucketName 桶名称
+     * @param path        文件唯一名
+     * @param multipartFile      文件
+     */
+    public static void storageFile(String bucketName, String path, MultipartFile multipartFile) {
+        InputStream inputStream;
+        try {
+            inputStream = multipartFile.getInputStream();
+        } catch (IOException e) {
+            throw new BusinessException("获取文件流异常:{}", multipartFile.getName());
+        }
+        storageFile(bucketName, path, inputStream);
+    }
+
+    /**
+     * 存储文件,返回存储的地址
+     *
+     * @param bucketName 桶名称
+     * @param path         文件唯一名
+     * @param multipartFile      文件
+     */
+    public static String storageFileWithReturnUrl(String bucketName, String path, MultipartFile multipartFile) {
+        storageFile(bucketName, path, multipartFile);
+        return genUploadFileDir(bucketName, path);
+    }
+
+    /**
+     * 根据桶名称和文件path获取文件
+     *
+     * @param path        文件路径
+     */
+    public static File getFileByBucketNameAndPath(String path) {
+        initClient();
+        String newPath = getUploadFilePath() + path;
+        File file = FileUtils.file(newPath);
+        if (!FileUtils.exist(file)) {
+            throw new BusinessException("文件{}不存在", newPath);
+        }
+        return file;
+    }
+
+    /**
+     * 根据桶名称和文件key获取文件
+     *
+     * @param bucketName 文件桶
+     * @param key        文件唯一名
+     * @return 上传文件目录
+     */
+    public static String genUploadFileDir(String bucketName, String key) {
+        return FileUtils.FILE_SEPARATOR + bucketName + FileUtils.FILE_SEPARATOR + key;
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param path        文件path
+     */
+    public static void deleteFile(String path) {
+        File file = getFileByBucketNameAndPath(path);
+        FileUtils.del(file);
+    }
+}

+ 60 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/FileUtils.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.io.FileUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @description FileUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 17:50
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FileUtils extends FileUtil {
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param response 响应对象
+     * @param realFileName 真实文件名
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) {
+        String percentEncodedFileName = percentEncode(realFileName);
+
+        String contentDispositionValue = "attachment; filename=" +
+                percentEncodedFileName +
+                ";" +
+                "filename*=" +
+                "utf-8''" +
+                percentEncodedFileName;
+        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+        response.setHeader("Content-disposition", contentDispositionValue);
+        response.setHeader("download-filename", percentEncodedFileName);
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
+        return encode.replaceAll("\\+", "%20");
+    }
+
+    public static String genFilePath(String key, String originalFileName) {
+        return StrUtils.format("{}{}{}_{}", DateUtils.datePath(),FileUtils.FILE_SEPARATOR, key, originalFileName);
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/IdUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.IdUtil;
+
+/**
+ * @description IdUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/10 14:46
+ */
+public class IdUtils extends IdUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ImgUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.img.ImgUtil;
+
+/**
+ * @description ImgUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 17:49
+ */
+public class ImgUtils extends ImgUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/IoUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.io.IoUtil;
+
+/**
+ * @description IoUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 18:35
+ */
+public class IoUtils extends IoUtil {
+}

+ 50 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/IpUtils.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.net.Ipv4Util;
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+
+/**
+ * @description IpUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/23 15:24
+ */
+@Slf4j
+public class IpUtils extends Ipv4Util {
+
+    public static String getIpAddress() {
+        try {
+            //从网卡中获取IP
+            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
+            InetAddress ip;
+            while (allNetInterfaces.hasMoreElements()) {
+                NetworkInterface netInterface = allNetInterfaces.nextElement();
+                //用于排除回送接口,非虚拟网卡,未在使用中的网络接口
+                if (!netInterface.isLoopback() && !netInterface.isVirtual() && netInterface.isUp()) {
+                    //返回和网络接口绑定的所有IP地址
+                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
+                    while (addresses.hasMoreElements()) {
+                        ip = addresses.nextElement();
+                        if (ip instanceof Inet4Address) {
+                            return ip.getHostAddress();
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.info("IP地址获取失败{}", e.getMessage());
+        }
+        return "127.0.0.1";
+    }
+}

+ 170 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/JsonUtils.java

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @description JsonUtil
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 16:27
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils extends JSONUtil {
+    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+    public static ObjectMapper getObjectMapper() {
+        return OBJECT_MAPPER;
+    }
+
+    public static String toJsonString(Object object) {
+        if (ObjUtils.isNull(object)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, Class<T> clazz) {
+        if (StrUtils.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
+        if (ArrayUtils.isEmpty(bytes)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(bytes, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
+        if (StrUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, typeReference);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Dict parseMap(String text) {
+        if (StrUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+        } catch (MismatchedInputException e) {
+            // 类型不匹配说明不是json
+            return null;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static List<Dict> parseArrayMap(String text) {
+        if (StrUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> List<T> parseArray(String text, Class<T> clazz) {
+        if (StrUtils.isEmpty(text)) {
+            return new ArrayList<>();
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取切面的参数JSON
+     *
+     * @param joinPoint 切面
+     * @return JSON字符串
+     */
+    public static String getArgsJsonString(JoinPoint joinPoint, String[] excludeProperties) {
+        Signature signature = joinPoint.getSignature();
+        // 参数名数组
+        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
+        // 构造参数组集合
+        Map<String, Object> map = MapUtil.newHashMap();
+        Object[] args = joinPoint.getArgs();
+        for (int i = 0; i < args.length; i++) {
+            if (ObjUtils.isNotEmpty(args[i]) && isUsefulParam(args[i])) {
+                if (isTypeJSON(StrUtil.toString(args[i]))) {
+                    try {
+                        JSONObject jsonObject = parseObj(args[i]);
+                        if (ObjUtils.isNotEmpty(jsonObject)) {
+                            map.put(parameterNames[i], jsonObject);
+                        } else {
+                            map.put(parameterNames[i], parseArray(args[i]));
+                        }
+                    } catch (Exception e) {
+                        map.put(parameterNames[i], null);
+                    }
+                } else {
+                    map.put(parameterNames[i], toJsonStr(args[i]));
+                }
+            }
+        }
+        MapUtils.removeAny(map, excludeProperties);
+        return toJsonStr(map);
+    }
+
+    /**
+     * 判断是否需要拼接的参数,过滤掉HttpServletRequest,MultipartFile,HttpServletResponse等类型参数
+     *
+     * @param arg 参数
+     * @return 是否需要拼接
+     */
+    private static boolean isUsefulParam(Object arg) {
+        return !(arg instanceof MultipartFile) && !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse);
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/MapUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.map.MapUtil;
+
+/**
+ * @description MapUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/29 14:34
+ */
+public class MapUtils extends MapUtil {
+}

+ 40 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/MapstructUtils.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import io.github.linpeilie.Converter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * @description MapstructUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 10:58
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MapstructUtils {
+    private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
+
+    /**
+     * 将 T 类型对象,转换为 desc 类型的对象并返回
+     *
+     * @param source 数据来源实体
+     * @param desc   描述对象 转换后的对象
+     * @return desc
+     */
+    public static <T, V> V convert(T source, Class<V> desc) {
+        if (ObjectUtil.isNull(source)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(desc)) {
+            return null;
+        }
+        return CONVERTER.convert(source, desc);
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/NetUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.net.NetUtil;
+
+/**
+ * @description NetUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/25 22:28
+ */
+public class NetUtils extends NetUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/NumberUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.NumberUtil;
+
+/**
+ * @description NumberUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/25 17:22
+ */
+public class NumberUtils extends NumberUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ObjUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.ObjUtil;
+
+/**
+ * @description ObjUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 11:15
+ */
+public class ObjUtils extends ObjUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/OshiUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.system.oshi.OshiUtil;
+
+/**
+ * @description OshiUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/25 16:34
+ */
+public class OshiUtils extends OshiUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/RandomUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.RandomUtil;
+
+/**
+ * @description RandomUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 13:45
+ */
+public class RandomUtils extends RandomUtil {
+}

+ 56 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ReflectUtils.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.ReflectUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @description ReflectUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 16:06
+ */
+@Slf4j
+public class ReflectUtils extends ReflectUtil {
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    private static final String CGLIB_CLASS_SEPARATOR = "$$";
+    private static final String UNKNOWN = "null";
+
+    /**
+     * 调用Getter方法.
+     * 支持多级,如:对象名.对象名.方法
+     */
+    public static Object invokeGetter(Object obj, String fieldName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        String getterName = GETTER_PREFIX + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
+        Method getter = obj.getClass().getMethod(getterName);
+        return getter.invoke(obj);
+    }
+
+    public static String getStringVal(Object obj) {
+        if (ObjUtils.isNotNull(obj) && !(UNKNOWN).equals(obj)) {
+            return obj.toString();
+        } else {
+            return "";
+        }
+    }
+
+    public static Long getLongVal(Object obj) {
+        return (ObjUtils.isNotNull(obj) && !(UNKNOWN).equals(obj)) ? (Long.parseLong(obj.toString())) : 0L;
+    }
+
+    public static Integer getIntVal(Object obj) {
+        return (ObjUtils.isNotNull(obj) && !(UNKNOWN).equals(obj)) ? (Integer.parseInt(obj.toString())) : 0;
+    }
+
+}

+ 87 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/Seqs.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @description Seqs
+ *
+ * @author GaoKunW
+ * @date 2025/7/23 14:46
+ */
+public class Seqs {
+    // 通用序列类型
+    public static final String COMM_SEQ_TYPE = "COMMON";
+
+    // 上传序列类型
+    public static final String UPLOAD_SEQ_TYPE = "UPLOAD";
+
+    // 通用接口序列数
+    private static final AtomicInteger COMM_SEQ = new AtomicInteger(1);
+
+    // 上传接口序列数
+    private static final AtomicInteger UPLOAD_SEQ = new AtomicInteger(1);
+
+    // 机器标识
+    private static final String MACHINE_CODE = "E";
+
+    /**
+     * 获取通用序列号
+     *
+     * @return 序列值
+     */
+    public static String getId() {
+        return getId(COMM_SEQ_TYPE);
+    }
+
+    /**
+     * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串
+     *
+     * @return 序列值
+     */
+    public static String getId(String type) {
+        AtomicInteger atomicInt = COMM_SEQ;
+        if (UPLOAD_SEQ_TYPE.equals(type)) {
+            atomicInt = UPLOAD_SEQ;
+        }
+        return getId(atomicInt, 3);
+    }
+
+    /**
+     * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串
+     *
+     * @param atomicInt 序列数
+     * @param length 数值长度
+     * @return 序列值
+     */
+    public static String getId(AtomicInteger atomicInt, int length) {
+
+        String result = DateUtils.dateTimeNow();
+        result += MACHINE_CODE;
+        result += getSeq(atomicInt, length);
+        return result;
+    }
+
+    /**
+     * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数
+     *
+     * @return 序列值
+     */
+    private synchronized static String getSeq(AtomicInteger atomicInt, int length) {
+        // 先取值再+1
+        int value = atomicInt.getAndIncrement();
+
+        // 如果更新后值>=10 的 (length)幂次方则重置为1
+        int maxSeq = (int) Math.pow(10, length);
+        if (atomicInt.get() >= maxSeq) {
+            atomicInt.set(1);
+        }
+        // 转字符串,用0左补齐
+        return StrUtils.padPre(StrUtils.toString(value), length, '0');
+    }
+}

+ 41 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ServletUtils.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.extra.servlet.JakartaServletUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * @description ServletUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 21:32
+ */
+public class ServletUtils extends JakartaServletUtil {
+
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name) {
+        return getRequest().getParameter(name);
+    }
+
+    /**
+     * 获取request
+     */
+    public static HttpServletRequest getRequest() {
+        return getRequestAttributes().getRequest();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes() {
+        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+        return (ServletRequestAttributes) attributes;
+    }
+}

+ 42 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/SpringUtils.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.boot.autoconfigure.thread.Threading;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+
+/**
+ * @description SpringUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 16:22
+ */
+public final class SpringUtils extends SpringUtil {
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name bean名称
+     * @return boolean
+     */
+    public static boolean containsBean(String name) {
+        return getBeanFactory().containsBean(name);
+    }
+
+    /**
+     * 获取spring上下文
+     */
+    public static ApplicationContext context() {
+        return getApplicationContext();
+    }
+
+    public static boolean isVirtual() {
+        return Threading.VIRTUAL.isActive(getBean(Environment.class));
+    }
+}

+ 50 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/SqlUtils.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+/**
+ * @description SqlUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 22:09
+ */
+public class SqlUtils {
+    /**
+     * 定义常用的 sql关键字
+     */
+    public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()";
+
+    /**
+     * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
+     */
+    public static String SQL_PATTERN = "[a-zA-Z0-9_ ,.]+";
+
+    /**
+     * 限制orderBy最大长度
+     */
+    private static final int ORDER_BY_MAX_LENGTH = 500;
+
+    /**
+     * 检查字符,防止注入绕过
+     */
+    public static String escapeOrderBySql(String value) {
+        if (StrUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
+            throw new IllegalArgumentException("参数不符合规范,不能进行查询");
+        }
+        if (StrUtils.length(value) > ORDER_BY_MAX_LENGTH) {
+            throw new IllegalArgumentException("参数已超过最大限制,不能进行查询");
+        }
+        return value;
+    }
+
+    /**
+     * 验证 order by 语法是否符合规范
+     */
+    public static boolean isValidOrderBySql(String value) {
+        return value.matches(SQL_PATTERN);
+    }
+}

+ 22 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/StrUtils.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @description StrUtil
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 16:51
+ */
+@Slf4j
+public class StrUtils extends StrUtil {
+
+    public final static String PERCENT_SIGN = "%";
+}

+ 268 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/StreamUtils.java

@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * @description StreamUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:52
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+    /**
+     * 将collection过滤
+     *
+     * @param collection 需要转化的集合
+     * @param function   过滤方法
+     * @return 过滤后的list
+     */
+    public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+        return collection.stream().filter(function).collect(Collectors.toList());
+    }
+
+    /**
+     * 将collection拼接
+     *
+     * @param collection 需要转化的集合
+     * @param function   拼接方法
+     * @return 拼接后的list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function) {
+        return join(collection, function, StrUtils.COMMA);
+    }
+
+    /**
+     * 将collection拼接
+     *
+     * @param collection 需要转化的集合
+     * @param function   拼接方法
+     * @param delimiter  拼接符
+     * @return 拼接后的list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
+        if (CollUtil.isEmpty(collection)) {
+            return StrUtils.EMPTY;
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+    }
+
+    /**
+     * 将collection排序
+     *
+     * @param collection 需要转化的集合
+     * @param comparing  排序方法
+     * @return 排序后的list
+     */
+    public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+        return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
+    }
+
+    /**
+     * 将collection转化为类型不变的map<br>
+     * <B>{@code Collection<V>  ---->  Map<K,V>}</B>
+     *
+     * @param collection 需要转化的集合
+     * @param key        V类型转化为K类型的lambda方法
+     * @param <V>        collection中的泛型
+     * @param <K>        map中的key类型
+     * @return 转化后的map
+     */
+    public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+    }
+
+    /**
+     * 将Collection转化为map(value类型与collection的泛型不同)<br>
+     * <B>{@code Collection<E> -----> Map<K,V>  }</B>
+     *
+     * @param collection 需要转化的集合
+     * @param key        E类型转化为K类型的lambda方法
+     * @param value      E类型转化为V类型的lambda方法
+     * @param <E>        collection中的泛型
+     * @param <K>        map中的key类型
+     * @param <V>        map中的value类型
+     * @return 转化后的map
+     */
+    public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
+    }
+
+    /**
+     * 将collection按照规则(比如有相同的班级id)分类成map<br>
+     * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
+     *
+     * @param collection 需要分类的集合
+     * @param key        分类的规则
+     * @param <E>        collection中的泛型
+     * @param <K>        map中的key类型
+     * @return 分类后的map
+     */
+    public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+                .stream().filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+    }
+
+    /**
+     * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B>
+     *
+     * @param collection 需要分类的集合
+     * @param key1       第一个分类的规则
+     * @param key2       第二个分类的规则
+     * @param <E>        集合元素类型
+     * @param <K>        第一个map中的key类型
+     * @param <U>        第二个map中的key类型
+     * @return 分类后的map
+     */
+    public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+                .stream().filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+    }
+
+    /**
+     * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B>
+     *
+     * @param collection 需要分类的集合
+     * @param key1       第一个分类的规则
+     * @param key2       第二个分类的规则
+     * @param <T>        第一个map中的key类型
+     * @param <U>        第二个map中的key类型
+     * @param <E>        collection中的泛型
+     * @return 分类后的map
+     */
+    public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+                .stream().filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+    }
+
+    /**
+     * 将collection转化为List集合,但是两者的泛型不同<br>
+     * <B>{@code Collection<E>  ------>  List<T> } </B>
+     *
+     * @param collection 需要转化的集合
+     * @param function   collection中的泛型转化为list泛型的lambda表达式
+     * @param <E>        collection中的泛型
+     * @param <T>        List中的泛型
+     * @return 转化后的list
+     */
+    public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        return collection
+                .stream()
+                .map(function)
+                .filter(Objects::nonNull)
+                // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 将collection转化为Set集合,但是两者的泛型不同<br>
+     * <B>{@code Collection<E>  ------>  Set<T> } </B>
+     *
+     * @param collection 需要转化的集合
+     * @param function   collection中的泛型转化为set泛型的lambda表达式
+     * @param <E>        collection中的泛型
+     * @param <T>        Set中的泛型
+     * @return 转化后的Set
+     */
+    public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection) || function == null) {
+            return CollUtil.newHashSet();
+        }
+        return collection
+                .stream()
+                .map(function)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+    }
+
+
+    /**
+     * 合并两个相同key类型的map
+     *
+     * @param map1  第一个需要合并的 map
+     * @param map2  第二个需要合并的 map
+     * @param merge 合并的lambda,将key  value1 value2合并成最终的类型,注意value可能为空的情况
+     * @param <K>   map中的key类型
+     * @param <X>   第一个 map的value类型
+     * @param <Y>   第二个 map的value类型
+     * @param <V>   最终map的value类型
+     * @return 合并后的map
+     */
+    public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
+        if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
+            return MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map1)) {
+            map1 = MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map2)) {
+            map2 = MapUtil.newHashMap();
+        }
+        Set<K> key = new HashSet<>();
+        key.addAll(map1.keySet());
+        key.addAll(map2.keySet());
+        Map<K, V> map = new HashMap<>();
+        for (K t : key) {
+            X x = map1.get(t);
+            Y y = map2.get(t);
+            V z = merge.apply(x, y);
+            if (z != null) {
+                map.put(t, z);
+            }
+        }
+        return map;
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/SystemUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.system.SystemUtil;
+
+/**
+ * @description SystemUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 16:29
+ */
+public class SystemUtils extends SystemUtil {
+}

+ 83 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ThreadUtils.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.thread.ThreadUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.*;
+
+/**
+ * @description ThreadUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/28 23:44
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@Slf4j
+public class ThreadUtils extends ThreadUtil {
+
+    /**
+     * 停止线程池
+     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+     * 如果仍然超時,則強制退出.
+     * 另对在shutdown时线程本身被调用中断做了处理.
+     */
+    public static void shutdownAndAwaitTermination(ExecutorService pool) {
+        if (pool != null && !pool.isShutdown()) {
+            pool.shutdown();
+            try {
+                if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+                    pool.shutdownNow();
+                    if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+                        log.info("Pool did not terminate");
+                    }
+                }
+            } catch (InterruptedException ie) {
+                pool.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+
+    /**
+     * 打印线程异常信息
+     */
+    public static void printException(Runnable r, Throwable t) {
+        if (t == null && r instanceof Future<?> future) {
+            try {
+                if (future.isDone()) {
+                    future.get();
+                }
+            } catch (CancellationException ce) {
+                t = ce;
+            } catch (ExecutionException ee) {
+                t = ee.getCause();
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null) {
+            log.error(t.getMessage(), t);
+        }
+    }
+
+    /**
+     * sleep等待,单位为毫秒
+     */
+    public static void sleeps(long milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/TreeUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.lang.tree.TreeUtil;
+
+/**
+ * @description TreeUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/7 17:37
+ */
+public class TreeUtils extends TreeUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/URLUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.core.util.URLUtil;
+
+/**
+ * @description URLUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/22 18:26
+ */
+public class URLUtils extends URLUtil {
+}

+ 18 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/UserAgentUtils.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import cn.hutool.http.useragent.UserAgentUtil;
+
+/**
+ * @description UserAgentUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/28 22:57
+ */
+public class UserAgentUtils extends UserAgentUtil {
+}

+ 37 - 0
eco-common/com-core/src/main/java/org/eco/vip/common/core/utils/ValidatorUtils.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.core.utils;
+
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validator;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eco.vip.common.core.exception.BusinessException;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @description ValidatorUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/1 16:21
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+    private static final Validator VALID = SpringUtils.getBean(Validator.class);
+
+    public static <T> void validate(T object, Class<?>... groups) {
+        Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
+        if (!validate.isEmpty()) {
+            String msg = validate.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(","));
+            throw new BusinessException("参数校验异常: {}", msg);
+        }
+    }
+}

+ 2 - 0
eco-common/com-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,2 @@
+org.eco.vip.common.core.config.ApplicationConfig
+org.eco.vip.common.core.config.AsyncConfig

+ 46 - 0
eco-common/com-excel/pom.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2025 GaoKunW
+  ~
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.eco.vip</groupId>
+        <artifactId>eco-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>com-excel</artifactId>
+    <name>${project.artifactId}</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.eco.vip</groupId>
+            <artifactId>com-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.idev.excel</groupId>
+            <artifactId>fastexcel</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.poi</groupId>
+                    <artifactId>poi-ooxml</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+</project>

+ 29 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/annotation/CellMerge.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @description CellMerge
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:13
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+    /**
+     * col 索引
+     */
+    int index() default -1;
+}

+ 42 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/annotation/ExcelDictFormat.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.annotation;
+
+
+import org.eco.vip.common.core.utils.StrUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @description ExcelDictFormat
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:13
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+    /**
+     * 如果是字典类型,请设置字典的type值 (如: COMMON_STATUS)
+     */
+    String dictType() default "";
+
+    /**
+     * 读取内容转表达式 value->label
+     */
+    String readConverterExp() default "";
+
+    /**
+     * 分隔符,读取字符串组内容
+     */
+    String separator() default StrUtils.COMMA;
+}

+ 39 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/annotation/ExcelEnumFormat.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @description ExcelEnumFormat
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:17
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+    /**
+     * 字典枚举类型
+     */
+    Class<? extends Enum<?>> enumClass();
+
+    /**
+     * 字典枚举类中对应的code属性名称,默认为code
+     */
+    String codeField() default "code";
+
+    /**
+     * 字典枚举类中对应的text属性名称,默认为text
+     */
+    String textField() default "text";
+}

+ 56 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/convert/ExcelBigNumberConvert.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.convert;
+
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * @description ExcelBigNumberConvert
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 17:10
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter<Long> {
+    @Override
+    public Class<Long> supportJavaTypeKey() {
+        return Long.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        return Convert.toLong(cellData.getData());
+    }
+
+    @Override
+    public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNotNull(object)) {
+            String str = Convert.toStr(object);
+            if (str.length() > 15) {
+                return new WriteCellData<>(str);
+            }
+        }
+        WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
+        cellData.setType(CellDataTypeEnum.NUMBER);
+        return cellData;
+    }
+}

+ 77 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/convert/ExcelDictConvert.java

@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.convert;
+
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import org.eco.vip.common.core.api.IDict;
+import org.eco.vip.common.core.utils.SpringUtils;
+import org.eco.vip.common.core.utils.StrUtils;
+import org.eco.vip.common.excel.annotation.ExcelDictFormat;
+import org.eco.vip.common.excel.utils.ExcelUtils;
+
+import java.lang.reflect.Field;
+
+/**
+ * @description ExcelDictConvert
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 17:11
+ */
+public class ExcelDictConvert implements Converter<Object> {
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+        String type = anno.dictType();
+        String label = cellData.getStringValue();
+        String value;
+        if (StrUtils.isBlank(type)) {
+            value = ExcelUtils.reverseByExp(label, anno.readConverterExp(), anno.separator());
+        } else {
+            value = SpringUtils.getBean(IDict.class).getDictValue(type, label, anno.separator());
+        }
+        return Convert.convert(contentProperty.getField().getType(), value);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+        String type = anno.dictType();
+        String value = Convert.toStr(object);
+        String label;
+        if (StrUtils.isBlank(type)) {
+            label = ExcelUtils.convertByExp(value, anno.readConverterExp(), anno.separator());
+        } else {
+            label = SpringUtils.getBean(IDict.class).getDictLabel(type, value, anno.separator());
+        }
+        return new WriteCellData<>(label);
+    }
+
+    private ExcelDictFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
+    }
+}

+ 18 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/convert/ExcelEnumConvert.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.convert;
+
+
+import cn.idev.excel.converters.Converter;
+
+/**
+ * @description ExcelEnumConvert
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 17:12
+ */
+public class ExcelEnumConvert implements Converter<Object> {
+}

+ 119 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/CellMergeStrategy.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.metadata.Head;
+import cn.idev.excel.write.merge.AbstractMergeStrategy;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.eco.vip.common.core.utils.ReflectUtils;
+import org.eco.vip.common.excel.annotation.CellMerge;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @description CellMergeStrategy 列值重复合并策略
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 16:02
+ */
+@Slf4j
+public class CellMergeStrategy extends AbstractMergeStrategy {
+    private final List<CellRangeAddress> cellList;
+
+    /**
+     * 是否有标题
+     */
+    private final boolean hasTitle;
+
+    /**
+     * 开始行号,从0开始(第一行),如果有要忽略的(标题行)需要设置非0
+     */
+    private final int rowIndex;
+
+    public CellMergeStrategy(List<?> list, boolean hasTitle) {
+        this.hasTitle = hasTitle;
+        // 行合并开始下标
+        this.rowIndex = hasTitle ? 1 : 0;
+        this.cellList = handle(list, hasTitle);
+    }
+
+    @Override
+    protected void merge(Sheet sheet, Cell cell, Head head, Integer integer) {
+        // 如果 cellList 不为空,并且当前单元格是第一列的第一行,则合并单元格
+        if (!cellList.isEmpty() && cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) {
+            for (CellRangeAddress item : cellList) {
+                sheet.addMergedRegion(item);
+            }
+        }
+    }
+
+    @SneakyThrows
+    private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
+        List<CellRangeAddress> localCellList = new ArrayList<>();
+        if (list.isEmpty()) {
+            return localCellList;
+        }
+        // 获取字段信息并初始化合并字段
+        List<Field> mergeFields = new ArrayList<>();
+        List<Integer> mergeFieldsIndex = new ArrayList<>();
+        int startRowIndex = initializeMergeFields(list.getFirst().getClass(), mergeFields, mergeFieldsIndex, hasTitle);
+
+        // 处理合并逻辑
+        Map<Field, Object> prevRowValues = new HashMap<>();
+        for (int i = 1; i < list.size(); i++) {
+            boolean merged = false;
+            for (int j = 0; j < mergeFields.size(); j++) {
+                Field field = mergeFields.get(j);
+                Object currentValue = ReflectUtils.invokeGetter(list.get(i), field.getName());
+                Object prevValue = prevRowValues.get(field);
+
+                if (prevValue != null && prevValue.equals(currentValue)) {
+                    if (!merged) {
+                        int colNum = mergeFieldsIndex.get(j) - 1;
+                        localCellList.add(new CellRangeAddress(i - 1 + startRowIndex, i + startRowIndex, colNum, colNum));
+                        merged = true;
+                    }
+                } else {
+                    prevRowValues.put(field, currentValue);
+                }
+            }
+        }
+        return localCellList;
+    }
+
+    private int initializeMergeFields(Class<?> clazz, List<Field> mergeFields, List<Integer> mergeFieldsIndex, boolean hasTitle) {
+        Field[] fields = ReflectUtils.getFields(clazz);
+        int startRowIndex = 0;
+        for (int i = 0; i < fields.length; i++) {
+            Field field = fields[i];
+            if (field.isAnnotationPresent(CellMerge.class)) {
+                CellMerge cm = field.getAnnotation(CellMerge.class);
+                mergeFields.add(field);
+                mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
+
+                if (hasTitle) {
+                    ExcelProperty property = field.getAnnotation(ExcelProperty.class);
+                    startRowIndex = Math.max(startRowIndex, property.value().length);
+                }
+            }
+        }
+        return startRowIndex;
+    }
+
+    public boolean isHasTitle() {
+        return hasTitle;
+    }
+}

+ 119 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/DefaultExcelListener.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import cn.hutool.core.util.StrUtil;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import cn.idev.excel.exception.ExcelAnalysisException;
+import cn.idev.excel.exception.ExcelDataConvertException;
+import cn.idev.excel.metadata.CellExtra;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eco.vip.common.core.utils.JsonUtils;
+import org.eco.vip.common.core.utils.StreamUtils;
+import org.eco.vip.common.core.utils.ValidatorUtils;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @description DefaultExcelListener
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:36
+ */
+@Slf4j
+@NoArgsConstructor
+public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
+
+    /**
+     * 是否Validator检验,默认为是
+     */
+    private Boolean isValidate = Boolean.TRUE;
+
+    /**
+     * excel 表头数据
+     */
+    private Map<Integer, String> headMap;
+    /**
+     * 导入回执
+     */
+    private ExcelResult<T> excelResult;
+
+    public DefaultExcelListener(boolean isValidate) {
+        this.excelResult = new DefaultExcelResult<>();
+        this.isValidate = isValidate;
+    }
+
+    @Override
+    public void invoke(T t, AnalysisContext analysisContext) {
+        if (isValidate) {
+            ValidatorUtils.validate(t);
+        }
+        excelResult.getList().add(t);
+    }
+
+    /**
+     * 处理异常
+     *
+     * @param exception ExcelDataConvertException
+     * @param context   Excel 上下文
+     */
+    @Override
+    public void onException(Exception exception, AnalysisContext context) {
+        String errMsg = null;
+        if (exception instanceof ExcelDataConvertException excelDataConvertException) {
+            // 如果是某一个单元格的转换异常 能获取到具体行号
+            Integer rowIndex = excelDataConvertException.getRowIndex();
+            Integer columnIndex = excelDataConvertException.getColumnIndex();
+            errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
+                    rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
+            if (log.isDebugEnabled()) {
+                log.error(errMsg);
+            }
+        }
+        if (exception instanceof ConstraintViolationException constraintViolationException) {
+            Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
+            String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");
+            errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
+            if (log.isDebugEnabled()) {
+                log.error(errMsg);
+            }
+        }
+        excelResult.getErrorList().add(errMsg);
+        throw new ExcelAnalysisException(errMsg);
+    }
+
+    @Override
+    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
+        this.headMap = headMap;
+        log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+        log.debug("所有数据解析完成!");
+    }
+
+    @Override
+    public void extra(CellExtra extra, AnalysisContext context) {
+        super.extra(extra, context);
+    }
+
+    @Override
+    public boolean hasNext(AnalysisContext context) {
+        return super.hasNext(context);
+    }
+
+    @Override
+    public ExcelResult<T> getExcelResult() {
+        return excelResult;
+    }
+}

+ 71 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/DefaultExcelResult.java

@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @description DefaultExcelResult
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:39
+ */
+@Setter
+public class DefaultExcelResult <T> implements ExcelResult<T>{
+
+    /**
+     * 数据对象list
+     */
+    private List<T> list;
+
+    /**
+     * 错误信息列表
+     */
+    private List<String> errorList;
+    public DefaultExcelResult() {
+        this.list = new ArrayList<>();
+        this.errorList = new ArrayList<>();
+    }
+
+    public DefaultExcelResult(List<T> list, List<String> errorList) {
+        this.list = list;
+        this.errorList = errorList;
+    }
+
+    public DefaultExcelResult(ExcelResult<T> excelResult) {
+        this.list = excelResult.getList();
+        this.errorList = excelResult.getErrorList();
+    }
+
+    @Override
+    public List<T> getList() {
+        return list;
+    }
+
+    @Override
+    public List<String> getErrorList() {
+        return errorList;
+    }
+
+    @Override
+    public String getAnalysis() {
+        int successCount = list.size();
+        int errorCount = errorList.size();
+        if (successCount == 0) {
+            return "读取失败,未解析到数据";
+        } else {
+            if (errorCount == 0) {
+                return String.format("恭喜您,全部读取成功!共%d条", successCount);
+            } else {
+                return "";
+            }
+        }
+    }
+}

+ 154 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/DropDownOptions.java

@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.eco.vip.common.core.exception.BusinessException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @description DropDownOptions
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 17:37
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class DropDownOptions {
+    /**
+     * 一级下拉所在列index,从0开始算
+     */
+    private int index = 0;
+    /**
+     * 二级下拉所在的index,从0开始算,不能与一级相同
+     */
+    private int nextIndex = 0;
+    /**
+     * 一级下拉所包含的数据
+     */
+    private List<String> options = new ArrayList<>();
+    /**
+     * 二级下拉所包含的数据Map
+     * <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p>
+     */
+    private Map<String, List<String>> nextOptions = new HashMap<>();
+    /**
+     * 分隔符
+     */
+    private static final String DELIMITER = "_";
+
+    /**
+     * 创建只有一级的下拉选
+     */
+    public DropDownOptions(int index, List<String> options) {
+        this.index = index;
+        this.options = options;
+    }
+
+    /**
+     * <h2>创建每个选项可选值</h2>
+     * <p>注意:不能以数字,特殊符号开头,选项中不可以包含任何运算符号</p>
+     *
+     * @param vars 可选值内包含的参数
+     * @return 合规的可选值
+     */
+    public static String createOptionValue(Object... vars) {
+        StringBuilder stringBuffer = new StringBuilder();
+        String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
+        for (int i = 0; i < vars.length; i++) {
+            String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
+            if (!var.matches(regex)) {
+                throw new BusinessException("选项数据不符合规则,仅允许使用中英文字符以及数字");
+            }
+            stringBuffer.append(var);
+            if (i < vars.length - 1) {
+                // 直至最后一个前,都以_作为切割线
+                stringBuffer.append(DELIMITER);
+            }
+        }
+        if (stringBuffer.toString().matches("^\\d_*$")) {
+            throw new BusinessException("禁止以数字开头");
+        }
+        return stringBuffer.toString();
+    }
+
+    /**
+     * 将处理后合理的可选值解析为原始的参数
+     *
+     * @param option 经过处理后的合理的可选项
+     * @return 原始的参数
+     */
+    public static List<String> analyzeOptionValue(String option) {
+        return StrUtil.split(option, DELIMITER, true, true);
+    }
+
+    /**
+     * 创建级联下拉选项
+     *
+     * @param parentList                  父实体可选项原始数据
+     * @param parentIndex                 父下拉选位置
+     * @param sonList                     子实体可选项原始数据
+     * @param sonIndex                    子下拉选位置
+     * @param parentHowToGetIdFunction    父类如何获取唯一标识
+     * @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识
+     * @param howToBuildEveryOption       如何生成下拉选内容
+     * @return 级联下拉选项
+     */
+    public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
+                                                         int parentIndex,
+                                                         List<T> sonList,
+                                                         int sonIndex,
+                                                         Function<T, Number> parentHowToGetIdFunction,
+                                                         Function<T, Number> sonHowToGetParentIdFunction,
+                                                         Function<T, String> howToBuildEveryOption) {
+        DropDownOptions parentLinkSonOptions = new DropDownOptions();
+        // 先创建父类的下拉
+        parentLinkSonOptions.setIndex(parentIndex);
+        parentLinkSonOptions.setOptions(
+                parentList.stream()
+                        .map(howToBuildEveryOption)
+                        .collect(Collectors.toList())
+        );
+        // 提取父-子级联下拉
+        Map<String, List<String>> sonOptions = new HashMap<>();
+        // 父级依据自己的ID分组
+        Map<Number, List<T>> parentGroupByIdMap =
+                parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
+        // 遍历每个子集,提取到Map中
+        sonList.forEach(everySon -> {
+            if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
+                // 找到对应的上级
+                T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
+                // 提取名称和ID作为Key
+                String key = howToBuildEveryOption.apply(parentObj);
+                // Key对应的Value
+                List<String> thisParentSonOptionList;
+                if (sonOptions.containsKey(key)) {
+                    thisParentSonOptionList = sonOptions.get(key);
+                } else {
+                    thisParentSonOptionList = new ArrayList<>();
+                    sonOptions.put(key, thisParentSonOptionList);
+                }
+                // 往Value中添加当前子集选项
+                thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
+            }
+        });
+        parentLinkSonOptions.setNextIndex(sonIndex);
+        parentLinkSonOptions.setNextOptions(sonOptions);
+        return parentLinkSonOptions;
+    }
+}

+ 380 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/ExcelDownHandler.java

@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.EnumUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.metadata.FieldCache;
+import cn.idev.excel.metadata.FieldWrapper;
+import cn.idev.excel.util.ClassUtils;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.DataValidation;
+import org.apache.poi.ss.usermodel.DataValidationConstraint;
+import org.apache.poi.ss.usermodel.DataValidationHelper;
+import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.ss.util.WorkbookUtil;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.eco.vip.common.core.api.IDict;
+import org.eco.vip.common.core.exception.BusinessException;
+import org.eco.vip.common.core.utils.CollUtils;
+import org.eco.vip.common.core.utils.SpringUtils;
+import org.eco.vip.common.core.utils.StrUtils;
+import org.eco.vip.common.core.utils.StreamUtils;
+import org.eco.vip.common.excel.annotation.ExcelDictFormat;
+import org.eco.vip.common.excel.annotation.ExcelEnumFormat;
+import org.eco.vip.common.excel.utils.ExcelUtils;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @description ExcelDownHandler
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 16:15
+ */
+@Slf4j
+public class ExcelDownHandler implements SheetWriteHandler {
+
+    /**
+     * Excel表格中的列名英文 仅为了解析列英文,禁止修改
+     */
+    private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    /**
+     * 单选数据Sheet名
+     */
+    private static final String OPTIONS_SHEET_NAME = "options";
+
+    /**
+     * 联动选择数据Sheet名的头
+     */
+    private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
+    /**
+     * 下拉可选项
+     */
+    private final List<DropDownOptions> dropDownOptions;
+
+    /**
+     * 当前单选进度
+     */
+    private int currentOptionsColumnIndex;
+
+    /**
+     * 当前联动选择进度
+     */
+    private int currentLinkedOptionsSheetIndex;
+
+    public ExcelDownHandler(List<DropDownOptions> options) {
+        this.dropDownOptions = options;
+        this.currentOptionsColumnIndex = 0;
+        this.currentLinkedOptionsSheetIndex = 0;
+    }
+
+    @Override
+    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
+        final Sheet sheet = writeSheetHolder.getSheet();
+        final DataValidationHelper helper = sheet.getDataValidationHelper();
+        final Workbook workbook = writeWorkbookHolder.getWorkbook();
+        final FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder);
+
+        for (Map.Entry<Integer, FieldWrapper> entry : fieldCache.getSortedFieldMap().entrySet()) {
+            final Integer index = entry.getKey();
+            final FieldWrapper wrapper = entry.getValue();
+            final Field field = wrapper.getField();
+            List<String> options = new ArrayList<>();
+            if (field.isAnnotationPresent(ExcelDictFormat.class)) {
+                final ExcelDictFormat dictFormat = field.getDeclaredAnnotation(ExcelDictFormat.class);
+                options = processDictFormat(dictFormat);
+            } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
+                final ExcelEnumFormat enumFormat = field.getDeclaredAnnotation(ExcelEnumFormat.class);
+                List<Object> values = EnumUtil.getFieldValues(enumFormat.enumClass(), enumFormat.textField());
+                options = StreamUtils.toList(values, String::valueOf);
+            }
+            if (CollUtils.isNotEmpty(options)) {
+                // 仅当下拉可选项不为空时执行
+                if (options.size() > 20) {
+                    // 这里限制如果可选项大于20,则使用额外表形式
+                    dropDownWithSheet(helper, workbook, sheet, index, options);
+                } else {
+                    // 否则使用固定值形式
+                    dropDownWithSimple(helper, sheet, index, options);
+                }
+            }
+            if (CollUtil.isEmpty(dropDownOptions)) {
+                return;
+            }
+            dropDownOptions.forEach(everyOptions -> {
+                // 如果传递了下拉框选择器参数
+                if (!everyOptions.getNextOptions().isEmpty()) {
+                    // 当二级选项不为空时,使用额外关联表的形式
+                    dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
+                } else if (everyOptions.getOptions().size() > 10) {
+                    // 当一级选项参数个数大于10,使用额外表的形式
+                    dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+                } else if (!everyOptions.getOptions().isEmpty()) {
+                    // 当一级选项个数不为空,使用默认形式
+                    dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+                }
+            });
+
+        }
+    }
+
+    private List<String> processDictFormat(ExcelDictFormat dictFormat) {
+        return getOptions(dictFormat);
+    }
+
+    private List<String> getOptions(ExcelDictFormat dictFormat) {
+        List<String> options = new ArrayList<>();
+        final String dictType = dictFormat.dictType();
+        final String converterExp = dictFormat.readConverterExp();
+        final String separator = dictFormat.separator();
+        if (StrUtils.isNotBlank(dictType)) {
+            options = getDictTypeOptions(dictType);
+        } else if (StrUtils.isNotBlank(converterExp)) {
+            options = ExcelUtils.listByExp(converterExp, separator);
+        }
+        return options;
+    }
+
+    private List<String> getDictTypeOptions(String dictType) {
+        List<String> options = new ArrayList<>();
+        final IDict dictService = SpringUtils.getBean(IDict.class);
+        // 如果传递了字典名,则依据字典建立下拉
+        Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
+                .orElseThrow(() -> new BusinessException(String.format("字典 %s 不存在", dictType)))
+                .values();
+        if (!values.isEmpty()) {
+            options.addAll(values);
+        }
+        return options;
+    }
+
+    private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
+        // 创建下拉数据表
+        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
+                .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
+        // 将下拉表隐藏
+        workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
+        // 完善纵向的一级选项数据表
+        for (int i = 0; i < value.size(); i++) {
+            int finalI = i;
+            // 获取每一选项行,如果没有则创建
+            Row row = Optional.ofNullable(simpleDataSheet.getRow(i)).orElseGet(() -> simpleDataSheet.createRow(finalI));
+            // 获取本级选项对应的选项列,如果没有则创建
+            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex)).orElseGet(() -> row.createCell(currentOptionsColumnIndex));
+            // 设置值
+            cell.setCellValue(value.get(i));
+        }
+
+        // 创建名称管理器
+        Name name = workbook.createName();
+        // 设置名称管理器的别名
+        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
+        name.setNameName(nameName);
+        // 以纵向第一列创建一级下拉拼接引用位置
+        String function = String.format("%s!$%s$1:$%s$%d", OPTIONS_SHEET_NAME, getExcelColumnName(currentOptionsColumnIndex),
+                getExcelColumnName(currentOptionsColumnIndex), value.size());
+        // 设置名称管理器的引用位置
+        name.setRefersToFormula(function);
+        // 设置数据校验为序列模式,引用的是名称管理器中的别名
+        this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
+        currentOptionsColumnIndex++;
+    }
+
+    /**
+     * 挂载下拉的列,仅限一级选项
+     */
+    private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex, DataValidationConstraint constraint) {
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
+        markDataValidationToSheet(helper, sheet, constraint, addressList);
+    }
+
+    /**
+     * 应用数据校验
+     */
+    private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet, DataValidationConstraint constraint, CellRangeAddressList addressList) {
+        // 数据有效性对象
+        DataValidation dataValidation = helper.createValidation(constraint, addressList);
+        // 处理Excel兼容性问题
+        if (dataValidation instanceof XSSFDataValidation) {
+            // 数据校验
+            dataValidation.setSuppressDropDownArrow(true);
+            // 错误提示
+            dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
+            dataValidation.createErrorBox("提示", "此值与单元格定义数据不一致");
+            dataValidation.setShowErrorBox(true);
+            // 选定提示
+            dataValidation.createPromptBox("填写说明:", "填写内容只能为下拉中数据,其他数据将导致导入失败");
+            dataValidation.setShowPromptBox(true);
+            sheet.addValidationData(dataValidation);
+        } else {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 依据列index获取列名英文 依据列index转换为Excel中的列名英文
+     * 例如第1列,index为0,解析出来为A列
+     * 第27列,index为26,解析为AA列
+     * 第28列,index为27,解析为AB列
+     *
+     * @param columnIndex 列index
+     * @return 列index所在得英文名
+     */
+    private String getExcelColumnName(int columnIndex) {
+        // 26一循环的次数
+        int columnCircleCount = columnIndex / 26;
+        // 26一循环内的位置
+        int thisCircleColumnIndex = columnIndex % 26;
+        // 26一循环的次数大于0,则视为栏名至少两位
+        String columnPrefix = columnCircleCount == 0 ? "" : StrUtils.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
+        // 从26一循环内取对应的栏位名
+        String columnNext = StrUtils.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
+        // 将二者拼接即为最终的栏位名
+        return columnPrefix + columnNext;
+    }
+
+    /**
+     * <h2>额外表格形式的级联下拉框</h2>
+     *
+     * @param options 额外表格形式存储的下拉可选项
+     */
+    private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
+        String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
+        // 创建联动下拉数据表
+        Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
+        // 将下拉表隐藏
+        workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
+        // 完善横向的一级选项数据表
+        List<String> firstOptions = options.getOptions();
+        Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
+
+        // 创建名称管理器
+        Name name = workbook.createName();
+        // 设置名称管理器的别名
+        name.setNameName(linkedOptionsSheetName);
+        // 以横向第一行创建一级下拉拼接引用位置
+        String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
+                linkedOptionsSheetName,
+                getExcelColumnName(0),
+                getExcelColumnName(firstOptions.size())
+        );
+        // 设置名称管理器的引用位置
+        name.setRefersToFormula(firstOptionsFunction);
+        // 设置数据校验为序列模式,引用的是名称管理器中的别名
+        this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
+
+        for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
+            // 先提取主表中一级下拉的列名
+            String firstOptionsColumnName = getExcelColumnName(columIndex);
+            // 一次循环是每一个一级选项
+            int finalI = columIndex;
+            // 本次循环的一级选项值
+            String thisFirstOptionsValue = firstOptions.get(columIndex);
+            // 创建第一行的数据
+            Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
+                    // 如果不存在则创建第一行
+                    .orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
+                    // 第一行当前列
+                    .createCell(columIndex)
+                    // 设置值为当前一级选项值
+                    .setCellValue(thisFirstOptionsValue);
+
+            // 第二行开始,设置第二级别选项参数
+            List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
+            if (CollUtil.isEmpty(secondOptions)) {
+                // 必须保证至少有一个关联选项,否则将导致Excel解析错误
+                secondOptions = Collections.singletonList("暂无_0");
+            }
+
+            // 以该一级选项值创建子名称管理器
+            Name sonName = workbook.createName();
+            // 设置名称管理器的别名
+            sonName.setNameName(thisFirstOptionsValue);
+            // 以第二行该列数据拼接引用位置
+            String sonFunction = String.format("%s!$%s$2:$%s$%d",
+                    linkedOptionsSheetName,
+                    firstOptionsColumnName,
+                    firstOptionsColumnName,
+                    secondOptions.size() + 1
+            );
+            // 设置名称管理器的引用位置
+            sonName.setRefersToFormula(sonFunction);
+            // 数据验证为序列模式,引用到每一个主表中的二级选项位置
+            // 创建子项的名称管理器,只是为了使得Excel可以识别到数据
+            String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
+            for (int i = 0; i < 100; i++) {
+                // 以一级选项对应的主体所在位置创建二级下拉
+                String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
+                // 二级只能主表每一行的每一列添加二级校验
+                markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
+            }
+
+            for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
+                // 从第二行开始填充二级选项
+                int finalRowIndex = rowIndex + 1;
+                int finalColumIndex = columIndex;
+
+                Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
+                        // 没有则创建
+                        .orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
+                Optional
+                        // 在本级一级选项所在的列
+                        .ofNullable(row.getCell(finalColumIndex))
+                        // 不存在则创建
+                        .orElseGet(() -> row.createCell(finalColumIndex))
+                        // 设置二级选项值
+                        .setCellValue(secondOptions.get(rowIndex));
+            }
+        }
+
+        currentLinkedOptionsSheetIndex++;
+    }
+
+    /**
+     * 挂载下拉的列,仅限二级选项
+     */
+    private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex,
+                                          Integer celIndex, DataValidationConstraint constraint) {
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
+        markDataValidationToSheet(helper, sheet, constraint, addressList);
+    }
+
+    /**
+     * <h2>简单下拉框</h2>
+     * 直接将可选项拼接为指定列的数据校验值
+     *
+     * @param celIndex 列index
+     * @param value    下拉选可选值
+     */
+    private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
+        if (ObjectUtil.isEmpty(value)) {
+            return;
+        }
+        this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class)));
+    }
+}

+ 19 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/ExcelListener.java

@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import cn.idev.excel.read.listener.ReadListener;
+
+/**
+ * @description ExcelListener
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:37
+ */
+public interface ExcelListener <T> extends ReadListener<T> {
+    ExcelResult<T> getExcelResult();
+}

+ 32 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/core/ExcelResult.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.core;
+
+
+import java.util.List;
+
+/**
+ * @description ExcelResult
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:37
+ */
+public interface ExcelResult<T> {
+    /**
+     * 对象列表
+     */
+    List<T> getList();
+
+    /**
+     * 错误列表
+     */
+    List<String> getErrorList();
+
+    /**
+     * 导入回执
+     */
+    String getAnalysis();
+}

+ 82 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/strategy/DefaultCellStyleStrategy.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.strategy;
+
+
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
+import cn.idev.excel.write.metadata.style.WriteCellStyle;
+import cn.idev.excel.write.metadata.style.WriteFont;
+import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
+import lombok.Getter;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+
+import java.util.List;
+
+/**
+ * @description DefaultCellStyleStrategy 设置表头和填充内容的样式
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:59
+ */
+public class DefaultCellStyleStrategy extends HorizontalCellStyleStrategy {
+
+    private final WriteCellStyle headWriteCellStyle;
+
+    private final WriteCellStyle contentWriteCellStyle;
+
+    @Getter
+    private final List<Integer> columnIndexes;
+
+    public DefaultCellStyleStrategy(List<Integer> columnIndexes, WriteCellStyle headWriteCellStyle, WriteCellStyle contentWriteCellStyle) {
+        this.columnIndexes = columnIndexes;
+        this.headWriteCellStyle = headWriteCellStyle;
+        this.contentWriteCellStyle = contentWriteCellStyle;
+    }
+
+    // 设置头样式
+    @Override
+    protected void setHeadCellStyle(CellWriteHandlerContext context) {
+        // 获取字体实例
+        WriteFont headWriteFont = new WriteFont();
+        headWriteFont.setFontName("宋体");
+
+        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
+        headWriteFont.setFontHeightInPoints((short) 14);
+        headWriteFont.setBold(false);
+        headWriteFont.setFontName("宋体");
+
+        headWriteCellStyle.setWriteFont(headWriteFont);
+        if (stopProcessing(context)) {
+            return;
+        }
+        WriteCellData<?> cellData = context.getFirstCellData();
+        WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle());
+    }
+
+    // 设置填充数据样式
+    @Override
+    protected void setContentCellStyle(CellWriteHandlerContext context) {
+        WriteFont contentWriteFont = new WriteFont();
+        contentWriteFont.setFontName("宋体");
+        contentWriteFont.setFontHeightInPoints((short) 12);
+        // 设置数据填充后的实线边框
+        contentWriteCellStyle.setWriteFont(contentWriteFont);
+        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
+        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
+        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
+        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
+        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
+        // 垂直居中
+        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        WriteCellData<?> cellData = context.getFirstCellData();
+        WriteCellStyle.merge(contentWriteCellStyle, cellData.getOrCreateStyle());
+    }
+}

+ 87 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/strategy/DefaultColumnWidthStyleStrategy.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.strategy;
+
+
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.Head;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.util.MapUtils;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.style.column.AbstractColumnWidthStyleStrategy;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @description DefaultColumnWidthStyleStrategy
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 16:00
+ */
+public class DefaultColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
+    private static final int MAX_COLUMN_WIDTH = 256;
+
+    private final Map<Integer, Map<Integer, Integer>> cache = MapUtils.newHashMapWithExpectedSize(8);
+
+    @Override
+    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex,
+                                  Boolean isHead) {
+        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
+        if (!needSetWidth) {
+            return;
+        }
+        Map<Integer, Integer> maxColumnWidthMap = cache.computeIfAbsent(writeSheetHolder.getSheetNo(), key -> new HashMap<>(16, 0.75f));
+        Integer columnWidth = getColumnWidth(cellDataList, cell, isHead);
+        if (columnWidth < 0) {
+            return;
+        }
+        if (columnWidth > MAX_COLUMN_WIDTH) {
+            columnWidth = MAX_COLUMN_WIDTH;
+        }
+        Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
+        if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
+            maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
+            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
+        }
+    }
+
+    private Integer getColumnWidth(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
+        if (isHead) {
+            return cell.getStringCellValue().getBytes().length;
+        }
+        WriteCellData<?> cellData = cellDataList.getFirst();
+        CellDataTypeEnum type = cellData.getType();
+        if (type == null) {
+            return -1;
+        }
+        return switch (type) {
+            case STRING -> getStringWidth(cellData.getStringValue());
+            case BOOLEAN -> cellData.getBooleanValue().toString().getBytes().length + 10;
+            case NUMBER -> cellData.getNumberValue().toString().getBytes().length + 10;
+            case DATE -> cellData.getDateValue().toString().getBytes().length + 10;
+            default -> -1;
+        };
+    }
+
+    private int getStringWidth(String str) {
+        if (str == null) {
+            return 0;
+        }
+        int width = str.length();
+        for (char ch : str.toCharArray()) {
+            // 中文字符
+            if (ch >= 0x4E00 && ch <= 0x9FA5) {
+                width++;
+            }
+        }
+        // 添加额外的填充以提高可读性
+        return width + 5;
+    }
+}

+ 268 - 0
eco-common/com-excel/src/main/java/org/eco/vip/common/excel/utils/ExcelUtils.java

@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.excel.utils;
+
+
+import cn.hutool.core.util.IdUtil;
+import cn.idev.excel.FastExcel;
+import cn.idev.excel.FastExcelFactory;
+import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
+import cn.idev.excel.write.metadata.style.WriteCellStyle;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletResponse;
+import org.eco.vip.common.core.utils.FileUtils;
+import org.eco.vip.common.core.utils.StrUtils;
+import org.eco.vip.common.excel.convert.ExcelBigNumberConvert;
+import org.eco.vip.common.excel.core.CellMergeStrategy;
+import org.eco.vip.common.excel.core.DefaultExcelListener;
+import org.eco.vip.common.excel.core.DropDownOptions;
+import org.eco.vip.common.excel.core.ExcelDownHandler;
+import org.eco.vip.common.excel.core.ExcelListener;
+import org.eco.vip.common.excel.core.ExcelResult;
+import org.eco.vip.common.excel.strategy.DefaultCellStyleStrategy;
+import org.eco.vip.common.excel.strategy.DefaultColumnWidthStyleStrategy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @description ExcelUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/21 15:34
+ */
+public class ExcelUtils {
+    private ExcelUtils() {
+        throw new IllegalStateException("ExcelUtils class Illegal");
+    }
+
+    /**
+     * 同步导入(适用于小数据量)
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
+        return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
+    }
+
+    /**
+     * 使用校验监听器 异步导入 同步返回
+     *
+     * @param is         输入流
+     * @param clazz      对象类型
+     * @param isValidate 是否 Validator 检验 默认为是
+     * @return 转换后集合
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
+        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
+        FastExcel.read(is, clazz, listener).sheet().doRead();
+        return listener.getExcelResult();
+    }
+
+    /**
+     * 使用自定义监听器 异步导入 自定义返回
+     *
+     * @param is       输入流
+     * @param clazz    对象类型
+     * @param listener 自定义监听器
+     * @return 转换后集合
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
+        FastExcel.read(is, clazz, listener).sheet().doRead();
+        return listener.getExcelResult();
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static String encodingFilename(String filename) {
+        return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
+    }
+
+    /**
+     * 重置响应体
+     */
+    private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
+        String filename = encodingFilename(sheetName);
+        FileUtils.setAttachmentResponseHeader(response, filename);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     * @param response  响应体
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
+        try {
+            resetResponse(sheetName, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportExcel(list, sheetName, clazz, false, os, null);
+        } catch (IOException e) {
+            throw new RuntimeException("导出Excel异常");
+        }
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     * @param merge     是否合并单元格
+     * @param os        输出流
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
+                                       OutputStream os, List<DropDownOptions> options) {
+        ExcelWriterSheetBuilder builder = FastExcelFactory.write(os, clazz).autoCloseStream(false)
+                // 列宽自动适配
+                .registerWriteHandler(new DefaultColumnWidthStyleStrategy())
+                // 大数值自动转换 防止失真
+                .registerConverter(new ExcelBigNumberConvert())
+                // 表格样式
+                .registerWriteHandler(new DefaultCellStyleStrategy(Arrays.asList(0, 1), new WriteCellStyle(), new WriteCellStyle()))
+                .sheet(sheetName);
+        if (merge) {
+            // 合并处理器
+            builder.registerWriteHandler(new CellMergeStrategy(list, true));
+        }
+        // 添加下拉框操作
+        builder.registerWriteHandler(new ExcelDownHandler(options));
+        builder.doWrite(list);
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     * @param os        输出流
+     * @param options   下拉框选项
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
+        ExcelWriterSheetBuilder builder = FastExcelFactory.write(os, clazz).autoCloseStream(false)
+                // 列宽自动适配
+                .registerWriteHandler(new DefaultColumnWidthStyleStrategy())
+                // 表格样式
+                .registerWriteHandler(new DefaultCellStyleStrategy(Arrays.asList(0, 1), new WriteCellStyle(), new WriteCellStyle()))
+                .sheet(sheetName);
+        // 添加下拉框操作
+        builder.registerWriteHandler(new ExcelDownHandler(options));
+        builder.doWrite(list);
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     * @param os        输出流
+     * @param options   下拉框选项
+     * @param merge     是否合并单元格
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options, boolean merge) {
+        ExcelWriterSheetBuilder builder = FastExcelFactory.write(os, clazz).autoCloseStream(false)
+                // 列宽自动适配
+                .registerWriteHandler(new DefaultColumnWidthStyleStrategy())
+                // 表格样式
+                .registerWriteHandler(new DefaultCellStyleStrategy(Arrays.asList(0, 1), new WriteCellStyle(), new WriteCellStyle()))
+                .sheet(sheetName);
+        if (merge) {
+            // 合并处理器
+            builder.registerWriteHandler(new CellMergeStrategy(list, true));
+        }
+        // 添加下拉框操作
+        builder.registerWriteHandler(new ExcelDownHandler(options));
+        builder.doWrite(list);
+    }
+
+    /**
+     * 将表达式转换为列表。
+     * 该方法根据指定的分隔符,将字符串表达式转换为字符串列表。
+     *
+     * @param converterExp 要转换的字符串表达式
+     * @param separator 用于分隔表达式的分隔符
+     * @return 转换后的字符串列表
+     */
+    public static List<String> listByExp(String converterExp, String separator) {
+        List<String> list = new ArrayList<>();
+        String[] convertSource = converterExp.split(separator);
+        for (String item : convertSource) {
+            String[] itemArray = item.split("=");
+            list.add(itemArray[0]);
+        }
+        return list;
+    }
+
+    /**
+     * 解析导出值 value -> label
+     *
+     * @param propertyValue 参数值
+     * @param converterExp  翻译注解
+     * @param separator     分隔符
+     * @return 解析后值
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator) {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(StrUtils.COMMA);
+        for (String item : convertSource) {
+            String[] itemArray = item.split("=");
+            if (StrUtils.containsAny(propertyValue, separator)) {
+                for (String value : propertyValue.split(separator)) {
+                    if (itemArray[0].equals(value)) {
+                        propertyString.append(itemArray[1]).append(separator);
+                        break;
+                    }
+                }
+            } else {
+                if (itemArray[0].equals(propertyValue)) {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StrUtils.removeSuffix(propertyString.toString(), separator);
+    }
+
+    /**
+     * 反向解析值 label -> value
+     *
+     * @param propertyValue 参数值
+     * @param converterExp  翻译注解
+     * @param separator     分隔符
+     * @return 解析后值
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator) {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(StrUtils.COMMA);
+        for (String item : convertSource) {
+            String[] itemArray = item.split("=");
+            if (StrUtils.containsAny(propertyValue, separator)) {
+                for (String value : propertyValue.split(separator)) {
+                    if (itemArray[1].equals(value)) {
+                        propertyString.append(itemArray[0]).append(separator);
+                        break;
+                    }
+                }
+            } else {
+                if (itemArray[1].equals(propertyValue)) {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StrUtils.removeSuffix(propertyString.toString(), separator);
+    }
+}

+ 23 - 0
eco-common/com-log/pom.xml

@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.eco.vip</groupId>
+        <artifactId>eco-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>com-log</artifactId>
+    <name>${project.artifactId}</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.eco.vip</groupId>
+            <artifactId>com-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eco.vip</groupId>
+            <artifactId>com-security</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 29 - 0
eco-common/com-log/src/main/java/org/eco/vip/common/log/annotation/Log.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.log.annotation;
+
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @description Log
+ *
+ * @author GaoKunW
+ * @date 2025/7/28 17:45
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+    /**
+     * 日志的名称
+     */
+    String value() default "未命名";
+}

+ 108 - 0
eco-common/com-log/src/main/java/org/eco/vip/common/log/aspect/LogAspect.java

@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.log.aspect;
+
+
+import cn.hutool.core.date.StopWatch;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.eco.vip.common.core.utils.JsonUtils;
+import org.eco.vip.common.core.utils.ObjUtils;
+import org.eco.vip.common.core.utils.SpringUtils;
+import org.eco.vip.common.core.utils.StrUtils;
+import org.eco.vip.common.log.annotation.Log;
+import org.eco.vip.common.log.enums.ExeStatusEnum;
+import org.eco.vip.common.log.enums.LogCategoryEnum;
+import org.eco.vip.common.log.event.LogEvent;
+import org.eco.vip.common.log.utils.LogUtils;
+import org.eco.vip.security.utils.LoginHelper;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+/**
+ * @description LogAspect
+ *
+ * @author GaoKunW
+ * @date 2025/7/28 17:46
+ */
+@Slf4j
+@Aspect
+@AutoConfiguration
+public class LogAspect {
+
+    /**
+     * 排除敏感属性字段
+     */
+    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword"};
+
+    /**
+     * 计算操作消耗时间
+     */
+    private static final ThreadLocal<StopWatch> TIME_THREADLOCAL = new ThreadLocal<>();
+
+    /**
+     * 处理请求前执行
+     */
+    @Before(value = "@annotation(controllerLog)")
+    public void boBefore(JoinPoint joinPoint, Log controllerLog) {
+        StopWatch stopWatch = new StopWatch();
+        TIME_THREADLOCAL.set(stopWatch);
+        stopWatch.start();
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "result")
+    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object result) {
+        handleLog(joinPoint, controllerLog, null, result);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object result) {
+        try {
+            LogEvent logEvent = LogUtils.getBasLogEvent();
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            logEvent.setName(controllerLog.value());
+            logEvent.setClassName(className);
+            logEvent.setMethodName(className + StrUtils.DOT + methodName + "()");
+            logEvent.setParamJson(JsonUtils.getArgsJsonString(joinPoint, EXCLUDE_PROPERTIES));
+            logEvent.setResultJson(JsonUtils.toJsonStr(result));
+            logEvent.setOpUserBy(LoginHelper.getUserId());
+            logEvent.setTenantId(LoginHelper.getTenantId());
+            logEvent.setCategory(LogCategoryEnum.OPERATE.getValue());
+            StopWatch stopWatch = TIME_THREADLOCAL.get();
+            stopWatch.stop();
+            logEvent.setCostTime(stopWatch.getTotalTimeMillis());
+            if (ObjUtils.isNotEmpty(e)) {
+                logEvent.setExeStatus(ExeStatusEnum.FAIL.getValue());
+                logEvent.setExeMessage(e.getMessage());
+            }
+            SpringUtils.context().publishEvent(logEvent);
+        } catch (Exception exception) {
+            log.error("日志异常信息: {}", exception.getMessage());
+        } finally {
+            TIME_THREADLOCAL.remove();
+        }
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param exception         异常
+     */
+    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "exception")
+    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception exception) {
+        handleLog(joinPoint, controllerLog, exception, null);
+
+    }
+}

+ 55 - 0
eco-common/com-log/src/main/java/org/eco/vip/common/log/enums/BusinessTypeEnum.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.log.enums;
+
+
+/**
+ * @description BusinessTypeEnum
+ *
+ * @author GaoKunW
+ * @date 2025/7/29 10:39
+ */
+public enum BusinessTypeEnum {
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 新增
+     */
+    INSERT,
+
+    /**
+     * 修改
+     */
+    UPDATE,
+
+    /**
+     * 删除
+     */
+    DELETE,
+
+    /**
+     * 授权
+     */
+    GRANT,
+
+    /**
+     * 导出
+     */
+    EXPORT,
+
+    /**
+     * 导入
+     */
+    IMPORT,
+
+    /**
+     * 清空数据
+     */
+    CLEAN,
+}

+ 31 - 0
eco-common/com-log/src/main/java/org/eco/vip/common/log/enums/ExeStatusEnum.java

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.common.log.enums;
+
+
+import lombok.Getter;
+
+/**
+ * @description ExeStatusEnum
+ *
+ * @author GaoKunW
+ * @date 2025/7/29 10:29
+ */
+@Getter
+public enum ExeStatusEnum {
+
+    /** 成功 */
+    SUCCESS("SUCCESS"),
+
+    /** 失败 */
+    FAIL("FAIL");
+
+    private final String value;
+
+    ExeStatusEnum(String value) {
+        this.value = value;
+    }
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно