carvendy 1 month ago
parent
commit
e04fb94993
100 changed files with 2671 additions and 39430 deletions
  1. 13 0
      build-prod/Dockerfile-bpm
  2. 19 0
      build-prod/Dockerfile-gateway
  3. 41 0
      build-prod/Dockerfile-grape
  4. 15 0
      build-prod/Dockerfile-infra
  5. 22 0
      build-prod/Dockerfile-laboratory
  6. 15 0
      build-prod/Dockerfile-member
  7. 15 0
      build-prod/Dockerfile-mp
  8. 15 0
      build-prod/Dockerfile-mq
  9. 29 0
      build-prod/Dockerfile-pressure
  10. 29 0
      build-prod/Dockerfile-quartz
  11. 25 0
      build-prod/Dockerfile-system
  12. BIN
      build-prod/gb2312.ttf
  13. BIN
      build-prod/gc-excel-lic
  14. 17 0
      build-prod/nginx.conf
  15. 76 0
      build-prod/settings-docker.xml
  16. BIN
      build-prod/songti.ttf
  17. 0 4120
      sql/dm/ruoyi-vue-pro-dm8.sql
  18. 0 4431
      sql/kingbase/ruoyi-vue-pro.sql
  19. 0 218
      sql/mysql/quartz.sql
  20. 0 3529
      sql/mysql/ruoyi-vue-pro.sql
  21. 0 4431
      sql/opengauss/ruoyi-vue-pro.sql
  22. 0 4298
      sql/oracle/ruoyi-vue-pro.sql
  23. 0 4528
      sql/postgresql/ruoyi-vue-pro.sql
  24. 0 11018
      sql/sqlserver/ruoyi-vue-pro.sql
  25. 9 1
      tz-dependencies/pom.xml
  26. 1 0
      tz-framework/pom.xml
  27. 12 0
      tz-framework/tz-common/pom.xml
  28. 1 0
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/enums/UserTypeEnum.java
  29. 12 0
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/pojo/CommonResult.java
  30. 52 0
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/DesensitizationUtils.java
  31. 183 0
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/QRCodeUtil.java
  32. 0 338
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/collection/CollectionUtils.java
  33. 0 165
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/date/DateUtils.java
  34. 24 3
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/io/FileUtils.java
  35. 65 5
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/json/JsonUtils.java
  36. 0 69
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/object/BeanUtils.java
  37. 0 35
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/validation/InEnum.java
  38. 0 28
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/validation/Mobile.java
  39. 0 28
      tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/validation/Telephone.java
  40. 0 35
      tz-framework/tz-spring-boot-starter-biz-data-permission/src/main/java/cn/start/tz/framework/datapermission/core/annotation/DataPermission.java
  41. 0 72
      tz-framework/tz-spring-boot-starter-biz-data-permission/src/main/java/cn/start/tz/framework/datapermission/core/aop/DataPermissionAnnotationInterceptor.java
  42. 0 108
      tz-framework/tz-spring-boot-starter-biz-data-permission/src/test/java/cn/start/tz/framework/datapermission/core/aop/DataPermissionAnnotationInterceptorTest.java
  43. 0 540
      tz-framework/tz-spring-boot-starter-biz-data-permission/src/test/java/cn/start/tz/framework/datapermission/core/db/DataPermissionRuleHandlerTest.java
  44. 0 5
      tz-framework/tz-spring-boot-starter-biz-tenant/pom.xml
  45. 0 7
      tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/cn/start/tz/framework/tenant/config/TzTenantAutoConfiguration.java
  46. 0 18
      tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/cn/start/tz/framework/tenant/core/aop/TenantIgnore.java
  47. 0 14
      tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/cn/start/tz/framework/tenant/core/job/TenantJob.java
  48. 0 275
      tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java
  49. 1 2
      tz-framework/tz-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories
  50. 0 50
      tz-framework/tz-spring-boot-starter-env/src/main/java/cn/start/tz/framework/env/config/EnvEnvironmentPostProcessor.java
  51. 0 22
      tz-framework/tz-spring-boot-starter-excel/src/main/java/cn/start/tz/framework/excel/core/annotations/DictFormat.java
  52. 0 27
      tz-framework/tz-spring-boot-starter-excel/src/main/java/cn/start/tz/framework/excel/core/annotations/ExcelColumnSelect.java
  53. 22 0
      tz-framework/tz-spring-boot-starter-job/src/main/java/cn/start/tz/framework/job/core/JobHandler.java
  54. 163 0
      tz-framework/tz-spring-boot-starter-llm/README.md
  55. 69 0
      tz-framework/tz-spring-boot-starter-llm/pom.xml
  56. 108 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmAutoConfiguration.java
  57. 52 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmClient.java
  58. 109 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmClientFactory.java
  59. 116 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmProperties.java
  60. 92 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmChatRequest.java
  61. 101 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmMessage.java
  62. 72 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmRequest.java
  63. 136 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmResponse.java
  64. 77 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/enums/LlmProviderEnum.java
  65. 77 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/enums/LlmRoleEnum.java
  66. 177 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/provider/openai/OpenAiLlmClient.java
  67. 162 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/provider/qwen/QwenLlmClient.java
  68. 163 0
      tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/provider/zhipu/ZhipuLlmClient.java
  69. 1 0
      tz-framework/tz-spring-boot-starter-llm/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  70. 0 42
      tz-framework/tz-spring-boot-starter-monitor/src/main/java/cn/start/tz/framework/tracer/core/annotation/BizTrace.java
  71. 0 151
      tz-framework/tz-spring-boot-starter-mq/src/main/java/cn/start/tz/framework/mq/redis/config/TzRedisMQConsumerAutoConfiguration.java
  72. 1 0
      tz-framework/tz-spring-boot-starter-mybatis/src/main/java/cn/start/tz/framework/mybatis/core/dataobject/BaseDO.java
  73. 5 0
      tz-framework/tz-spring-boot-starter-mybatis/src/main/java/cn/start/tz/framework/mybatis/core/handler/DefaultDBFieldHandler.java
  74. 0 63
      tz-framework/tz-spring-boot-starter-protection/src/main/java/cn/start/tz/framework/idempotent/core/annotation/Idempotent.java
  75. 0 62
      tz-framework/tz-spring-boot-starter-protection/src/main/java/cn/start/tz/framework/ratelimiter/core/annotation/RateLimiter.java
  76. 0 59
      tz-framework/tz-spring-boot-starter-protection/src/main/java/cn/start/tz/framework/signature/core/annotation/ApiSignature.java
  77. 8 0
      tz-framework/tz-spring-boot-starter-security/pom.xml
  78. 2 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/config/TzSecurityAutoConfiguration.java
  79. 51 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/LoginUser.java
  80. 41 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/enums/LoginUnitTypeEnum.java
  81. 17 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/filter/TokenAuthenticationFilter.java
  82. 34 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/service/MiniAuthService.java
  83. 86 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/service/MiniAuthServiceImpl.java
  84. 7 0
      tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/util/SecurityFrameworkUtils.java
  85. 0 65
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/apilog/core/annotation/ApiAccessLog.java
  86. 0 103
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/apilog/core/interceptor/ApiAccessLogInterceptor.java
  87. 0 28
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/base/annotation/DesensitizeBy.java
  88. 0 40
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/regex/annotation/EmailDesensitize.java
  89. 0 42
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/regex/annotation/RegexDesensitize.java
  90. 0 43
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/BankCardDesensitize.java
  91. 0 43
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java
  92. 0 43
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java
  93. 0 43
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java
  94. 0 43
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/IdCardDesensitize.java
  95. 0 43
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/MobileDesensitize.java
  96. 0 45
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/PasswordDesensitize.java
  97. 0 47
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/SliderDesensitize.java
  98. 5 5
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/web/config/TzWebAutoConfiguration.java
  99. 26 0
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/web/core/util/WebFrameworkUtils.java
  100. 0 0
      tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/xss/core/clean/JsoupXssCleaner.java

+ 13 - 0
build-prod/Dockerfile-bpm

@@ -0,0 +1,13 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+COPY ./tz-module-bpm/tz-module-bpm-biz/target/tz-module-bpm-biz*.jar /srv/tz-module-bpm-biz.jar
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48083
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-bpm-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 19 - 0
build-prod/Dockerfile-gateway

@@ -0,0 +1,19 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk update
+#RUN apk -Uuv add --no-cache ca-certificates tini tzdata && \
+#      ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+COPY ./tz-gateway/target/tz-gateway*.jar /srv/tz-gateway.jar
+
+WORKDIR /srv
+
+# -Xmx512m -Xms256m -XX:MaxMetaspaceSize=256m
+ENV JAVA_OPS=""
+
+EXPOSE 48080
+ENTRYPOINT ["java", "-jar", "/srv/tz-gateway.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 41 - 0
build-prod/Dockerfile-grape

@@ -0,0 +1,41 @@
+#FROM openjdk:17-jdk-alpine
+
+#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk update
+#RUN apk -Uuv add --no-cache ca-certificates tini tzdata ttf-dejavu ttf-droid ttf-freefont ttf-liberation&& \
+#      ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+
+FROM registry.cn-hangzhou.aliyuncs.com/dexdev/common:ubuntu22
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
+    && echo $TZ > /etc/timezone \
+    && apt-get update \
+    && apt-get install -y tzdata language-pack-zh-hans
+
+# 设置环境变量
+ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
+ENV PATH=$JAVA_HOME/bin:$PATH
+ENV TZ=Asia/Shanghai
+
+WORKDIR /srv
+
+ADD build/gc-excel-lic /srv
+RUN mkdir -p /srv/fonts
+ADD build/gb2312.ttf /srv/fonts
+ADD build/songti.ttf /srv/fonts
+
+RUN echo 'export LANG=zh_CN.UTF-8' >> ~/.bashrc && echo 'export LC_ALL=zh_CN.UTF-8' >> ~/bash.bashrc
+#RUN source ~/bash.bashrc
+RUN chmod a+x /srv/gc-excel-lic
+# 授权使用目录
+RUN mkdir -p $HOME/.local/share
+
+
+COPY ./tz-module-pressure/tz-module-pressure-biz/target/tz-module-pressure-biz*.jar /srv/tz-module-pressure-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48099
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-pressure-biz.jar","$JAVA_OPS","--spring.profiles.active=dev"]

+ 15 - 0
build-prod/Dockerfile-infra

@@ -0,0 +1,15 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+COPY ./tz-module-infra/tz-module-infra-biz/target/tz-module-infra-biz*.jar /srv/tz-module-infra-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48082
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-infra-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 22 - 0
build-prod/Dockerfile-laboratory

@@ -0,0 +1,22 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+
+# 设置环境变量
+ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
+ENV PATH=$JAVA_HOME/bin:$PATH
+ENV TZ=Asia/Shanghai
+
+WORKDIR /srv
+
+
+RUN echo 'export LANG=zh_CN.UTF-8' >> ~/.bashrc && echo 'export LANG=zh_CN.UTF-8' >> ~/.profile
+
+# 授权使用目录
+RUN mkdir -p $HOME/.local/share
+COPY ./tz-module-laboratory/tz-module-laboratory-biz/target/tz-module-laboratory-biz*.jar /srv/tz-module-laboratory-biz.jar
+
+ENV JAVA_OPS=""
+
+EXPOSE 48099
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-laboratory-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 15 - 0
build-prod/Dockerfile-member

@@ -0,0 +1,15 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+COPY ./tz-module-member/tz-module-member-biz/target/tz-module-member-biz*.jar /srv/tz-module-member-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48087
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-member-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 15 - 0
build-prod/Dockerfile-mp

@@ -0,0 +1,15 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+COPY ./tz-module-mp/tz-module-mp-biz/target/tz-module-mp-biz*.jar /srv/tz-module-mp-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48086
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-mp-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 15 - 0
build-prod/Dockerfile-mq

@@ -0,0 +1,15 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+COPY ./tz-module-mq/tz-module-mq-biz/target/tz-module-mq-biz*.jar /srv/tz-module-mq-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48001
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-mq-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 29 - 0
build-prod/Dockerfile-pressure

@@ -0,0 +1,29 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+RUN sed -i 's|ports.ubuntu.com/ubuntu-ports|mirrors.aliyun.com/ubuntu-ports|g' /etc/apt/sources.list
+
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends \
+    fontconfig \
+    fonts-dejavu \
+    fonts-liberation \
+    fonts-noto \
+    fonts-noto-cjk \
+    fonts-opensymbol less vim  \
+    && rm -rf /var/lib/apt/lists/*
+
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+WORKDIR /srv
+
+COPY ./tz-module-pressure/tz-module-pressure-biz/target/tz-module-pressure-biz*.jar /srv/tz-module-pressure-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48099
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-pressure-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 29 - 0
build-prod/Dockerfile-quartz

@@ -0,0 +1,29 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+RUN sed -i 's|ports.ubuntu.com/ubuntu-ports|mirrors.aliyun.com/ubuntu-ports|g' /etc/apt/sources.list
+
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends \
+    fontconfig \
+    fonts-dejavu \
+    fonts-liberation \
+    fonts-noto \
+    fonts-noto-cjk \
+    fonts-opensymbol \
+    && rm -rf /var/lib/apt/lists/*
+
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+WORKDIR /srv
+
+COPY ./tz-module-quartz/tz-module-quartz-biz/target/tz-module-quartz-biz*.jar /srv/tz-module-quartz-biz.jar
+
+WORKDIR /srv
+
+ENV JAVA_OPS=""
+
+EXPOSE 48001
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-quartz-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

+ 25 - 0
build-prod/Dockerfile-system

@@ -0,0 +1,25 @@
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM eclipse-temurin:17-jre
+
+
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends less vim  \
+    && rm -rf /var/lib/apt/lists/*
+
+# 设置环境变量
+ENV TZ=Asia/Shanghai
+
+WORKDIR /srv
+
+RUN mkdir -p /srv/fonts
+
+RUN echo 'export LANG=zh_CN.UTF-8' >> ~/.bashrc && echo 'export LC_ALL=zh_CN.UTF-8' >> ~/bash.bashrc
+# 授权使用目录
+RUN mkdir -p $HOME/.local/share
+
+COPY ./tz-module-system/tz-module-system-biz/target/tz-module-system-biz*.jar /srv/tz-module-system-biz.jar
+
+ENV JAVA_OPS=""
+
+EXPOSE 48081
+ENTRYPOINT ["java", "-jar", "/srv/tz-module-system-biz.jar","$JAVA_OPS","--spring.profiles.active=prod"]

BIN
build-prod/gb2312.ttf


BIN
build-prod/gc-excel-lic


+ 17 - 0
build-prod/nginx.conf

@@ -0,0 +1,17 @@
+server {
+    listen 80;
+    # gzip config
+    gzip on;
+    gzip_min_length 1k;
+    gzip_comp_level 9;
+    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
+    gzip_vary on;
+    gzip_disable "MSIE [1-6]\.";
+
+    root /usr/share/nginx/html;
+
+    location / {
+        add_header Cache-Control no-cache;
+        try_files $uri $uri/ /index.html;
+    }
+}

+ 76 - 0
build-prod/settings-docker.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <localRepository>/root/.m2</localRepository>
+
+    <pluginGroups>
+    </pluginGroups>
+
+    <proxies>
+    </proxies>
+
+    <mirrors>
+        <mirror>
+            <id>nexus-aliyun</id>
+            <mirrorOf>*</mirrorOf>
+            <name>Nexus aliyun</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public</url>
+        </mirror>
+    </mirrors>
+    <servers>
+    </servers>
+
+    <profiles>
+        <profile>
+            <id>nexus</id>
+            <repositories>
+                <repository>
+                    <id>maven-public</id>
+                    <url>http://maven-public</url>
+                    <releases><enabled>true</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </repository>
+                <repository>
+                    <id>spring-public</id>
+                    <url>http://spring-public</url>
+                    <releases><enabled>true</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </repository>
+                <repository>
+                    <id>3rd-releases</id>
+                    <url>http://3rd-releases</url>
+                    <releases><enabled>true</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </repository>
+            </repositories>
+            <pluginRepositories>
+                <pluginRepository>
+                    <id>maven-public</id>
+                    <url>http://maven-public</url>
+                    <releases><enabled>true</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </pluginRepository>
+                <pluginRepository>
+                    <id>spring-public</id>
+                    <url>http://spring-public</url>
+                    <releases><enabled>true</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </pluginRepository>
+                <pluginRepository>
+                    <id>3rd-releases</id>
+                    <url>http://3rd-releases</url>
+                    <releases><enabled>true</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </pluginRepository>
+            </pluginRepositories>
+        </profile>
+    </profiles>
+
+    <activeProfiles>
+        <activeProfile>nexus</activeProfile>
+    </activeProfiles>
+
+</settings>

BIN
build-prod/songti.ttf


File diff suppressed because it is too large
+ 0 - 4120
sql/dm/ruoyi-vue-pro-dm8.sql


File diff suppressed because it is too large
+ 0 - 4431
sql/kingbase/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 0 - 218
sql/mysql/quartz.sql


File diff suppressed because it is too large
+ 0 - 3529
sql/mysql/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 0 - 4431
sql/opengauss/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 0 - 4298
sql/oracle/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 0 - 4528
sql/postgresql/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 0 - 11018
sql/sqlserver/ruoyi-vue-pro.sql


+ 9 - 1
tz-dependencies/pom.xml

@@ -79,7 +79,7 @@
         <justauth.version>2.0.5</justauth.version>
         <jimureport.version>1.8.1</jimureport.version>
         <weixin-java.version>4.7.5.B</weixin-java.version>
-        <gcexcel.version>8.0.6</gcexcel.version>
+        <gcexcel.version>8.2.5</gcexcel.version>
 <!--        <gcexcel.version>8.1.4</gcexcel.version>-->
         <okhttp.version>4.12.0</okhttp.version>
 <!--        <spring-security.version>6.4.4</spring-security.version>-->
@@ -90,6 +90,8 @@
         <xstream.version>1.4.21</xstream.version>
         <weixin-java-miniapp.version>4.7.5.B</weixin-java-miniapp.version>
 <!--        <weixin-java-miniapp.version>4.7.0</weixin-java-miniapp.version>-->
+
+        <itextpdf.version>5.5.13.3</itextpdf.version>
     </properties>
 
     <dependencyManagement>
@@ -798,6 +800,12 @@
                 <artifactId>core</artifactId>
                 <version>3.5.3</version>
             </dependency>
+
+            <dependency>
+                <groupId>com.itextpdf</groupId>
+                <artifactId>itextpdf</artifactId>
+                <version>${itextpdf.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 1 - 0
tz-framework/pom.xml

@@ -31,6 +31,7 @@
         <module>tz-spring-boot-starter-biz-tenant</module>
         <module>tz-spring-boot-starter-biz-data-permission</module>
         <module>tz-spring-boot-starter-biz-ip</module>
+        <module>tz-spring-boot-starter-llm</module>
     </modules>
 
     <artifactId>tz-framework</artifactId>

+ 12 - 0
tz-framework/tz-common/pom.xml

@@ -144,6 +144,18 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.5.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.5.1</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 1 - 0
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/enums/UserTypeEnum.java

@@ -18,6 +18,7 @@ public enum UserTypeEnum implements ArrayValuable<Integer> {
     ADMIN(2, "管理员"), // 面向 b 端,管理后台
      // 面向 b 端,管理后台
     APP_CLIENT(3, "APP客户端"),
+    MP_CLIENT(5, "小程序个人用户"),
 
     PLATFORM_USE_UNIT(10, "服务平台使用单位"),
     PLATFORM_MAKE_UNIT(11, "服务平台制造单位"),

+ 12 - 0
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/pojo/CommonResult.java

@@ -77,6 +77,18 @@ public class CommonResult<T> implements Serializable {
         return result;
     }
 
+    /**
+     * 返回错误
+     */
+    public static <T> CommonResult<T> error(Integer code, String message, T data) {
+        Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
+        CommonResult<T> result = new CommonResult<>();
+        result.code = code;
+        result.msg = message;
+        result.data = data;
+        return result;
+    }
+
     public static boolean isSuccess(Integer code) {
         return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
     }

+ 52 - 0
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/DesensitizationUtils.java

@@ -0,0 +1,52 @@
+package cn.start.tz.framework.common.util;
+
+/**
+ * 脱敏工具类
+ *
+ * @author 芋道源码
+ */
+public class DesensitizationUtils {
+
+    /**
+     * 中文姓名脱敏处理
+     * 规则:保留第一个字符,其余用*替换
+     * 例如:张三 -> 张*
+     *      刘子豪 -> 刘**
+     *      王小明 -> 王**
+     * @param chineseName 中文姓名
+     * @return 脱敏后的姓名
+     */
+    public static String desensitizeChineseName(String chineseName) {
+        if (chineseName == null || chineseName.trim().isEmpty()) {
+            return chineseName;
+        }
+
+        chineseName = chineseName.trim();
+
+        // 如果姓名长度为1,直接返回
+        if (chineseName.length() == 1) {
+            return chineseName;
+        }
+
+        // 如果姓名长度为2,保留第一个字符,第二个用*替换
+        if (chineseName.length() == 2) {
+            return chineseName.charAt(0) + "*";
+        }
+
+        // 手机号
+        if (chineseName.length() == 11) {
+            return chineseName.substring(0, 3) + "****" + chineseName.substring(7);
+        }
+
+        // 如果姓名长度大于等于3,保留第一个字符,其余用*替换
+        StringBuilder sb = new StringBuilder();
+        sb.append(chineseName.charAt(0));
+        sb.append("*");
+
+        int length = chineseName.length();
+        sb.append(chineseName.charAt(length - 1));
+
+        return sb.toString();
+    }
+
+}

+ 183 - 0
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/QRCodeUtil.java

@@ -0,0 +1,183 @@
+package cn.start.tz.framework.common.util;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 二维码生成工具类
+ */
+public class QRCodeUtil {
+
+	// 默认配置
+	private static final int DEFAULT_WIDTH = 300;
+	private static final int DEFAULT_HEIGHT = 300;
+	private static final String DEFAULT_FORMAT = "png";
+	private static final String CHARSET = "UTF-8";
+
+	/**
+	 * 生成二维码并保存到文件
+	 */
+	public static void generateQRCodeToFile(String content, String filePath) {
+		generateQRCodeToFile(content, filePath, DEFAULT_WIDTH, DEFAULT_HEIGHT, null);
+	}
+
+	/**
+	 * 生成二维码并保存到文件(可带logo)
+	 */
+	public static void generateQRCodeToFile(String content, String filePath, int width, int height, String logoPath) {
+		try {
+			BufferedImage image = generateQRCodeImage(content, width, height, logoPath);
+			File file = new File(filePath);
+			ImageIO.write(image, DEFAULT_FORMAT, file);
+		} catch (Exception e) {
+			throw new RuntimeException("生成二维码失败", e);
+		}
+	}
+
+	/**
+	 * 生成二维码图片对象
+	 */
+	public static BufferedImage generateQRCodeImage(String content, int width, int height, String logoPath) {
+		try {
+			Map<EncodeHintType, Object> hints = getHints();
+			BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
+			BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
+
+			// 如果提供了logo路径,尝试添加logo
+			if (logoPath != null && !logoPath.trim().isEmpty()) {
+				image = addLogoToImage(image, logoPath);
+			}
+			return image;
+		} catch (Exception e) {
+			throw new RuntimeException("生成二维码失败", e);
+		}
+	}
+
+	/**
+	 * 生成二维码字节数组(便于上传到服务器)
+	 */
+	public static byte[] generateQRCodeToBytes(String content) {
+		return generateQRCodeToBytes(content, DEFAULT_WIDTH, DEFAULT_HEIGHT, null);
+	}
+
+	/**
+	 * 生成二维码字节数组(可带logo)
+	 */
+	public static byte[] generateQRCodeToBytes(String content, int width, int height, String logoPath) {
+		try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+			BufferedImage image = generateQRCodeImage(content, width, height, logoPath);
+			ImageIO.write(image, DEFAULT_FORMAT, outputStream);
+			return outputStream.toByteArray();
+		} catch (Exception e) {
+			throw new RuntimeException("生成二维码失败", e);
+		}
+	}
+
+	/**
+	 * 生成Base64格式的二维码
+	 */
+	public static String generateQRCodeToBase64(String content) {
+		return generateQRCodeToBase64(content, null);
+	}
+
+	/**
+	 * 生成Base64格式的二维码(可带logo)
+	 */
+	public static String generateQRCodeToBase64(String content, String logoPath) {
+		return generateQRCodeToBase64(content, DEFAULT_WIDTH, DEFAULT_HEIGHT, logoPath);
+	}
+
+	/**
+	 * 生成Base64格式的二维码(可带logo)
+	 */
+	public static String generateQRCodeToBase64(String content, int width, int height, String logoPath) {
+		try {
+			byte[] imageBytes = generateQRCodeToBytes(content, width, height, logoPath);
+			return "data:image/png;base64," + Base64.getEncoder().encodeToString(imageBytes);
+		} catch (Exception e) {
+			throw new RuntimeException("生成二维码失败", e);
+		}
+	}
+
+	/**
+	 * 添加logo到图片
+	 */
+	private static BufferedImage addLogoToImage(BufferedImage qrImage, String logoPath) {
+		try {
+			File logoFile = new File(logoPath);
+			if (!logoFile.exists()) {
+				System.err.println("Logo文件不存在: " + logoPath);
+				return qrImage;
+			}
+
+			BufferedImage logoImage = ImageIO.read(logoFile);
+			if (logoImage == null) {
+				System.err.println("无法读取logo文件: " + logoPath);
+				return qrImage;
+			}
+
+			Graphics2D graphics = qrImage.createGraphics();
+
+			// 设置渲染质量
+			graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+			graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+
+			// 计算logo尺寸(二维码大小的1/5到1/4)
+			int logoWidth = Math.min(qrImage.getWidth() / 5, logoImage.getWidth());
+			int logoHeight = Math.min(qrImage.getHeight() / 5, logoImage.getHeight());
+
+			// 保持logo宽高比
+			double aspectRatio = (double) logoImage.getWidth() / logoImage.getHeight();
+			if (aspectRatio > 1) {
+				logoHeight = (int) (logoWidth / aspectRatio);
+			} else {
+				logoWidth = (int) (logoHeight * aspectRatio);
+			}
+
+			// 计算logo位置(居中)
+			int x = (qrImage.getWidth() - logoWidth) / 2;
+			int y = (qrImage.getHeight() - logoHeight) / 2;
+
+			// 绘制白色背景(可选)
+			int padding = 2;
+			graphics.setColor(Color.WHITE);
+			graphics.fillRoundRect(x - padding, y - padding,
+					logoWidth + 2 * padding, logoHeight + 2 * padding, 10, 10);
+
+			// 绘制logo
+			graphics.drawImage(logoImage, x, y, logoWidth, logoHeight, null);
+			graphics.dispose();
+
+			return qrImage;
+		} catch (Exception e) {
+			System.err.println("添加logo失败: " + e.getMessage());
+			// 返回原始二维码图片
+			return qrImage;
+		}
+	}
+
+	/**
+	 * 获取二维码配置(增强纠错能力以支持logo)
+	 */
+	private static Map<EncodeHintType, Object> getHints() {
+		Map<EncodeHintType, Object> hints = new HashMap<>();
+		hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
+		// 使用较高的纠错级别,以支持logo覆盖
+		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+		hints.put(EncodeHintType.MARGIN, 1);
+		return hints;
+	}
+}

+ 0 - 338
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/collection/CollectionUtils.java

@@ -1,338 +0,0 @@
-package cn.start.tz.framework.common.util.collection;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.util.ArrayUtil;
-import cn.start.tz.framework.common.pojo.PageResult;
-import com.google.common.collect.ImmutableMap;
-
-import java.util.*;
-import java.util.function.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.util.Arrays.asList;
-
-/**
- * Collection 工具类
- *
- * @author 芋道源码
- */
-public class CollectionUtils {
-
-    public static boolean containsAny(Object source, Object... targets) {
-        return asList(targets).contains(source);
-    }
-
-    public static boolean isAnyEmpty(Collection<?>... collections) {
-        return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
-    }
-
-    public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
-        return from.stream().anyMatch(predicate);
-    }
-
-    public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return from.stream().filter(predicate).collect(Collectors.toList());
-    }
-
-    public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return distinct(from, keyMapper, (t1, t2) -> t1);
-    }
-
-    public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
-    }
-
-    public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
-        if (ArrayUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return convertList(Arrays.asList(from), func);
-    }
-
-    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
-    }
-
-    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
-    }
-
-    public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
-        if (ArrayUtil.isEmpty(from)) {
-            return new PageResult<>(from.getTotal());
-        }
-        return new PageResult<>(convertList(from.getList(), func), from.getTotal());
-    }
-
-    public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
-                                                      Function<T, ? extends Stream<? extends U>> func) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
-    }
-
-    public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
-                                                         Function<? super T, ? extends U> mapper,
-                                                         Function<U, ? extends Stream<? extends R>> func) {
-        if (CollUtil.isEmpty(from)) {
-            return new ArrayList<>();
-        }
-        return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
-    }
-
-    public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
-        return map.values()
-                .stream()
-                .flatMap(List::stream)
-                .collect(Collectors.toList());
-    }
-
-    public static <T> Set<T> convertSet(Collection<T> from) {
-        return convertSet(from, v -> v);
-    }
-
-    public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashSet<>();
-        }
-        return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
-    }
-
-    public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashSet<>();
-        }
-        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
-    }
-
-    public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
-    }
-
-    public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
-                                                    Function<T, ? extends Stream<? extends U>> func) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashSet<>();
-        }
-        return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
-    }
-
-    public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
-                                                       Function<? super T, ? extends U> mapper,
-                                                       Function<U, ? extends Stream<? extends R>> func) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashSet<>();
-        }
-        return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
-    }
-
-    public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return convertMap(from, keyFunc, Function.identity());
-    }
-
-    public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
-        if (CollUtil.isEmpty(from)) {
-            return supplier.get();
-        }
-        return convertMap(from, keyFunc, Function.identity(), supplier);
-    }
-
-    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
-    }
-
-    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
-    }
-
-    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
-        if (CollUtil.isEmpty(from)) {
-            return supplier.get();
-        }
-        return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
-    }
-
-    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
-    }
-
-    public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
-    }
-
-    public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return from.stream()
-                .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
-    }
-
-    // 暂时没想好名字,先以 2 结尾噶
-    public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return new HashMap<>();
-        }
-        return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
-    }
-
-    public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return Collections.emptyMap();
-        }
-        ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
-        from.forEach(item -> builder.put(keyFunc.apply(item), item));
-        return builder.build();
-    }
-
-    /**
-     * 对比老、新两个列表,找出新增、修改、删除的数据
-     *
-     * @param oldList  老列表
-     * @param newList  新列表
-     * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
-     *                 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
-     * @return [新增列表、修改列表、删除列表]
-     */
-    public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
-                                             BiFunction<T, T, Boolean> sameFunc) {
-        List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
-        List<T> updateList = new ArrayList<>();
-        List<T> deleteList = new ArrayList<>();
-
-        // 通过以 oldList 为主遍历,找出 updateList 和 deleteList
-        for (T oldObj : oldList) {
-            // 1. 寻找是否有匹配的
-            T foundObj = null;
-            for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
-                T newObj = iterator.next();
-                // 1.1 不匹配,则直接跳过
-                if (!sameFunc.apply(oldObj, newObj)) {
-                    continue;
-                }
-                // 1.2 匹配,则移除,并结束寻找
-                iterator.remove();
-                foundObj = newObj;
-                break;
-            }
-            // 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
-            if (foundObj != null) {
-                updateList.add(foundObj);
-            } else {
-                deleteList.add(oldObj);
-            }
-        }
-        return asList(createList, updateList, deleteList);
-    }
-
-    public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
-        return org.springframework.util.CollectionUtils.containsAny(source, candidates);
-    }
-
-    public static <T> T getFirst(List<T> from) {
-        return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
-    }
-
-    public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
-        return findFirst(from, predicate, Function.identity());
-    }
-
-    public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
-        if (CollUtil.isEmpty(from)) {
-            return null;
-        }
-        return from.stream().filter(predicate).findFirst().map(func).orElse(null);
-    }
-
-    public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return null;
-        }
-        assert !from.isEmpty(); // 断言,避免告警
-        T t = from.stream().max(Comparator.comparing(valueFunc)).get();
-        return valueFunc.apply(t);
-    }
-
-    public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return null;
-        }
-        assert from.size() > 0; // 断言,避免告警
-        T t = from.stream().min(Comparator.comparing(valueFunc)).get();
-        return valueFunc.apply(t);
-    }
-
-    public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) {
-        if (CollUtil.isEmpty(from)) {
-            return null;
-        }
-        assert from.size() > 0; // 断言,避免告警
-        return from.stream().min(Comparator.comparing(valueFunc)).get();
-    }
-
-    public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
-                                                                     BinaryOperator<V> accumulator) {
-        return getSumValue(from, valueFunc, accumulator, null);
-    }
-
-    public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
-                                                                     BinaryOperator<V> accumulator, V defaultValue) {
-        if (CollUtil.isEmpty(from)) {
-            return defaultValue;
-        }
-        assert !from.isEmpty(); // 断言,避免告警
-        return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
-    }
-
-    public static <T> void addIfNotNull(Collection<T> coll, T item) {
-        if (item == null) {
-            return;
-        }
-        coll.add(item);
-    }
-
-    public static <T> Collection<T> singleton(T obj) {
-        return obj == null ? Collections.emptyList() : Collections.singleton(obj);
-    }
-
-    public static <T> List<T> newArrayList(List<List<T>> list) {
-        return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
-    }
-
-}

+ 0 - 165
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/date/DateUtils.java

@@ -1,165 +0,0 @@
-package cn.start.tz.framework.common.util.date;
-
-import cn.hutool.core.date.LocalDateTimeUtil;
-
-import java.time.*;
-import java.util.Calendar;
-import java.util.Date;
-
-/**
- * 时间工具类
- *
- * @author 芋道源码
- */
-public class DateUtils {
-
-    /**
-     * 时区 - 默认
-     */
-    public static final String TIME_ZONE_DEFAULT = "GMT+8";
-
-    /**
-     * 秒转换成毫秒
-     */
-    public static final long SECOND_MILLIS = 1000;
-
-    public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
-
-    public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
-
-    /**
-     * 将 LocalDateTime 转换成 Date
-     *
-     * @param date LocalDateTime
-     * @return LocalDateTime
-     */
-    public static Date of(LocalDateTime date) {
-        if (date == null) {
-            return null;
-        }
-        // 将此日期时间与时区相结合以创建 ZonedDateTime
-        ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
-        // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
-        Instant instant = zonedDateTime.toInstant();
-        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
-        return Date.from(instant);
-    }
-
-    /**
-     * 将 Date 转换成 LocalDateTime
-     *
-     * @param date Date
-     * @return LocalDateTime
-     */
-    public static LocalDateTime of(Date date) {
-        if (date == null) {
-            return null;
-        }
-        // 转为时间戳
-        Instant instant = date.toInstant();
-        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
-        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
-    }
-
-    public static Date addTime(Duration duration) {
-        return new Date(System.currentTimeMillis() + duration.toMillis());
-    }
-
-    public static boolean isExpired(LocalDateTime time) {
-        LocalDateTime now = LocalDateTime.now();
-        return now.isAfter(time);
-    }
-
-    /**
-     * 创建指定时间
-     *
-     * @param year  年
-     * @param mouth 月
-     * @param day   日
-     * @return 指定时间
-     */
-    public static Date buildTime(int year, int mouth, int day) {
-        return buildTime(year, mouth, day, 0, 0, 0);
-    }
-
-    /**
-     * 创建指定时间
-     *
-     * @param year   年
-     * @param mouth  月
-     * @param day    日
-     * @param hour   小时
-     * @param minute 分钟
-     * @param second 秒
-     * @return 指定时间
-     */
-    public static Date buildTime(int year, int mouth, int day,
-                                 int hour, int minute, int second) {
-        Calendar calendar = Calendar.getInstance();
-        calendar.set(Calendar.YEAR, year);
-        calendar.set(Calendar.MONTH, mouth - 1);
-        calendar.set(Calendar.DAY_OF_MONTH, day);
-        calendar.set(Calendar.HOUR_OF_DAY, hour);
-        calendar.set(Calendar.MINUTE, minute);
-        calendar.set(Calendar.SECOND, second);
-        calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
-        return calendar.getTime();
-    }
-
-    public static Date max(Date a, Date b) {
-        if (a == null) {
-            return b;
-        }
-        if (b == null) {
-            return a;
-        }
-        return a.compareTo(b) > 0 ? a : b;
-    }
-
-    public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
-        if (a == null) {
-            return b;
-        }
-        if (b == null) {
-            return a;
-        }
-        return a.isAfter(b) ? a : b;
-    }
-
-    /**
-     * 是否今天
-     *
-     * @param date 日期
-     * @return 是否
-     */
-    public static boolean isToday(LocalDateTime date) {
-        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
-    }
-
-    /**
-     * 是否昨天
-     *
-     * @param date 日期
-     * @return 是否
-     */
-    public static boolean isYesterday(LocalDateTime date) {
-        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
-    }
-
-    public static long getCurEndTime() {
-        // 获取带时区的当前时间(使用系统默认时区)
-        ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
-
-        // 计算当天结束时间(次日00:00:00)
-        ZonedDateTime midnight = now.toLocalDate()
-                .plusDays(1)
-                .atStartOfDay(ZoneId.systemDefault());
-
-        // 计算时间差并获取秒数
-        long secondsRemaining = Duration.between(now, midnight).getSeconds();
-
-        //System.out.println("距离今日24点还剩:" + secondsRemaining + "秒");
-        return secondsRemaining;
-    }
-
-}

+ 24 - 3
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/io/FileUtils.java

@@ -10,6 +10,8 @@ import lombok.SneakyThrows;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 
 /**
  * 文件工具类
@@ -65,20 +67,39 @@ public class FileUtils {
 
     /**
      * 生成文件路径
+     * 路径格式: yyyy-MM/dd/文件名.扩展名
      *
      * @param content      文件内容
      * @param originalName 原始文件名
-     * @return path,唯一不可重复
+     * @return path,格式为 yyyy-MM/dd/文件名.扩展名
      */
     public static String generatePath(byte[] content, String originalName) {
+        // 生成日期文件夹路径: yyyy-MM/dd/
+        LocalDate now = LocalDate.now();
+        String datePath = now.format(DateTimeFormatter.ofPattern("yyyy-MM")) +
+                         "/" +
+                         now.format(DateTimeFormatter.ofPattern("dd")) +
+                         "/";
+
+        // 使用SHA-256的前16位,保证足够唯一性但大幅缩短长度
         String sha256Hex = DigestUtil.sha256Hex(content);
+        String shortHash = sha256Hex.substring(0, 16);
+
+        // 使用短UUID(8位)+ 时间戳后6位,确保唯一性
+        String shortUuid = IdUtil.fastSimpleUUID().substring(0, 8);
+        String timestamp = String.valueOf(System.currentTimeMillis()).substring(7); // 取时间戳后6位
+        String uniqueSuffix = shortUuid + timestamp;
+
+        String fileName = shortHash + uniqueSuffix;
+
         // 情况一:如果存在 name,则优先使用 name 的后缀
         if (StrUtil.isNotBlank(originalName)) {
             String extName = FileNameUtil.extName(originalName);
-            return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
+            String fileNameWithExt = StrUtil.isBlank(extName) ? fileName : fileName + "." + extName;
+            return datePath + fileNameWithExt;
         }
         // 情况二:基于 content 计算
-        return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
+        return datePath + fileName + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
     }
 
 }

+ 65 - 5
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/json/JsonUtils.java

@@ -385,11 +385,7 @@ public class JsonUtils {
         }
 
         // 空数组检查
-        if (node.isArray() && node.size() == 0) {
-            return true;
-        }
-
-        return false;
+        return node.isArray() && node.size() == 0;
     }
 
 
@@ -416,9 +412,73 @@ public class JsonUtils {
                     JsonNode element = arrayNode.get(i);
                     if (element.isObject()) {
                         setNullValuesToDefault((ObjectNode) element); // 递归处理数组中的对象
+                    } else if (element.isNull() || (element.isTextual() && element.asText().isEmpty())) {
+                        // 处理数组中的 null 或空字符串,替换为 "—"
+                        arrayNode.set(i, objectMapper.getNodeFactory().textNode("—"));
                     }
+                    // 其他类型(数字、布尔值等)保持原值
                 }
             }
         }
     }
+
+    /**
+     * 合并json 字段相同时json2会覆盖json1
+     * @param json1
+     * @param json2
+     * @return
+     * @throws Exception
+     */
+    public static String mergeJson(String json1, String json2) throws Exception {
+        ObjectMapper mapper = new ObjectMapper();
+
+        // 解析两个JSON字符串为JsonNode
+        JsonNode node1 = mapper.readTree(json1);
+        JsonNode node2 = mapper.readTree(json2);
+
+        // 合并两个JsonNode
+        JsonNode mergedNode = mergeNodes(node1, node2);
+
+        // 将合并后的JsonNode转换回JSON字符串
+        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mergedNode);
+    }
+
+    private static JsonNode mergeNodes(JsonNode mainNode, JsonNode updateNode) {
+        // 如果任意一个节点为空,返回另一个节点
+        if (mainNode.isNull()) {
+            return updateNode;
+        }
+        if (updateNode.isNull()) {
+            return mainNode;
+        }
+
+        // 如果不是对象节点,直接返回更新节点(覆盖)
+        if (!mainNode.isObject() || !updateNode.isObject()) {
+            return updateNode;
+        }
+
+        // 创建合并后的对象节点
+        ObjectNode mergedNode = ((ObjectNode) mainNode).deepCopy();
+
+        // 遍历更新节点的所有字段
+        Iterator<Map.Entry<String, JsonNode>> fields = updateNode.fields();
+        while (fields.hasNext()) {
+            Map.Entry<String, JsonNode> entry = fields.next();
+            String fieldName = entry.getKey();
+            JsonNode updateValue = entry.getValue();
+
+            // 如果主节点中也存在该字段,递归合并
+            if (mergedNode.has(fieldName)) {
+                JsonNode mainValue = mergedNode.get(fieldName);
+                // 递归合并子节点
+                JsonNode mergedValue = mergeNodes(mainValue, updateValue);
+                mergedNode.set(fieldName, mergedValue);
+            } else {
+                // 如果主节点中不存在该字段,直接添加
+                mergedNode.set(fieldName, updateValue);
+            }
+        }
+
+        return mergedNode;
+    }
 }

+ 0 - 69
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/util/object/BeanUtils.java

@@ -1,69 +0,0 @@
-package cn.start.tz.framework.common.util.object;
-
-import cn.hutool.core.bean.BeanUtil;
-import cn.start.tz.framework.common.pojo.PageResult;
-import cn.start.tz.framework.common.util.collection.CollectionUtils;
-
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Bean 工具类
- *
- * 1. 默认使用 {@link BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
- * 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
- *
- * @author 芋道源码
- */
-public class BeanUtils {
-
-    public static <T> T toBean(Object source, Class<T> targetClass) {
-        return BeanUtil.toBean(source, targetClass);
-    }
-
-    public static <T> T toBean(Object source, Class<T> targetClass, Consumer<T> peek) {
-        T target = toBean(source, targetClass);
-        if (target != null) {
-            peek.accept(target);
-        }
-        return target;
-    }
-
-    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
-        if (source == null) {
-            return null;
-        }
-        return CollectionUtils.convertList(source, s -> toBean(s, targetType));
-    }
-
-    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType, Consumer<T> peek) {
-        List<T> list = toBean(source, targetType);
-        if (list != null) {
-            list.forEach(peek);
-        }
-        return list;
-    }
-
-    public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
-        return toBean(source, targetType, null);
-    }
-
-    public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType, Consumer<T> peek) {
-        if (source == null) {
-            return null;
-        }
-        List<T> list = toBean(source.getList(), targetType);
-        if (peek != null) {
-            list.forEach(peek);
-        }
-        return new PageResult<>(list, source.getTotal());
-    }
-
-    public static void copyProperties(Object source, Object target) {
-        if (source == null || target == null) {
-            return;
-        }
-        BeanUtil.copyProperties(source, target, false);
-    }
-
-}

+ 0 - 35
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/validation/InEnum.java

@@ -1,35 +0,0 @@
-package cn.start.tz.framework.common.validation;
-
-import cn.start.tz.framework.common.core.ArrayValuable;
-import jakarta.validation.Constraint;
-import jakarta.validation.Payload;
-
-import java.lang.annotation.*;
-
-@Target({
-        ElementType.METHOD,
-        ElementType.FIELD,
-        ElementType.ANNOTATION_TYPE,
-        ElementType.CONSTRUCTOR,
-        ElementType.PARAMETER,
-        ElementType.TYPE_USE
-})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Constraint(
-        validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
-)
-public @interface InEnum {
-
-    /**
-     * @return 实现 ArrayValuable 接口的类
-     */
-    Class<? extends ArrayValuable<?>> value();
-
-    String message() default "必须在指定范围 {value}";
-
-    Class<?>[] groups() default {};
-
-    Class<? extends Payload>[] payload() default {};
-
-}

+ 0 - 28
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/validation/Mobile.java

@@ -1,28 +0,0 @@
-package cn.start.tz.framework.common.validation;
-
-import jakarta.validation.Constraint;
-import jakarta.validation.Payload;
-import java.lang.annotation.*;
-
-@Target({
-        ElementType.METHOD,
-        ElementType.FIELD,
-        ElementType.ANNOTATION_TYPE,
-        ElementType.CONSTRUCTOR,
-        ElementType.PARAMETER,
-        ElementType.TYPE_USE
-})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Constraint(
-        validatedBy = MobileValidator.class
-)
-public @interface Mobile {
-
-    String message() default "手机号格式不正确";
-
-    Class<?>[] groups() default {};
-
-    Class<? extends Payload>[] payload() default {};
-
-}

+ 0 - 28
tz-framework/tz-common/src/main/java/cn/start/tz/framework/common/validation/Telephone.java

@@ -1,28 +0,0 @@
-package cn.start.tz.framework.common.validation;
-
-import jakarta.validation.Constraint;
-import jakarta.validation.Payload;
-import java.lang.annotation.*;
-
-@Target({
-        ElementType.METHOD,
-        ElementType.FIELD,
-        ElementType.ANNOTATION_TYPE,
-        ElementType.CONSTRUCTOR,
-        ElementType.PARAMETER,
-        ElementType.TYPE_USE
-})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Constraint(
-        validatedBy = TelephoneValidator.class
-)
-public @interface Telephone {
-
-    String message() default "电话格式不正确";
-
-    Class<?>[] groups() default {};
-
-    Class<? extends Payload>[] payload() default {};
-
-}

+ 0 - 35
tz-framework/tz-spring-boot-starter-biz-data-permission/src/main/java/cn/start/tz/framework/datapermission/core/annotation/DataPermission.java

@@ -1,35 +0,0 @@
-package cn.start.tz.framework.datapermission.core.annotation;
-
-import cn.start.tz.framework.datapermission.core.rule.DataPermissionRule;
-
-import java.lang.annotation.*;
-
-/**
- * 数据权限注解
- * 可声明在类或者方法上,标识使用的数据权限规则
- *
- * @author 芋道源码
- */
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface DataPermission {
-
-    /**
-     * 当前类或方法是否开启数据权限
-     * 即使不添加 @DataPermission 注解,默认是开启状态
-     * 可通过设置 enable 为 false 禁用
-     */
-    boolean enable() default true;
-
-    /**
-     * 生效的数据权限规则数组,优先级高于 {@link #excludeRules()}
-     */
-    Class<? extends DataPermissionRule>[] includeRules() default {};
-
-    /**
-     * 排除的数据权限规则数组,优先级最低
-     */
-    Class<? extends DataPermissionRule>[] excludeRules() default {};
-
-}

+ 0 - 72
tz-framework/tz-spring-boot-starter-biz-data-permission/src/main/java/cn/start/tz/framework/datapermission/core/aop/DataPermissionAnnotationInterceptor.java

@@ -1,72 +0,0 @@
-package cn.start.tz.framework.datapermission.core.aop;
-
-import cn.start.tz.framework.datapermission.core.annotation.DataPermission;
-import lombok.Getter;
-import org.aopalliance.intercept.MethodInterceptor;
-import org.aopalliance.intercept.MethodInvocation;
-import org.springframework.core.MethodClassKey;
-import org.springframework.core.annotation.AnnotationUtils;
-
-import java.lang.reflect.Method;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * {@link DataPermission} 注解的拦截器
- * 1. 在执行方法前,将 @DataPermission 注解入栈
- * 2. 在执行方法后,将 @DataPermission 注解出栈
- *
- * @author 芋道源码
- */
-@DataPermission // 该注解,用于 {@link DATA_PERMISSION_NULL} 的空对象
-public class DataPermissionAnnotationInterceptor implements MethodInterceptor {
-
-    /**
-     * DataPermission 空对象,用于方法无 {@link DataPermission} 注解时,使用 DATA_PERMISSION_NULL 进行占位
-     */
-    static final DataPermission DATA_PERMISSION_NULL = DataPermissionAnnotationInterceptor.class.getAnnotation(DataPermission.class);
-
-    @Getter
-    private final Map<MethodClassKey, DataPermission> dataPermissionCache = new ConcurrentHashMap<>();
-
-    @Override
-    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
-        // 入栈
-        DataPermission dataPermission = this.findAnnotation(methodInvocation);
-        if (dataPermission != null) {
-            DataPermissionContextHolder.add(dataPermission);
-        }
-        try {
-            // 执行逻辑
-            return methodInvocation.proceed();
-        } finally {
-            // 出栈
-            if (dataPermission != null) {
-                DataPermissionContextHolder.remove();
-            }
-        }
-    }
-
-    private DataPermission findAnnotation(MethodInvocation methodInvocation) {
-        // 1. 从缓存中获取
-        Method method = methodInvocation.getMethod();
-        Object targetObject = methodInvocation.getThis();
-        Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();
-        MethodClassKey methodClassKey = new MethodClassKey(method, clazz);
-        DataPermission dataPermission = dataPermissionCache.get(methodClassKey);
-        if (dataPermission != null) {
-            return dataPermission != DATA_PERMISSION_NULL ? dataPermission : null;
-        }
-
-        // 2.1 从方法中获取
-        dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class);
-        // 2.2 从类上获取
-        if (dataPermission == null) {
-            dataPermission = AnnotationUtils.findAnnotation(clazz, DataPermission.class);
-        }
-        // 2.3 添加到缓存中
-        dataPermissionCache.put(methodClassKey, dataPermission != null ? dataPermission : DATA_PERMISSION_NULL);
-        return dataPermission;
-    }
-
-}

+ 0 - 108
tz-framework/tz-spring-boot-starter-biz-data-permission/src/test/java/cn/start/tz/framework/datapermission/core/aop/DataPermissionAnnotationInterceptorTest.java

@@ -1,108 +0,0 @@
-package cn.start.tz.framework.datapermission.core.aop;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.start.tz.framework.datapermission.core.annotation.DataPermission;
-import cn.start.tz.framework.test.core.ut.BaseMockitoUnitTest;
-import org.aopalliance.intercept.MethodInvocation;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-
-import java.lang.reflect.Method;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.when;
-
-/**
- * {@link DataPermissionAnnotationInterceptor} 的单元测试
- *
- * @author 芋道源码
- */
-public class DataPermissionAnnotationInterceptorTest extends BaseMockitoUnitTest {
-
-    @InjectMocks
-    private DataPermissionAnnotationInterceptor interceptor;
-
-    @Mock
-    private MethodInvocation methodInvocation;
-
-    @BeforeEach
-    public void setUp() {
-        interceptor.getDataPermissionCache().clear();
-    }
-
-    @Test // 无 @DataPermission 注解
-    public void testInvoke_none() throws Throwable {
-        // 参数
-        mockMethodInvocation(TestNone.class);
-
-        // 调用
-        Object result = interceptor.invoke(methodInvocation);
-        // 断言
-        assertEquals("none", result);
-        assertEquals(1, interceptor.getDataPermissionCache().size());
-        assertTrue(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
-    }
-
-    @Test // 在 Method 上有 @DataPermission 注解
-    public void testInvoke_method() throws Throwable {
-        // 参数
-        mockMethodInvocation(TestMethod.class);
-
-        // 调用
-        Object result = interceptor.invoke(methodInvocation);
-        // 断言
-        assertEquals("method", result);
-        assertEquals(1, interceptor.getDataPermissionCache().size());
-        assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
-    }
-
-    @Test // 在 Class 上有 @DataPermission 注解
-    public void testInvoke_class() throws Throwable {
-        // 参数
-        mockMethodInvocation(TestClass.class);
-
-        // 调用
-        Object result = interceptor.invoke(methodInvocation);
-        // 断言
-        assertEquals("class", result);
-        assertEquals(1, interceptor.getDataPermissionCache().size());
-        assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
-    }
-
-    private void mockMethodInvocation(Class<?> clazz) throws Throwable {
-        Object targetObject = clazz.newInstance();
-        Method method = targetObject.getClass().getMethod("echo");
-        when(methodInvocation.getThis()).thenReturn(targetObject);
-        when(methodInvocation.getMethod()).thenReturn(method);
-        when(methodInvocation.proceed()).then(invocationOnMock -> method.invoke(targetObject));
-    }
-
-    static class TestMethod {
-
-        @DataPermission(enable = false)
-        public String echo() {
-            return "method";
-        }
-
-    }
-
-    @DataPermission(enable = false)
-    static class TestClass {
-
-        public String echo() {
-            return "class";
-        }
-
-    }
-
-    static class TestNone {
-
-        public String echo() {
-            return "none";
-        }
-
-    }
-
-}

+ 0 - 540
tz-framework/tz-spring-boot-starter-biz-data-permission/src/test/java/cn/start/tz/framework/datapermission/core/db/DataPermissionRuleHandlerTest.java

@@ -1,540 +0,0 @@
-package cn.start.tz.framework.datapermission.core.db;
-
-import cn.start.tz.framework.datapermission.core.rule.DataPermissionRule;
-import cn.start.tz.framework.datapermission.core.rule.DataPermissionRuleFactory;
-import cn.start.tz.framework.mybatis.core.util.MyBatisUtils;
-import cn.start.tz.framework.test.core.ut.BaseMockitoUnitTest;
-import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
-import net.sf.jsqlparser.expression.Alias;
-import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.expression.LongValue;
-import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
-import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
-import net.sf.jsqlparser.expression.operators.relational.InExpression;
-import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
-import net.sf.jsqlparser.schema.Column;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-
-import java.util.Arrays;
-import java.util.Set;
-
-import static cn.start.tz.framework.common.util.collection.SetUtils.asSet;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-/**
- * {@link DataPermissionRuleHandler} 的单元测试
- * 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试
- * 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~
- *
- * @author 芋道源码
- */
-public class DataPermissionRuleHandlerTest extends BaseMockitoUnitTest {
-
-    @InjectMocks
-    private DataPermissionRuleHandler handler;
-
-    @Mock
-    private DataPermissionRuleFactory ruleFactory;
-
-    private DataPermissionInterceptor interceptor;
-
-    @BeforeEach
-    public void setUp() {
-        interceptor = new DataPermissionInterceptor(handler);
-
-        // 租户的数据权限规则
-        DataPermissionRule tenantRule = new DataPermissionRule() {
-
-            private static final String COLUMN = "tenant_id";
-
-            @Override
-            public Set<String> getTableNames() {
-                return asSet("entity", "entity1", "entity2", "entity3", "t1", "t2", "sys_dict_item", // 支持 MyBatis Plus 的单元测试
-                        "t_user", "t_role"); // 满足自己的单元测试
-            }
-
-            @Override
-            public Expression getExpression(String tableName, Alias tableAlias) {
-                Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
-                LongValue value = new LongValue(1L);
-                return new EqualsTo(column, value);
-            }
-
-        };
-        // 部门的数据权限规则
-        DataPermissionRule deptRule = new DataPermissionRule() {
-
-            private static final String COLUMN = "dept_id";
-
-            @Override
-            public Set<String> getTableNames() {
-                return asSet("t_user");  // 满足自己的单元测试
-            }
-
-            @Override
-            public Expression getExpression(String tableName, Alias tableAlias) {
-                Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
-                ExpressionList<LongValue> values = new ExpressionList<>(new LongValue(10L),
-                        new LongValue(20L));
-                return new InExpression(column, new ParenthesedExpressionList((values)));
-            }
-
-        };
-        // 设置到上下文
-        when(ruleFactory.getDataPermissionRule(any())).thenReturn(Arrays.asList(tenantRule, deptRule));
-    }
-
-    @Test
-    void delete() {
-        assertSql("delete from entity where id = ?",
-                "DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1");
-    }
-
-    @Test
-    void update() {
-        assertSql("update entity set name = ? where id = ?",
-                "UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1");
-    }
-
-    @Test
-    void selectSingle() {
-        // 单表
-        assertSql("select * from entity where id = ?",
-                "SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1");
-
-        assertSql("select * from entity where id = ? or name = ?",
-                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity WHERE (id = ? OR name = ?)",
-                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
-
-        /* not */
-        assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
-                "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1");
-    }
-
-    @Test
-    void selectSubSelectIn() {
-        /* in */
-        assertSql("SELECT * FROM entity e WHERE e.id IN (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE e.id IN (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-        // 在最前
-        assertSql("SELECT * FROM entity e WHERE e.id IN " +
-                        "(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?",
-                "SELECT * FROM entity e WHERE e.id IN " +
-                        "(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1");
-        // 在最后
-        assertSql("SELECT * FROM entity e WHERE e.id = ? and e.id IN " +
-                        "(select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE e.id = ? AND e.id IN " +
-                        "(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-        // 在中间
-        assertSql("SELECT * FROM entity e WHERE e.id = ? and e.id IN " +
-                        "(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?",
-                "SELECT * FROM entity e WHERE e.id = ? AND e.id IN " +
-                        "(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1");
-    }
-
-    @Test
-    void selectSubSelectEq() {
-        /* = */
-        assertSql("SELECT * FROM entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-    }
-
-    @Test
-    void selectSubSelectInnerNotEq() {
-        /* inner not = */
-        assertSql("SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?))",
-                "SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1)) AND e.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?) and e.id = ?)",
-                "SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ?) AND e.tenant_id = 1");
-    }
-
-    @Test
-    void selectSubSelectExists() {
-        /* EXISTS */
-        assertSql("SELECT * FROM entity e WHERE EXISTS (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-
-
-        /* NOT EXISTS */
-        assertSql("SELECT * FROM entity e WHERE NOT EXISTS (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE NOT EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-    }
-
-    @Test
-    void selectSubSelect() {
-        /* >= */
-        assertSql("SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-
-
-        /* <= */
-        assertSql("SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-
-
-        /* <> */
-        assertSql("SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)",
-                "SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
-    }
-
-    @Test
-    void selectFromSelect() {
-        assertSql("SELECT * FROM (select e.id from entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?))",
-                "SELECT * FROM (SELECT e.id FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1)");
-    }
-
-    @Test
-    void selectBodySubSelect() {
-        assertSql("select t1.col1,(select t2.col2 from t2 t2 where t1.col1=t2.col1) from t1 t1",
-                "SELECT t1.col1, (SELECT t2.col2 FROM t2 t2 WHERE t1.col1 = t2.col1 AND t2.tenant_id = 1) FROM t1 t1 WHERE t1.tenant_id = 1");
-    }
-
-    @Test
-    void selectLeftJoin() {
-        // left join
-        assertSql("SELECT * FROM entity e " +
-                        "left join entity1 e1 on e1.id = e.id " +
-                        "WHERE e.id = ? OR e.name = ?",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "left join entity1 e1 on e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.name = ?)",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "left join entity1 e1 on e1.id = e.id " +
-                        "left join entity2 e2 on e1.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
-                        "WHERE e.tenant_id = 1");
-    }
-
-    @Test
-    void selectRightJoin() {
-        // right join
-        assertSql("SELECT * FROM entity e " +
-                        "right join entity1 e1 on e1.id = e.id",
-                "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
-                        "WHERE e1.tenant_id = 1");
-
-        assertSql("SELECT * FROM with_as_1 e " +
-                        "right join entity1 e1 on e1.id = e.id",
-                "SELECT * FROM with_as_1 e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id " +
-                        "WHERE e1.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "right join entity1 e1 on e1.id = e.id " +
-                        "WHERE e.id = ? OR e.name = ?",
-                "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "right join entity1 e1 on e1.id = e.id " +
-                        "right join entity2 e2 on e1.id = e2.id ",
-                "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
-                        "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
-                        "WHERE e2.tenant_id = 1");
-    }
-
-    @Test
-    void selectMixJoin() {
-        assertSql("SELECT * FROM entity e " +
-                        "right join entity1 e1 on e1.id = e.id " +
-                        "left join entity2 e2 on e1.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
-                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
-                        "WHERE e1.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "left join entity1 e1 on e1.id = e.id " +
-                        "right join entity2 e2 on e1.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 " +
-                        "WHERE e2.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "left join entity1 e1 on e1.id = e.id " +
-                        "inner join entity2 e2 on e1.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1");
-    }
-
-
-    @Test
-    void selectJoinSubSelect() {
-        assertSql("select * from (select * from entity) e1 " +
-                        "left join entity2 e2 on e1.id = e2.id",
-                "SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 " +
-                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1");
-
-        assertSql("select * from entity1 e1 " +
-                        "left join (select * from entity2) e2 " +
-                        "on e1.id = e2.id",
-                "SELECT * FROM entity1 e1 " +
-                        "LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 " +
-                        "ON e1.id = e2.id " +
-                        "WHERE e1.tenant_id = 1");
-    }
-
-    @Test
-    void selectSubJoin() {
-
-        assertSql("select * FROM " +
-                        "(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)",
-                "SELECT * FROM " +
-                        "(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
-                        "WHERE e2.tenant_id = 1");
-
-        assertSql("select * FROM " +
-                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)",
-                "SELECT * FROM " +
-                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
-                        "WHERE e1.tenant_id = 1");
-
-
-        assertSql("select * FROM " +
-                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) " +
-                        "right join entity3 e3 on e1.id = e3.id",
-                "SELECT * FROM " +
-                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
-                        "RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 " +
-                        "WHERE e3.tenant_id = 1");
-
-
-        assertSql("select * FROM entity e " +
-                        "LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) " +
-                        "on e.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
-                        "ON e.id = e2.id AND e2.tenant_id = 1 " +
-                        "WHERE e.tenant_id = 1");
-
-        assertSql("select * FROM entity e " +
-                        "LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
-                        "on e.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
-                        "ON e.id = e2.id AND e1.tenant_id = 1 " +
-                        "WHERE e.tenant_id = 1");
-
-        assertSql("select * FROM entity e " +
-                        "RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
-                        "on e.id = e2.id",
-                "SELECT * FROM entity e " +
-                        "RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
-                        "ON e.id = e2.id AND e.tenant_id = 1 " +
-                        "WHERE e1.tenant_id = 1");
-    }
-
-
-    @Test
-    void selectLeftJoinMultipleTrailingOn() {
-        // 多个 on 尾缀的
-        assertSql("SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 " +
-                        "LEFT JOIN entity2 e2 ON e2.id = e1.id " +
-                        "ON e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.NAME = ?)",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 " +
-                        "LEFT JOIN entity2 e2 ON e2.id = e1.id AND e2.tenant_id = 1 " +
-                        "ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1");
-
-        assertSql("SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 " +
-                        "LEFT JOIN with_as_A e2 ON e2.id = e1.id " +
-                        "ON e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.NAME = ?)",
-                "SELECT * FROM entity e " +
-                        "LEFT JOIN entity1 e1 " +
-                        "LEFT JOIN with_as_A e2 ON e2.id = e1.id " +
-                        "ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1");
-    }
-
-    @Test
-    void selectInnerJoin() {
-        // inner join
-        assertSql("SELECT * FROM entity e " +
-                        "inner join entity1 e1 on e1.id = e.id " +
-                        "WHERE e.id = ? OR e.name = ?",
-                "SELECT * FROM entity e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
-                        "WHERE e.id = ? OR e.name = ?");
-
-        assertSql("SELECT * FROM entity e " +
-                        "inner join entity1 e1 on e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.name = ?)",
-                "SELECT * FROM entity e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?)");
-
-        // 隐式内连接
-        assertSql("SELECT * FROM entity,entity1 " +
-                        "WHERE entity.id = entity1.id",
-                "SELECT * FROM entity, entity1 " +
-                        "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
-
-        // 隐式内连接
-        assertSql("SELECT * FROM entity a, with_as_entity1 b " +
-                        "WHERE a.id = b.id",
-                "SELECT * FROM entity a, with_as_entity1 b " +
-                        "WHERE a.id = b.id AND a.tenant_id = 1");
-
-        assertSql("SELECT * FROM with_as_entity a, with_as_entity1 b " +
-                        "WHERE a.id = b.id",
-                "SELECT * FROM with_as_entity a, with_as_entity1 b " +
-                        "WHERE a.id = b.id");
-
-        // SubJoin with 隐式内连接
-        assertSql("SELECT * FROM (entity,entity1) " +
-                        "WHERE entity.id = entity1.id",
-                "SELECT * FROM (entity, entity1) " +
-                        "WHERE entity.id = entity1.id " +
-                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
-
-        assertSql("SELECT * FROM ((entity,entity1),entity2) " +
-                        "WHERE entity.id = entity1.id and entity.id = entity2.id",
-                "SELECT * FROM ((entity, entity1), entity2) " +
-                        "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
-                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
-
-        assertSql("SELECT * FROM (entity,(entity1,entity2)) " +
-                        "WHERE entity.id = entity1.id and entity.id = entity2.id",
-                "SELECT * FROM (entity, (entity1, entity2)) " +
-                        "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
-                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
-
-        // 沙雕的括号写法
-        assertSql("SELECT * FROM (((entity,entity1))) " +
-                        "WHERE entity.id = entity1.id",
-                "SELECT * FROM (((entity, entity1))) " +
-                        "WHERE entity.id = entity1.id " +
-                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
-
-    }
-
-
-    @Test
-    void selectWithAs() {
-        assertSql("with with_as_A as (select * from entity) select * from with_as_A",
-                "WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A");
-    }
-
-
-    @Test
-    void selectIgnoreTable() {
-        assertSql(" SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)",
-                "SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)");
-    }
-
-    private void assertSql(String sql, String targetSql) {
-        assertEquals(targetSql, interceptor.parserSingle(sql, null));
-    }
-
-    // ========== 额外的测试 ==========
-
-    @Test
-    public void testSelectSingle() {
-        // 单表
-        assertSql("select * from t_user where id = ?",
-                "SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
-
-        assertSql("select * from t_user where id = ? or name = ?",
-                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
-
-        assertSql("SELECT * FROM t_user WHERE (id = ? OR name = ?)",
-                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
-
-        /* not */
-        assertSql("SELECT * FROM t_user WHERE not (id = ? OR name = ?)",
-                "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
-    }
-
-    @Test
-    public void testSelectLeftJoin() {
-        // left join
-        assertSql("SELECT * FROM t_user e " +
-                        "left join t_role e1 on e1.id = e.id " +
-                        "WHERE e.id = ? OR e.name = ?",
-                "SELECT * FROM t_user e " +
-                        "LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
-
-        // 条件 e.id = ? OR e.name = ? 带括号
-        assertSql("SELECT * FROM t_user e " +
-                        "left join t_role e1 on e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.name = ?)",
-                "SELECT * FROM t_user e " +
-                        "LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
-    }
-
-    @Test
-    public void testSelectRightJoin() {
-        // right join
-        assertSql("SELECT * FROM t_user e " +
-                        "right join t_role e1 on e1.id = e.id " +
-                        "WHERE e.id = ? OR e.name = ?",
-                "SELECT * FROM t_user e " +
-                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
-
-        // 条件 e.id = ? OR e.name = ? 带括号
-        assertSql("SELECT * FROM t_user e " +
-                        "right join t_role e1 on e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.name = ?)",
-                "SELECT * FROM t_user e " +
-                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
-    }
-
-    @Test
-    public void testSelectInnerJoin() {
-        // inner join
-        assertSql("SELECT * FROM t_user e " +
-                        "inner join entity1 e1 on e1.id = e.id " +
-                        "WHERE e.id = ? OR e.name = ?",
-                "SELECT * FROM t_user e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
-                        "WHERE e.id = ? OR e.name = ?");
-
-        // 条件 e.id = ? OR e.name = ? 带括号
-        assertSql("SELECT * FROM t_user e " +
-                        "inner join entity1 e1 on e1.id = e.id " +
-                        "WHERE (e.id = ? OR e.name = ?)",
-                "SELECT * FROM t_user e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?)");
-
-        // 没有 On 的 inner join
-        assertSql("SELECT * FROM entity,entity1 " +
-                "WHERE entity.id = entity1.id",
-            "SELECT * FROM entity, entity1 " +
-                    "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
-    }
-
-}

+ 0 - 5
tz-framework/tz-spring-boot-starter-biz-tenant/pom.xml

@@ -58,11 +58,6 @@
             <artifactId>tz-spring-boot-starter-mq</artifactId>
             <optional>true</optional>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-            <optional>true</optional>
-        </dependency>
         <dependency>
             <groupId>org.springframework.amqp</groupId>
             <artifactId>spring-rabbit</artifactId>

+ 0 - 7
tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/cn/start/tz/framework/tenant/config/TzTenantAutoConfiguration.java

@@ -6,7 +6,6 @@ import cn.start.tz.framework.redis.config.TzCacheProperties;
 import cn.start.tz.framework.tenant.core.aop.TenantIgnoreAspect;
 import cn.start.tz.framework.tenant.core.db.TenantDatabaseInterceptor;
 import cn.start.tz.framework.tenant.core.job.TenantJobAspect;
-import cn.start.tz.framework.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer;
 import cn.start.tz.framework.tenant.core.mq.redis.TenantRedisMessageInterceptor;
 import cn.start.tz.framework.tenant.core.mq.rocketmq.TenantRocketMQInitializer;
 import cn.start.tz.framework.tenant.core.redis.TenantRedisCacheManager;
@@ -115,12 +114,6 @@ public class TzTenantAutoConfiguration {
 
     }
 
-    @Bean
-    @ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
-    public TenantRabbitMQInitializer tenantRabbitMQInitializer() {
-        return new TenantRabbitMQInitializer();
-    }
-
     @Bean
     @ConditionalOnClass(name = "org.apache.rocketmq.spring.core.RocketMQTemplate")
     public TenantRocketMQInitializer tenantRocketMQInitializer() {

+ 0 - 18
tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/cn/start/tz/framework/tenant/core/aop/TenantIgnore.java

@@ -1,18 +0,0 @@
-package cn.start.tz.framework.tenant.core.aop;
-
-import java.lang.annotation.*;
-
-/**
- * 忽略租户,标记指定方法不进行租户的自动过滤
- *
- * 注意,只有 DB 的场景会过滤,其它场景暂时不过滤:
- * 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的
- * 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略
- *
- * @author 芋道源码
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-@Inherited
-public @interface TenantIgnore {
-}

+ 0 - 14
tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/cn/start/tz/framework/tenant/core/job/TenantJob.java

@@ -1,14 +0,0 @@
-package cn.start.tz.framework.tenant.core.job;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * 多租户 Job 注解
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface TenantJob {
-}

+ 0 - 275
tz-framework/tz-spring-boot-starter-biz-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java

@@ -1,275 +0,0 @@
-/*
- * Copyright 2002-2023 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.messaging.handler.invocation;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.util.Arrays;
-
-import cn.start.tz.framework.tenant.core.context.TenantContextHolder;
-import cn.start.tz.framework.tenant.core.util.TenantUtils;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.MethodParameter;
-import org.springframework.core.ParameterNameDiscoverer;
-import org.springframework.core.ResolvableType;
-import org.springframework.lang.Nullable;
-import org.springframework.messaging.Message;
-import org.springframework.messaging.handler.HandlerMethod;
-import org.springframework.util.ObjectUtils;
-
-import static cn.start.tz.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
-
-/**
- * Extension of {@link HandlerMethod} that invokes the underlying method with
- * argument values resolved from the current HTTP request through a list of
- * {@link HandlerMethodArgumentResolver}.
- *
- * 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中
- * TODO 芋艿:持续跟进,看看有没新的拓展点
- *
- * @author Rossen Stoyanchev
- * @author Juergen Hoeller
- * @since 4.0
- */
-public class InvocableHandlerMethod extends HandlerMethod {
-
-    private static final Object[] EMPTY_ARGS = new Object[0];
-
-
-    private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
-
-    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
-
-
-    /**
-     * Create an instance from a {@code HandlerMethod}.
-     */
-    public InvocableHandlerMethod(HandlerMethod handlerMethod) {
-        super(handlerMethod);
-    }
-
-    /**
-     * Create an instance from a bean instance and a method.
-     */
-    public InvocableHandlerMethod(Object bean, Method method) {
-        super(bean, method);
-    }
-
-    /**
-     * Construct a new handler method with the given bean instance, method name and parameters.
-     * @param bean the object bean
-     * @param methodName the method name
-     * @param parameterTypes the method parameter types
-     * @throws NoSuchMethodException when the method cannot be found
-     */
-    public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
-            throws NoSuchMethodException {
-
-        super(bean, methodName, parameterTypes);
-    }
-
-
-    /**
-     * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values.
-     */
-    public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
-        this.resolvers = argumentResolvers;
-    }
-
-    /**
-     * Set the ParameterNameDiscoverer for resolving parameter names when needed
-     * (e.g. default request attribute name).
-     * <p>Default is a {@link DefaultParameterNameDiscoverer}.
-     */
-    public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
-        this.parameterNameDiscoverer = parameterNameDiscoverer;
-    }
-
-
-    /**
-     * Invoke the method after resolving its argument values in the context of the given message.
-     * <p>Argument values are commonly resolved through
-     * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
-     * The {@code providedArgs} parameter however may supply argument values to be used directly,
-     * i.e. without argument resolution.
-     * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
-     * resolved arguments.
-     * @param message the current message being processed
-     * @param providedArgs "given" arguments matched by type, not resolved
-     * @return the raw value returned by the invoked method
-     * @throws Exception raised if no suitable argument resolver can be found,
-     * or if the method raised an exception
-     * @see #getMethodArgumentValues
-     * @see #doInvoke
-     */
-    @Nullable
-    public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
-        Object[] args = getMethodArgumentValues(message, providedArgs);
-        if (logger.isTraceEnabled()) {
-            logger.trace("Arguments: " + Arrays.toString(args));
-        }
-        // 注意:如下是本类的改动点!!!
-        // 情况一:无租户编号的情况
-        String tenantId= parseTenantId(message);
-        if (tenantId == null) {
-            return doInvoke(args);
-        }
-        // 情况二:有租户的情况下
-        return TenantUtils.execute(tenantId, () -> doInvoke(args));
-    }
-
-    private String parseTenantId(Message<?> message) {
-        Object tenantId = message.getHeaders().get(HEADER_TENANT_ID);
-        if (tenantId == null) {
-            return null;
-        }
-        if (tenantId instanceof Long) {
-            return (String) tenantId;
-        }
-        if (tenantId instanceof Number) {
-            return ((Number) tenantId).toString();
-        }
-        if (tenantId instanceof String) {
-            return (String) tenantId;
-        }
-        if (tenantId instanceof byte[]) {
-            return new String((byte[]) tenantId);
-        }
-        throw new IllegalArgumentException("未知的数据类型:" + tenantId);
-    }
-
-    /**
-     * Get the method argument values for the current message, checking the provided
-     * argument values and falling back to the configured argument resolvers.
-     * <p>The resulting array will be passed into {@link #doInvoke}.
-     * @since 5.1.2
-     */
-    protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
-        MethodParameter[] parameters = getMethodParameters();
-        if (ObjectUtils.isEmpty(parameters)) {
-            return EMPTY_ARGS;
-        }
-
-        Object[] args = new Object[parameters.length];
-        for (int i = 0; i < parameters.length; i++) {
-            MethodParameter parameter = parameters[i];
-            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
-            args[i] = findProvidedArgument(parameter, providedArgs);
-            if (args[i] != null) {
-                continue;
-            }
-            if (!this.resolvers.supportsParameter(parameter)) {
-                throw new MethodArgumentResolutionException(
-                        message, parameter, formatArgumentError(parameter, "No suitable resolver"));
-            }
-            try {
-                args[i] = this.resolvers.resolveArgument(parameter, message);
-            }
-            catch (Exception ex) {
-                // Leave stack trace for later, exception may actually be resolved and handled...
-                if (logger.isDebugEnabled()) {
-                    String exMsg = ex.getMessage();
-                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
-                        logger.debug(formatArgumentError(parameter, exMsg));
-                    }
-                }
-                throw ex;
-            }
-        }
-        return args;
-    }
-
-    /**
-     * Invoke the handler method with the given argument values.
-     */
-    @Nullable
-    protected Object doInvoke(Object... args) throws Exception {
-        try {
-            return getBridgedMethod().invoke(getBean(), args);
-        }
-        catch (IllegalArgumentException ex) {
-            assertTargetBean(getBridgedMethod(), getBean(), args);
-            String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ?
-                    "Illegal argument": ex.getMessage();
-            throw new IllegalStateException(formatInvokeError(text, args), ex);
-        }
-        catch (InvocationTargetException ex) {
-            // Unwrap for HandlerExceptionResolvers ...
-            Throwable targetException = ex.getTargetException();
-            if (targetException instanceof RuntimeException runtimeException) {
-                throw runtimeException;
-            }
-            else if (targetException instanceof Error error) {
-                throw error;
-            }
-            else if (targetException instanceof Exception exception) {
-                throw exception;
-            }
-            else {
-                throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
-            }
-        }
-    }
-
-    MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {
-        return new AsyncResultMethodParameter(returnValue);
-    }
-
-
-    private class AsyncResultMethodParameter extends AnnotatedMethodParameter {
-
-        @Nullable
-        private final Object returnValue;
-
-        private final ResolvableType returnType;
-
-        public AsyncResultMethodParameter(@Nullable Object returnValue) {
-            super(-1);
-            this.returnValue = returnValue;
-            this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();
-        }
-
-        protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
-            super(original);
-            this.returnValue = original.returnValue;
-            this.returnType = original.returnType;
-        }
-
-        @Override
-        public Class<?> getParameterType() {
-            if (this.returnValue != null) {
-                return this.returnValue.getClass();
-            }
-            if (!ResolvableType.NONE.equals(this.returnType)) {
-                return this.returnType.toClass();
-            }
-            return super.getParameterType();
-        }
-
-        @Override
-        public Type getGenericParameterType() {
-            return this.returnType.getType();
-        }
-
-        @Override
-        public AsyncResultMethodParameter clone() {
-            return new AsyncResultMethodParameter(this);
-        }
-    }
-
-}

+ 1 - 2
tz-framework/tz-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories

@@ -1,2 +1 @@
-org.springframework.boot.env.EnvironmentPostProcessor=\
-  cn.start.tz.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor
+

+ 0 - 50
tz-framework/tz-spring-boot-starter-env/src/main/java/cn/start/tz/framework/env/config/EnvEnvironmentPostProcessor.java

@@ -1,50 +0,0 @@
-package cn.start.tz.framework.env.config;
-
-import cn.hutool.core.util.StrUtil;
-import cn.start.tz.framework.common.util.collection.SetUtils;
-import cn.start.tz.framework.env.core.util.EnvUtils;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.env.EnvironmentPostProcessor;
-import org.springframework.core.env.ConfigurableEnvironment;
-
-import java.util.Set;
-
-import static cn.start.tz.framework.env.core.util.EnvUtils.HOST_NAME_VALUE;
-
-/**
- * 多环境的 {@link EnvEnvironmentPostProcessor} 实现类
- * 将 tz.env.tag 设置到 nacos 等组件对应的 tag 配置项,当且仅当它们不存在时
- *
- * @author 芋道源码
- */
-public class EnvEnvironmentPostProcessor implements EnvironmentPostProcessor {
-
-    private static final Set<String> TARGET_TAG_KEYS = SetUtils.asSet(
-            "spring.cloud.nacos.discovery.metadata.tag" // Nacos 注册中心
-            // MQ TODO
-    );
-
-    @Override
-    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
-        // 0. 设置 ${HOST_NAME} 兜底的环境变量
-        String hostNameKey = StrUtil.subBetween(HOST_NAME_VALUE, "{", "}");
-        if (!environment.containsProperty(hostNameKey)) {
-            environment.getSystemProperties().put(hostNameKey, EnvUtils.getHostName());
-        }
-
-        // 1.1 如果没有 tz.env.tag 配置项,则不进行配置项的修改
-        String tag = EnvUtils.getTag(environment);
-        if (StrUtil.isEmpty(tag)) {
-            return;
-        }
-        // 1.2 需要修改的配置项
-        for (String targetTagKey : TARGET_TAG_KEYS) {
-            String targetTagValue = environment.getProperty(targetTagKey);
-            if (StrUtil.isNotEmpty(targetTagValue)) {
-                continue;
-            }
-            environment.getSystemProperties().put(targetTagKey, tag);
-        }
-    }
-
-}

+ 0 - 22
tz-framework/tz-spring-boot-starter-excel/src/main/java/cn/start/tz/framework/excel/core/annotations/DictFormat.java

@@ -1,22 +0,0 @@
-package cn.start.tz.framework.excel.core.annotations;
-
-import java.lang.annotation.*;
-
-/**
- * 字典格式化
- *
- * 实现将字典数据的值,格式化成字典数据的标签
- */
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@Inherited
-public @interface DictFormat {
-
-    /**
-     * 例如说,SysDictTypeConstants、InfDictTypeConstants
-     *
-     * @return 字典类型
-     */
-    String value();
-
-}

+ 0 - 27
tz-framework/tz-spring-boot-starter-excel/src/main/java/cn/start/tz/framework/excel/core/annotations/ExcelColumnSelect.java

@@ -1,27 +0,0 @@
-package cn.start.tz.framework.excel.core.annotations;
-
-import java.lang.annotation.*;
-
-/**
- * 给 Excel 列添加下拉选择数据
- *
- * 其中 {@link #dictType()} 和 {@link #functionName()} 二选一
- *
- * @author HUIHUI
- */
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@Inherited
-public @interface ExcelColumnSelect {
-
-    /**
-     * @return 字典类型
-     */
-    String dictType() default "";
-
-    /**
-     * @return 获取下拉数据源的方法名称
-     */
-    String functionName() default "";
-
-}

+ 22 - 0
tz-framework/tz-spring-boot-starter-job/src/main/java/cn/start/tz/framework/job/core/JobHandler.java

@@ -0,0 +1,22 @@
+package cn.start.tz.framework.job.core;
+
+/**
+ * 任务处理器接口
+ * <p>
+ * 所有的定时任务处理器都需要实现此接口
+ * </p>
+ *
+ * @author 特种管理员
+ */
+public interface JobHandler {
+
+    /**
+     * 执行任务
+     *
+     * @param jobId     任务编号
+     * @param handlerParam 任务处理器参数
+     * @throws Exception 执行异常
+     */
+    void execute(String jobId, String handlerParam) throws Exception;
+
+}

+ 163 - 0
tz-framework/tz-spring-boot-starter-llm/README.md

@@ -0,0 +1,163 @@
+# TZ Spring Boot Starter LLM
+
+大语言模型 Spring Boot Starter,支持通义千问、智谱 AI、OpenAI 等多种 Provider。
+
+## 功能特性
+
+- 支持多种 LLM Provider
+- 统一的客户端接口
+- 灵活的配置化管理
+- 完整的 JavaDoc 注释
+- 开箱即用的 Spring Boot 集成
+
+## Maven 依赖
+
+```xml
+<dependency>
+    <groupId>cn.tz.cloud</groupId>
+    <artifactId>tz-spring-boot-starter-llm</artifactId>
+    <version>${revision}</version>
+</dependency>
+```
+
+## 配置说明
+
+### 基础配置
+
+在 `application.yml` 中添加配置:
+
+```yaml
+tz:
+  llm:
+    enabled: true
+    default-provider: qwen  # 默认使用的 Provider
+```
+
+### Provider 配置
+
+#### 通义千问 (Qwen)
+
+```yaml
+tz:
+  llm:
+    providers:
+      qwen:
+        api-key: your-qwen-api-key
+        model: qwen-turbo  # 可选,默认 qwen-turbo
+        temperature: 0.7   # 可选,温度参数
+        max-tokens: 2000   # 可选,最大 tokens 数
+```
+
+#### 智谱 AI (Zhipu)
+
+```yaml
+tz:
+  llm:
+    providers:
+      zhipu:
+        api-key: your-zhipu-api-key
+        model: glm-4  # 可选,默认 glm-4
+        temperature: 0.7
+        max-tokens: 2000
+```
+
+#### OpenAI
+
+```yaml
+tz:
+  llm:
+    providers:
+      openai:
+        api-key: your-openai-api-key
+        model: gpt-3.5-turbo  # 可选,默认 gpt-3.5-turbo
+        api-url: https://api.openai.com/v1/chat/completions  # 可选,用于自定义 API 端点
+        temperature: 0.7
+        max-tokens: 2000
+```
+
+## 使用示例
+
+### 注入客户端工厂
+
+```java
+@Autowired
+private LlmClientFactory llmClientFactory;
+```
+
+### 简单对话
+
+```java
+// 使用默认 Provider
+List<LlmMessage> messages = List.of(
+    LlmMessage.system("你是一个智能助手"),
+    LlmMessage.user("请介绍一下 Java 编程语言")
+);
+
+LlmResponse response = llmClientFactory.chat(messages);
+System.out.println(response.getContent());
+```
+
+### 指定 Provider
+
+```java
+// 使用通义千问
+List<LlmMessage> messages = List.of(
+    LlmMessage.system("你是一个智能助手"),
+    LlmMessage.user("请介绍一下 Java 编程语言")
+);
+
+LlmResponse response = llmClientFactory.chat("qwen", messages);
+System.out.println(response.getContent());
+```
+
+### 直接使用客户端
+
+```java
+@Autowired
+private LlmClientFactory llmClientFactory;
+
+public void chat() {
+    LlmClient client = llmClientFactory.getClient("qwen");
+
+    LlmChatRequest request = LlmChatRequest.builder()
+        .messages(List.of(
+            LlmMessage.system("你是一个智能助手"),
+            LlmMessage.user("请介绍一下 Java 编程语言")
+        ))
+        .model("qwen-turbo")
+        .temperature(0.7)
+        .maxTokens(2000)
+        .build();
+
+    LlmResponse response = client.chat(request);
+    System.out.println(response.getContent());
+}
+```
+
+### 检查 Provider 可用性
+
+```java
+// 检查特定 Provider 是否可用
+boolean available = llmClientFactory.isAvailable("qwen");
+
+// 获取所有可用的 Provider
+List<String> providers = llmClientFactory.getAvailableProviders();
+```
+
+## 支持的 Provider
+
+| Provider | 说明 | 模型示例 |
+|----------|------|----------|
+| qwen | 通义千问 | qwen-turbo, qwen-plus, qwen-max |
+| zhipu | 智谱 AI | glm-4, glm-3-turbo |
+| openai | OpenAI | gpt-3.5-turbo, gpt-4 |
+
+## API 文档
+
+详细的 API 文档请参考 JavaDoc 注释。
+
+## 注意事项
+
+1. 请妥善保管 API Key,不要将其提交到版本控制系统
+2. 建议使用环境变量或配置中心管理敏感配置
+3. 不同 Provider 的 API 限制和计费方式可能不同,请参考各自的官方文档

+ 69 - 0
tz-framework/tz-spring-boot-starter-llm/pom.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.tz.cloud</groupId>
+        <artifactId>tz-framework</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>tz-spring-boot-starter-llm</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>LLM 大语言模型 Spring Boot Starter,支持通义千问、智谱、OpenAI 等多种 Provider</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.tz.cloud</groupId>
+            <artifactId>tz-common</artifactId>
+        </dependency>
+
+        <!-- Spring Boot 配置所需依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- HTTP 客户端 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+
+        <!-- JSON 处理 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- Hutool 工具类 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 108 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmAutoConfiguration.java

@@ -0,0 +1,108 @@
+package cn.start.tz.framework.llm;
+
+import cn.hutool.core.util.StrUtil;
+import cn.start.tz.framework.llm.enums.LlmProviderEnum;
+import cn.start.tz.framework.llm.provider.openai.OpenAiLlmClient;
+import cn.start.tz.framework.llm.provider.qwen.QwenLlmClient;
+import cn.start.tz.framework.llm.provider.zhipu.ZhipuLlmClient;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * LLM 自动配置类
+ * <p>
+ * 自动配置 LLM 相关的 Bean,包括各种 Provider 的客户端实现
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(LlmProperties.class)
+@Slf4j
+public class LlmAutoConfiguration {
+
+    /**
+     * 配置 LLM 客户端工厂
+     *
+     * @param properties LLM 配置属性
+     * @return LLM 客户端工厂
+     */
+    @Bean
+    public LlmClientFactory llmClientFactory(LlmProperties properties) {
+        Map<String, LlmClient> clients = createClients(properties);
+        log.info("[LlmAutoConfiguration] LLM 客户端初始化完成,共 {} 个 Provider", clients.size());
+        return new LlmClientFactory(clients, properties);
+    }
+
+    /**
+     * 创建所有 Provider 的客户端实例
+     *
+     * @param properties LLM 配置属性
+     * @return Provider -> LlmClient 映射
+     */
+    private Map<String, LlmClient> createClients(LlmProperties properties) {
+        return properties.getProviders().entrySet().stream()
+                .filter(entry -> {
+                    String provider = entry.getKey();
+                    LlmProperties.ProviderConfig config = entry.getValue();
+                    boolean valid = StrUtil.isNotBlank(config.getApiKey());
+                    if (!valid) {
+                        log.warn("[LlmAutoConfiguration] Provider {} 配置无效,缺少 API Key", provider);
+                    }
+                    return valid;
+                })
+                .collect(Collectors.toMap(
+                        Map.Entry::getKey,
+                        entry -> createClient(entry.getKey(), entry.getValue())
+                ));
+    }
+
+    /**
+     * 根据配置创建对应的客户端实例
+     *
+     * @param provider Provider 类型
+     * @param config   Provider 配置
+     * @return LlmClient 实例
+     */
+    private LlmClient createClient(String provider, LlmProperties.ProviderConfig config) {
+        String apiKey = config.getApiKey();
+        String model = config.getModel();
+        ObjectMapper objectMapper = new ObjectMapper();
+
+        return switch (LlmProviderEnum.fromCode(provider)) {
+            case QWEN -> {
+                if (StrUtil.isBlank(model)) {
+                    yield new QwenLlmClient(apiKey);
+                } else {
+                    yield new QwenLlmClient(apiKey, model, objectMapper);
+                }
+            }
+            case ZHIPU -> {
+                if (StrUtil.isBlank(model)) {
+                    yield new ZhipuLlmClient(apiKey);
+                } else {
+                    yield new ZhipuLlmClient(apiKey, model, objectMapper);
+                }
+            }
+            case OPENAI -> {
+                String apiUrl = config.getApiUrl();
+                if (StrUtil.isNotBlank(apiUrl)) {
+                    yield new OpenAiLlmClient(apiKey, apiUrl);
+                } else {
+                    yield new OpenAiLlmClient(apiKey);
+                }
+            }
+            default -> {
+                log.warn("[LlmAutoConfiguration] 未知的 Provider 类型: {}", provider);
+                yield null;
+            }
+        };
+    }
+
+}

+ 52 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmClient.java

@@ -0,0 +1,52 @@
+package cn.start.tz.framework.llm;
+
+import cn.start.tz.framework.llm.dto.LlmChatRequest;
+import cn.start.tz.framework.llm.dto.LlmResponse;
+
+/**
+ * LLM 客户端接口
+ * <p>
+ * 定义了 LLM 服务的基础操作,所有 Provider 实现都需要实现此接口
+ * </p>
+ *
+ * @author Tz Framework
+ */
+public interface LlmClient {
+
+    /**
+     * 发送聊天请求
+     * <p>
+     * 发送对话消息到 LLM 服务,并返回响应结果
+     * </p>
+     *
+     * @param request 聊天请求
+     * @return LLM 响应
+     */
+    LlmResponse chat(LlmChatRequest request);
+
+    /**
+     * 获取 Provider 类型
+     *
+     * @return Provider 类型
+     */
+    String getProvider();
+
+    /**
+     * 获取默认模型名称
+     *
+     * @return 模型名称
+     */
+    default String getDefaultModel() {
+        return "default";
+    }
+
+    /**
+     * 检查客户端是否可用
+     *
+     * @return 是否可用
+     */
+    default boolean isAvailable() {
+        return true;
+    }
+
+}

+ 109 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmClientFactory.java

@@ -0,0 +1,109 @@
+package cn.start.tz.framework.llm;
+
+import cn.start.tz.framework.llm.dto.LlmChatRequest;
+import cn.start.tz.framework.llm.dto.LlmMessage;
+import cn.start.tz.framework.llm.dto.LlmResponse;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * LLM 客户端工厂
+ * <p>
+ * 用于获取和管理不同 Provider 的 LLM 客户端实例
+ * </p>
+ *
+ * @author Tz Framework
+ */
+public class LlmClientFactory {
+
+    private final Map<String, LlmClient> clients;
+    private final LlmProperties properties;
+    private final String defaultProvider;
+
+    public LlmClientFactory(Map<String, LlmClient> clients, LlmProperties properties) {
+        this.clients = clients;
+        this.properties = properties;
+        this.defaultProvider = properties.getDefaultProvider();
+    }
+
+    /**
+     * 获取默认 Provider 的客户端
+     *
+     * @return LlmClient 实例
+     */
+    public LlmClient getClient() {
+        return getClient(defaultProvider);
+    }
+
+    /**
+     * 获取指定 Provider 的客户端
+     *
+     * @param provider Provider 类型
+     * @return LlmClient 实例
+     */
+    public LlmClient getClient(String provider) {
+        LlmClient client = clients.get(provider);
+        if (client == null) {
+            throw new IllegalArgumentException("未找到 Provider: " + provider);
+        }
+        return client;
+    }
+
+    /**
+     * 发送聊天请求(使用默认 Provider)
+     *
+     * @param messages 消息列表
+     * @return LLM 响应
+     */
+    public LlmResponse chat(List<LlmMessage> messages) {
+        return chat(defaultProvider, messages);
+    }
+
+    /**
+     * 发送聊天请求(指定 Provider)
+     *
+     * @param provider Provider 类型
+     * @param messages 消息列表
+     * @return LLM 响应
+     */
+    public LlmResponse chat(String provider, List<LlmMessage> messages) {
+        LlmClient client = getClient(provider);
+        LlmProperties.ProviderConfig config = properties.getProviders().get(provider);
+
+        cn.start.tz.framework.llm.dto.LlmChatRequest request = cn.start.tz.framework.llm.dto.LlmChatRequest.builder()
+                .messages(messages)
+                .model(config != null ? config.getModel() : client.getDefaultModel())
+                .temperature(config != null ? config.getTemperature() : null)
+                .maxTokens(config != null ? config.getMaxTokens() : null)
+                .topP(config != null ? config.getTopP() : null)
+                .build();
+
+        return client.chat(request);
+    }
+
+    /**
+     * 检查客户端是否可用
+     *
+     * @param provider Provider 类型
+     * @return 是否可用
+     */
+    public boolean isAvailable(String provider) {
+        LlmClient client = clients.get(provider);
+        return client != null && client.isAvailable();
+    }
+
+    /**
+     * 获取所有可用的 Provider
+     *
+     * @return Provider 列表
+     */
+    public List<String> getAvailableProviders() {
+        return clients.entrySet().stream()
+                .filter(entry -> entry.getValue().isAvailable())
+                .map(Map.Entry::getKey)
+                .collect(Collectors.toList());
+    }
+
+}

+ 116 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/LlmProperties.java

@@ -0,0 +1,116 @@
+package cn.start.tz.framework.llm;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * LLM 配置属性
+ * <p>
+ * 用于配置 LLM 服务的相关参数,支持多 Provider 配置
+ * </p>
+ * <p>
+ * 配置示例:
+ * <pre>
+ * tz.llm.enabled=true
+ * tz.llm.default-provider=qwen
+ * tz.llm.providers.qwen.api-key=your-api-key
+ * tz.llm.providers.qwen.model=qwen-turbo
+ * tz.llm.providers.zhipu.api-key=your-api-key
+ * tz.llm.providers.zhipu.model=glm-4
+ * tz.llm.providers.openai.api-key=your-api-key
+ * tz.llm.providers.openai.model=gpt-3.5-turbo
+ * </pre>
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@ConfigurationProperties(prefix = "tz.llm")
+@Data
+public class LlmProperties {
+
+    /**
+     * 是否启用 LLM 功能
+     */
+    private static final Boolean ENABLED_DEFAULT = true;
+
+    /**
+     * 是否启用
+     */
+    private Boolean enabled = ENABLED_DEFAULT;
+
+    /**
+     * 默认 Provider
+     * <p>
+     * 当未指定 Provider 时使用此默认值
+     * </p>
+     */
+    private String defaultProvider = "qwen";
+
+    /**
+     * 默认超时时间(毫秒)
+     */
+    private Long timeout = 30000L;
+
+    /**
+     * Provider 配置
+     * <p>
+     * Key: Provider 类型 (qwen, zhipu, openai)
+     * Value: Provider 具体配置
+     * </p>
+     */
+    private Map<String, ProviderConfig> providers = new HashMap<>();
+
+    /**
+     * Provider 配置
+     */
+    @Data
+    public static class ProviderConfig {
+
+        /**
+         * API Key
+         */
+        private String apiKey;
+
+        /**
+         * 模型名称
+         */
+        private String model;
+
+        /**
+         * API 地址(可选,用于自定义 API 端点)
+         * <p>
+         * 主要用于 OpenAI 兼容服务
+         * </p>
+         */
+        private String apiUrl;
+
+        /**
+         * 温度参数
+         * <p>
+         * 默认温度值
+         * </p>
+         */
+        private Double temperature;
+
+        /**
+         * 最大 tokens 数
+         * <p>
+         * 默认最大 tokens
+         * </p>
+         */
+        private Integer maxTokens;
+
+        /**
+         * Top-P 采样参数
+         * <p>
+         * 默认 top_p 值
+         * </p>
+         */
+        private Double topP;
+
+    }
+
+}

+ 92 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmChatRequest.java

@@ -0,0 +1,92 @@
+package cn.start.tz.framework.llm.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LLM 聊天请求 DTO
+ * <p>
+ * 用于发送聊天请求的参数封装
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class LlmChatRequest {
+
+    /**
+     * 对话消息列表
+     */
+    private List<LlmMessage> messages;
+
+    /**
+     * 模型名称
+     * <p>
+     * 例如: qwen-turbo、chatglm3、gpt-3.5-turbo 等
+     * </p>
+     */
+    private String model;
+
+    /**
+     * 温度参数
+     * <p>
+     * 控制输出的随机性,取值范围 0.0-1.0,值越大输出越随机
+     * </p>
+     */
+    private Double temperature;
+
+    /**
+     * 最大输出 tokens 数
+     */
+    private Integer maxTokens;
+
+    /**
+     * Top-P 采样参数
+     * <p>
+     * 控制输出的多样性,取值范围 0.0-1.0
+     * </p>
+     */
+    private Double topP;
+
+    /**
+     * 停止序列
+     * <p>
+     * 当生成内容遇到这些字符串时停止生成
+     * </p>
+     */
+    private List<String> stop;
+
+    /**
+     * 扩展参数
+     * <p>
+     * 用于存储一些特定 Provider 需要的额外参数
+     * </p>
+     */
+    private Map<String, Object> extra;
+
+    /**
+     * 添加扩展参数
+     *
+     * @param key   键
+     * @param value 值
+     * @return 当前请求实例
+     */
+    public LlmChatRequest addExtra(String key, Object value) {
+        if (this.extra == null) {
+            this.extra = new java.util.HashMap<>();
+        }
+        this.extra.put(key, value);
+        return this;
+    }
+
+}

+ 101 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmMessage.java

@@ -0,0 +1,101 @@
+package cn.start.tz.framework.llm.dto;
+
+import cn.start.tz.framework.llm.enums.LlmRoleEnum;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * LLM 消息 DTO
+ * <p>
+ * 表示对话中的一条消息,包含角色和内容
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class LlmMessage {
+
+    /**
+     * 消息角色
+     */
+    private LlmRoleEnum role;
+
+    /**
+     * 消息内容
+     */
+    private String content;
+
+    /**
+     * 扩展参数
+     * <p>
+     * 用于存储一些特定 Provider 需要的额外参数,例如名称、工具调用等
+     * </p>
+     */
+    @Builder.Default
+    private Map<String, Object> extra = new HashMap<>();
+
+    /**
+     * 创建用户消息
+     *
+     * @param content 消息内容
+     * @return 消息实例
+     */
+    public static LlmMessage user(String content) {
+        return LlmMessage.builder()
+                .role(LlmRoleEnum.USER)
+                .content(content)
+                .build();
+    }
+
+    /**
+     * 创建系统消息
+     *
+     * @param content 消息内容
+     * @return 消息实例
+     */
+    public static LlmMessage system(String content) {
+        return LlmMessage.builder()
+                .role(LlmRoleEnum.SYSTEM)
+                .content(content)
+                .build();
+    }
+
+    /**
+     * 创建助手消息
+     *
+     * @param content 消息内容
+     * @return 消息实例
+     */
+    public static LlmMessage assistant(String content) {
+        return LlmMessage.builder()
+                .role(LlmRoleEnum.ASSISTANT)
+                .content(content)
+                .build();
+    }
+
+    /**
+     * 添加扩展参数
+     *
+     * @param key   键
+     * @param value 值
+     * @return 当前消息实例
+     */
+    public LlmMessage addExtra(String key, Object value) {
+        if (this.extra == null) {
+            this.extra = new HashMap<>();
+        }
+        this.extra.put(key, value);
+        return this;
+    }
+
+}

+ 72 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmRequest.java

@@ -0,0 +1,72 @@
+package cn.start.tz.framework.llm.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * LLM 通用请求 DTO
+ * <p>
+ * 通用的 LLM 请求参数封装,支持不同类型的请求
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class LlmRequest {
+
+    /**
+     * 请求类型
+     * <p>
+     * 例如: chat、completion、embedding 等
+     * </p>
+     */
+    private String type;
+
+    /**
+     * 模型名称
+     */
+    private String model;
+
+    /**
+     * 请求内容
+     */
+    private String content;
+
+    /**
+     * 提示词
+     */
+    private String prompt;
+
+    /**
+     * 扩展参数
+     * <p>
+     * 用于存储不同类型请求的特定参数
+     * </p>
+     */
+    private Map<String, Object> parameters;
+
+    /**
+     * 添加参数
+     *
+     * @param key   键
+     * @param value 值
+     * @return 当前请求实例
+     */
+    public LlmRequest addParameter(String key, Object value) {
+        if (this.parameters == null) {
+            this.parameters = new java.util.HashMap<>();
+        }
+        this.parameters.put(key, value);
+        return this;
+    }
+
+}

+ 136 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/dto/LlmResponse.java

@@ -0,0 +1,136 @@
+package cn.start.tz.framework.llm.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LLM 响应 DTO
+ * <p>
+ * 表示 LLM 返回的响应结果
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class LlmResponse {
+
+    /**
+     * 响应内容
+     */
+    private String content;
+
+    /**
+     * 模型名称
+     */
+    private String model;
+
+    /**
+     * 使用的 prompt tokens 数量
+     */
+    private Integer promptTicks;
+
+    /**
+     * 生成的 completion tokens 数量
+     */
+    private Integer completionTicks;
+
+    /**
+     * 总 tokens 数量
+     */
+    private Integer totalTicks;
+
+    /**
+     * 响应状态码
+     */
+    private Integer code;
+
+    /**
+     * 响应消息
+     */
+    private String message;
+
+    /**
+     * 请求是否成功
+     */
+    private Boolean success;
+
+    /**
+     * 扩展参数
+     * <p>
+     * 用于存储一些特定 Provider 返回的额外信息
+     * </p>
+     */
+    private Map<String, Object> extra;
+
+    /**
+     * 选择项列表(用于多选场景)
+     */
+    private List<LlmChoice> choices;
+
+    /**
+     * 创建成功响应
+     *
+     * @param content 响应内容
+     * @return 响应实例
+     */
+    public static LlmResponse success(String content) {
+        return LlmResponse.builder()
+                .content(content)
+                .success(true)
+                .code(200)
+                .message("Success")
+                .build();
+    }
+
+    /**
+     * 创建失败响应
+     *
+     * @param message 错误消息
+     * @return 响应实例
+     */
+    public static LlmResponse error(String message) {
+        return LlmResponse.builder()
+                .success(false)
+                .code(500)
+                .message(message)
+                .build();
+    }
+
+    /**
+     * 选择项
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    public static class LlmChoice {
+
+        /**
+         * 选择项内容
+         */
+        private String content;
+
+        /**
+         * 结束原因
+         */
+        private String finishReason;
+
+        /**
+         * 扩展参数
+         */
+        private Map<String, Object> extra;
+
+    }
+
+}

+ 77 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/enums/LlmProviderEnum.java

@@ -0,0 +1,77 @@
+package cn.start.tz.framework.llm.enums;
+
+import cn.start.tz.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * LLM Provider 枚举
+ * <p>
+ * 定义支持的大语言模型服务提供商
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Getter
+@AllArgsConstructor
+public enum LlmProviderEnum implements ArrayValuable<String> {
+
+    /**
+     * 通义千问
+     */
+    QWEN("qwen", "通义千问"),
+
+    /**
+     * 智谱 AI
+     */
+    ZHIPU("zhipu", "智谱AI"),
+
+    /**
+     * OpenAI
+     */
+    OPENAI("openai", "OpenAI");
+
+    public static final String[] ARRAYS = Arrays.stream(values()).map(LlmProviderEnum::getCode).toArray(String[]::new);
+
+    /**
+     * 编码
+     */
+    private final String code;
+
+    /**
+     * 名称
+     */
+    private final String name;
+
+    @Override
+    public String[] array() {
+        return ARRAYS;
+    }
+
+    /**
+     * 根据编码获取枚举
+     *
+     * @param code 编码
+     * @return 枚举实例
+     */
+    public static LlmProviderEnum fromCode(String code) {
+        return Arrays.stream(values())
+                .filter(e -> e.getCode().equals(code))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 校验编码是否有效
+     *
+     * @param code 编码
+     * @return 是否有效
+     */
+    public static boolean isValid(String code) {
+        return Arrays.stream(values())
+                .anyMatch(e -> e.getCode().equals(code));
+    }
+
+}

+ 77 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/enums/LlmRoleEnum.java

@@ -0,0 +1,77 @@
+package cn.start.tz.framework.llm.enums;
+
+import cn.start.tz.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * LLM 消息角色枚举
+ * <p>
+ * 定义对话消息的角色类型
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Getter
+@AllArgsConstructor
+public enum LlmRoleEnum implements ArrayValuable<String> {
+
+    /**
+     * 系统角色,用于设定 AI 的行为和背景
+     */
+    SYSTEM("system", "系统"),
+
+    /**
+     * 用户角色,表示用户发送的消息
+     */
+    USER("user", "用户"),
+
+    /**
+     * 助手角色,表示 AI 返回的消息
+     */
+    ASSISTANT("assistant", "助手");
+
+    public static final String[] ARRAYS = Arrays.stream(values()).map(LlmRoleEnum::getCode).toArray(String[]::new);
+
+    /**
+     * 编码
+     */
+    private final String code;
+
+    /**
+     * 名称
+     */
+    private final String name;
+
+    @Override
+    public String[] array() {
+        return ARRAYS;
+    }
+
+    /**
+     * 根据编码获取枚举
+     *
+     * @param code 编码
+     * @return 枚举实例
+     */
+    public static LlmRoleEnum fromCode(String code) {
+        return Arrays.stream(values())
+                .filter(e -> e.getCode().equals(code))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 校验编码是否有效
+     *
+     * @param code 编码
+     * @return 是否有效
+     */
+    public static boolean isValid(String code) {
+        return Arrays.stream(values())
+                .anyMatch(e -> e.getCode().equals(code));
+    }
+
+}

+ 177 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/provider/openai/OpenAiLlmClient.java

@@ -0,0 +1,177 @@
+package cn.start.tz.framework.llm.provider.openai;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import cn.start.tz.framework.llm.LlmClient;
+import cn.start.tz.framework.llm.dto.LlmChatRequest;
+import cn.start.tz.framework.llm.dto.LlmResponse;
+import cn.start.tz.framework.llm.enums.LlmProviderEnum;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * OpenAI LLM 客户端实现
+ * <p>
+ * 基于 OpenAI API 实现 LLM 服务调用
+ * </p>
+ * <p>
+ * API 文档: https://platform.openai.com/docs/api-reference/chat
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Slf4j
+@AllArgsConstructor
+public class OpenAiLlmClient implements LlmClient {
+
+    private static final String DEFAULT_API_URL = "https://api.openai.com/v1/chat/completions";
+    private static final String DEFAULT_MODEL = "gpt-3.5-turbo";
+
+    private final String apiKey;
+    private final String model;
+    private final String apiUrl;
+    private final ObjectMapper objectMapper;
+
+    /**
+     * 构造函数
+     *
+     * @param apiKey API Key
+     */
+    public OpenAiLlmClient(String apiKey) {
+        this(apiKey, DEFAULT_MODEL, DEFAULT_API_URL, new ObjectMapper());
+    }
+
+    /**
+     * 构造函数(支持自定义 API 地址)
+     *
+     * @param apiKey API Key
+     * @param apiUrl API 地址(用于兼容其他 OpenAI 兼容服务)
+     */
+    public OpenAiLlmClient(String apiKey, String apiUrl) {
+        this(apiKey, DEFAULT_MODEL, apiUrl, new ObjectMapper());
+    }
+
+    @Override
+    public LlmResponse chat(LlmChatRequest request) {
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = buildRequestBody(request);
+
+            // 发送请求
+            HttpResponse response = HttpRequest.post(apiUrl)
+                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
+                    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+                    .body(JSONUtil.toJsonStr(requestBody))
+                    .execute();
+
+            String body = response.body();
+            int status = response.getStatus();
+
+            if (status == 200) {
+                return parseSuccessResponse(body);
+            } else {
+                log.error("[OpenAiLlmClient] 调用失败,状态码: {}, 响应: {}", status, body);
+                return LlmResponse.error("调用失败: " + body);
+            }
+        } catch (Exception e) {
+            log.error("[OpenAiLlmClient] 调用异常", e);
+            return LlmResponse.error("调用异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 构建请求体
+     *
+     * @param request 聊天请求
+     * @return 请求体 Map
+     */
+    private Map<String, Object> buildRequestBody(LlmChatRequest request) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("model", StrUtil.isNotBlank(request.getModel()) ? request.getModel() : this.model);
+
+        // 转换消息格式
+        List<Map<String, String>> messages = request.getMessages().stream()
+                .map(msg -> {
+                    Map<String, String> map = new HashMap<>();
+                    map.put("role", msg.getRole().getCode());
+                    map.put("content", msg.getContent());
+                    return map;
+                })
+                .toList();
+
+        body.put("messages", messages);
+
+        // 添加参数
+        if (request.getTemperature() != null) {
+            body.put("temperature", request.getTemperature());
+        }
+        if (request.getMaxTokens() != null) {
+            body.put("max_tokens", request.getMaxTokens());
+        }
+        if (request.getTopP() != null) {
+            body.put("top_p", request.getTopP());
+        }
+        if (request.getStop() != null && !request.getStop().isEmpty()) {
+            body.put("stop", request.getStop());
+        }
+
+        return body;
+    }
+
+    /**
+     * 解析成功响应
+     *
+     * @param body 响应体
+     * @return LLM 响应
+     */
+    private LlmResponse parseSuccessResponse(String body) {
+        try {
+            Map<String, Object> response = JSONUtil.toBean(body, Map.class);
+            List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
+
+            if (choices != null && !choices.isEmpty()) {
+                Map<String, Object> firstChoice = choices.get(0);
+                Map<String, Object> message = (Map<String, Object>) firstChoice.get("message");
+                String content = (String) message.get("content");
+
+                Map<String, Object> usage = (Map<String, Object>) response.get("usage");
+
+            return LlmResponse.builder()
+                    .content(content)
+                    .model((String) response.get("model"))
+                    .promptTicks(usage != null ? ((Number) usage.get("prompt_tokens")).intValue() : null)
+                    .completionTicks(usage != null ? ((Number) usage.get("completion_tokens")).intValue() : null)
+                    .totalTicks(usage != null ? ((Number) usage.get("total_tokens")).intValue() : null)
+                    .success(true)
+                    .code(200)
+                    .message("Success")
+                    .build();
+            }
+
+            return LlmResponse.error("解析响应失败");
+        } catch (Exception e) {
+            log.error("[OpenAiLlmClient] 解析响应异常", e);
+            return LlmResponse.error("解析响应异常: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public String getProvider() {
+        return LlmProviderEnum.OPENAI.getCode();
+    }
+
+    @Override
+    public String getDefaultModel() {
+        return DEFAULT_MODEL;
+    }
+
+}

+ 162 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/provider/qwen/QwenLlmClient.java

@@ -0,0 +1,162 @@
+package cn.start.tz.framework.llm.provider.qwen;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import cn.start.tz.framework.llm.LlmClient;
+import cn.start.tz.framework.llm.dto.LlmChatRequest;
+import cn.start.tz.framework.llm.dto.LlmResponse;
+import cn.start.tz.framework.llm.dto.LlmResponse.LlmChoice;
+import cn.start.tz.framework.llm.enums.LlmProviderEnum;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通义千问 LLM 客户端实现
+ * <p>
+ * 基于通义千问 API 实现 LLM 服务调用
+ * </p>
+ * <p>
+ * API 文档: https://help.aliyun.com/zh/dashscope/developer-reference/api-details
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Slf4j
+@AllArgsConstructor
+public class QwenLlmClient implements LlmClient {
+
+    private static final String API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
+    private static final String DEFAULT_MODEL = "qwen-turbo";
+
+    private final String apiKey;
+    private final String model;
+    private final ObjectMapper objectMapper;
+
+    /**
+     * 构造函数
+     *
+     * @param apiKey API Key
+     */
+    public QwenLlmClient(String apiKey) {
+        this(apiKey, DEFAULT_MODEL, new ObjectMapper());
+    }
+
+    @Override
+    public LlmResponse chat(LlmChatRequest request) {
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = buildRequestBody(request);
+
+            // 发送请求
+            HttpResponse response = HttpRequest.post(API_URL)
+                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
+                    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+                    .body(JSONUtil.toJsonStr(requestBody))
+                    .execute();
+
+            String body = response.body();
+            int status = response.getStatus();
+
+            if (status == 200) {
+                return parseSuccessResponse(body);
+            } else {
+                log.error("[QwenLlmClient] 调用失败,状态码: {}, 响应: {}", status, body);
+                return LlmResponse.error("调用失败: " + body);
+            }
+        } catch (Exception e) {
+            log.error("[QwenLlmClient] 调用异常", e);
+            return LlmResponse.error("调用异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 构建请求体
+     *
+     * @param request 聊天请求
+     * @return 请求体 Map
+     */
+    private Map<String, Object> buildRequestBody(LlmChatRequest request) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("model", StrUtil.isNotBlank(request.getModel()) ? request.getModel() : this.model);
+
+        // 转换消息格式
+        List<Map<String, String>> messages = request.getMessages().stream()
+                .map(msg -> {
+                    Map<String, String> map = new HashMap<>();
+                    map.put("role", msg.getRole().getCode());
+                    map.put("content", msg.getContent());
+                    return map;
+                })
+                .toList();
+
+        Map<String, Object> input = new HashMap<>();
+        input.put("messages", messages);
+        body.put("input", input);
+
+        // 添加参数
+        Map<String, Object> parameters = new HashMap<>();
+        if (request.getTemperature() != null) {
+            parameters.put("temperature", request.getTemperature());
+        }
+        if (request.getMaxTokens() != null) {
+            parameters.put("max_tokens", request.getMaxTokens());
+        }
+        if (request.getTopP() != null) {
+            parameters.put("top_p", request.getTopP());
+        }
+        if (!parameters.isEmpty()) {
+            body.put("parameters", parameters);
+        }
+
+        return body;
+    }
+
+    /**
+     * 解析成功响应
+     *
+     * @param body 响应体
+     * @return LLM 响应
+     */
+    private LlmResponse parseSuccessResponse(String body) {
+        try {
+            Map<String, Object> response = JSONUtil.toBean(body, Map.class);
+            Map<String, Object> output = (Map<String, Object>) response.get("output");
+
+            if (output != null) {
+                String content = (String) output.get("text");
+                return LlmResponse.builder()
+                        .content(content)
+                        .model((String) response.get("model"))
+                        .success(true)
+                        .code(200)
+                        .message("Success")
+                        .build();
+            }
+
+            return LlmResponse.error("解析响应失败");
+        } catch (Exception e) {
+            log.error("[QwenLlmClient] 解析响应异常", e);
+            return LlmResponse.error("解析响应异常: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public String getProvider() {
+        return LlmProviderEnum.QWEN.getCode();
+    }
+
+    @Override
+    public String getDefaultModel() {
+        return DEFAULT_MODEL;
+    }
+
+}

+ 163 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/java/cn/start/tz/framework/llm/provider/zhipu/ZhipuLlmClient.java

@@ -0,0 +1,163 @@
+package cn.start.tz.framework.llm.provider.zhipu;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import cn.start.tz.framework.llm.LlmClient;
+import cn.start.tz.framework.llm.dto.LlmChatRequest;
+import cn.start.tz.framework.llm.dto.LlmResponse;
+import cn.start.tz.framework.llm.enums.LlmProviderEnum;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 智谱 AI LLM 客户端实现
+ * <p>
+ * 基于智谱 AI API 实现 LLM 服务调用
+ * </p>
+ * <p>
+ * API 文档: https://open.bigmodel.cn/dev/api
+ * </p>
+ *
+ * @author Tz Framework
+ */
+@Slf4j
+@AllArgsConstructor
+public class ZhipuLlmClient implements LlmClient {
+
+    private static final String API_URL = "https://open.bigmodel.cn/api/paas/v4/chat/completions";
+    private static final String DEFAULT_MODEL = "glm-4";
+
+    private final String apiKey;
+    private final String model;
+    private final ObjectMapper objectMapper;
+
+    /**
+     * 构造函数
+     *
+     * @param apiKey API Key
+     */
+    public ZhipuLlmClient(String apiKey) {
+        this(apiKey, DEFAULT_MODEL, new ObjectMapper());
+    }
+
+    @Override
+    public LlmResponse chat(LlmChatRequest request) {
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = buildRequestBody(request);
+
+            // 发送请求
+            HttpResponse response = HttpRequest.post(API_URL)
+                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
+                    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+                    .body(JSONUtil.toJsonStr(requestBody))
+                    .execute();
+
+            String body = response.body();
+            int status = response.getStatus();
+
+            if (status == 200) {
+                return parseSuccessResponse(body);
+            } else {
+                log.error("[ZhipuLlmClient] 调用失败,状态码: {}, 响应: {}", status, body);
+                return LlmResponse.error("调用失败: " + body);
+            }
+        } catch (Exception e) {
+            log.error("[ZhipuLlmClient] 调用异常", e);
+            return LlmResponse.error("调用异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 构建请求体
+     *
+     * @param request 聊天请求
+     * @return 请求体 Map
+     */
+    private Map<String, Object> buildRequestBody(LlmChatRequest request) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("model", StrUtil.isNotBlank(request.getModel()) ? request.getModel() : this.model);
+
+        // 转换消息格式
+        List<Map<String, String>> messages = request.getMessages().stream()
+                .map(msg -> {
+                    Map<String, String> map = new HashMap<>();
+                    map.put("role", msg.getRole().getCode());
+                    map.put("content", msg.getContent());
+                    return map;
+                })
+                .toList();
+
+        body.put("messages", messages);
+
+        // 添加参数
+        if (request.getTemperature() != null) {
+            body.put("temperature", request.getTemperature());
+        }
+        if (request.getMaxTokens() != null) {
+            body.put("max_tokens", request.getMaxTokens());
+        }
+        if (request.getTopP() != null) {
+            body.put("top_p", request.getTopP());
+        }
+
+        return body;
+    }
+
+    /**
+     * 解析成功响应
+     *
+     * @param body 响应体
+     * @return LLM 响应
+     */
+    private LlmResponse parseSuccessResponse(String body) {
+        try {
+            Map<String, Object> response = JSONUtil.toBean(body, Map.class);
+            List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
+
+            if (choices != null && !choices.isEmpty()) {
+                Map<String, Object> firstChoice = choices.get(0);
+                Map<String, Object> message = (Map<String, Object>) firstChoice.get("message");
+                String content = (String) message.get("content");
+
+                Map<String, Object> usage = (Map<String, Object>) response.get("usage");
+
+                return LlmResponse.builder()
+                        .content(content)
+                        .model((String) response.get("model"))
+                        .promptTicks(usage != null ? ((Number) usage.get("prompt_tokens")).intValue() : null)
+                        .completionTicks(usage != null ? ((Number) usage.get("completion_tokens")).intValue() : null)
+                        .totalTicks(usage != null ? ((Number) usage.get("total_tokens")).intValue() : null)
+                        .success(true)
+                        .code(200)
+                        .message("Success")
+                        .build();
+            }
+
+            return LlmResponse.error("解析响应失败");
+        } catch (Exception e) {
+            log.error("[ZhipuLlmClient] 解析响应异常", e);
+            return LlmResponse.error("解析响应异常: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public String getProvider() {
+        return LlmProviderEnum.ZHIPU.getCode();
+    }
+
+    @Override
+    public String getDefaultModel() {
+        return DEFAULT_MODEL;
+    }
+
+}

+ 1 - 0
tz-framework/tz-spring-boot-starter-llm/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.start.tz.framework.llm.LlmAutoConfiguration

+ 0 - 42
tz-framework/tz-spring-boot-starter-monitor/src/main/java/cn/start/tz/framework/tracer/core/annotation/BizTrace.java

@@ -1,42 +0,0 @@
-package cn.start.tz.framework.tracer.core.annotation;
-
-import java.lang.annotation.*;
-
-/**
- * 打印业务编号 / 业务类型注解
- *
- * 使用时,需要设置 SkyWalking OAP Server 的 application.yaml 配置文件,修改 SW_SEARCHABLE_TAG_KEYS 配置项,
- * 增加 biz.type 和 biz.id 两值,然后重启 SkyWalking OAP Server 服务器。
- *
- * @author 麻薯
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-@Inherited
-public @interface BizTrace {
-
-    /**
-     * 业务编号 tag 名
-     */
-    String ID_TAG = "biz.id";
-    /**
-     * 业务类型 tag 名
-     */
-    String TYPE_TAG = "biz.type";
-
-    /**
-     * @return 操作名
-     */
-    String operationName() default "";
-
-    /**
-     * @return 业务编号
-     */
-    String id();
-
-    /**
-     * @return 业务类型
-     */
-    String type();
-
-}

+ 0 - 151
tz-framework/tz-spring-boot-starter-mq/src/main/java/cn/start/tz/framework/mq/redis/config/TzRedisMQConsumerAutoConfiguration.java

@@ -1,151 +0,0 @@
-package cn.start.tz.framework.mq.redis.config;
-
-import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.system.SystemUtil;
-import cn.start.tz.framework.common.enums.DocumentEnum;
-import cn.start.tz.framework.mq.redis.core.RedisMQTemplate;
-import cn.start.tz.framework.mq.redis.core.job.RedisPendingMessageResendJob;
-import cn.start.tz.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
-import cn.start.tz.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
-import cn.start.tz.framework.redis.config.TzRedisAutoConfiguration;
-import lombok.extern.slf4j.Slf4j;
-import org.redisson.api.RedissonClient;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.data.redis.connection.RedisServerCommands;
-import org.springframework.data.redis.connection.stream.Consumer;
-import org.springframework.data.redis.connection.stream.ObjectRecord;
-import org.springframework.data.redis.connection.stream.ReadOffset;
-import org.springframework.data.redis.connection.stream.StreamOffset;
-import org.springframework.data.redis.core.RedisCallback;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.listener.ChannelTopic;
-import org.springframework.data.redis.listener.RedisMessageListenerContainer;
-import org.springframework.data.redis.stream.StreamMessageListenerContainer;
-import org.springframework.scheduling.annotation.EnableScheduling;
-
-import java.util.List;
-import java.util.Properties;
-
-/**
- * Redis 消息队列 Consumer 配置类
- *
- * @author 芋道源码
- */
-@Slf4j
-@EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
-@AutoConfiguration(after = TzRedisAutoConfiguration.class)
-public class TzRedisMQConsumerAutoConfiguration {
-
-    /**
-     * 创建 Redis Pub/Sub 广播消费的容器
-     */
-    @Bean
-    @ConditionalOnBean(AbstractRedisChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
-    public RedisMessageListenerContainer redisMessageListenerContainer(
-            RedisMQTemplate redisMQTemplate, List<AbstractRedisChannelMessageListener<?>> listeners) {
-        // 创建 RedisMessageListenerContainer 对象
-        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
-        // 设置 RedisConnection 工厂。
-        container.setConnectionFactory(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory());
-        // 添加监听器
-        listeners.forEach(listener -> {
-            listener.setRedisMQTemplate(redisMQTemplate);
-            container.addMessageListener(listener, new ChannelTopic(listener.getChannel()));
-            log.info("[redisMessageListenerContainer][注册 Channel({}) 对应的监听器({})]",
-                    listener.getChannel(), listener.getClass().getName());
-        });
-        return container;
-    }
-
-    /**
-     * 创建 Redis Stream 重新消费的任务
-     */
-    @Bean
-    @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
-    public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractRedisStreamMessageListener<?>> listeners,
-                                                                     RedisMQTemplate redisTemplate,
-                                                                     @Value("${spring.application.name}") String groupName,
-                                                                     RedissonClient redissonClient) {
-        return new RedisPendingMessageResendJob(listeners, redisTemplate, groupName, redissonClient);
-    }
-
-    /**
-     * 创建 Redis Stream 集群消费的容器
-     *
-     * 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a>
-     */
-    @Bean(initMethod = "start", destroyMethod = "stop")
-    @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
-    public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
-            RedisMQTemplate redisMQTemplate, List<AbstractRedisStreamMessageListener<?>> listeners) {
-        RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
-        checkRedisVersion(redisTemplate);
-        // 第一步,创建 StreamMessageListenerContainer 容器
-        // 创建 options 配置
-        StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
-                StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
-                        .batchSize(10) // 一次性最多拉取多少条消息
-                        .targetType(String.class) // 目标类型。统一使用 String,通过自己封装的 AbstractStreamMessageListener 去反序列化
-                        .build();
-        // 创建 container 对象
-        StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
-                StreamMessageListenerContainer.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
-
-        // 第二步,注册监听器,消费对应的 Stream 主题
-        String consumerName = buildConsumerName();
-        listeners.parallelStream().forEach(listener -> {
-            log.info("[redisStreamMessageListenerContainer][开始注册 StreamKey({}) 对应的监听器({})]",
-                    listener.getStreamKey(), listener.getClass().getName());
-            // 创建 listener 对应的消费者分组
-            try {
-                redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
-            } catch (Exception ignore) {
-            }
-            // 设置 listener 对应的 redisTemplate
-            listener.setRedisMQTemplate(redisMQTemplate);
-            // 创建 Consumer 对象
-            Consumer consumer = Consumer.from(listener.getGroup(), consumerName);
-            // 设置 Consumer 消费进度,以最小消费进度为准
-            StreamOffset<String> streamOffset = StreamOffset.create(listener.getStreamKey(), ReadOffset.lastConsumed());
-            // 设置 Consumer 监听
-            StreamMessageListenerContainer.StreamReadRequestBuilder<String> builder = StreamMessageListenerContainer.StreamReadRequest
-                    .builder(streamOffset).consumer(consumer)
-                    .autoAcknowledge(false) // 不自动 ack
-                    .cancelOnError(throwable -> false); // 默认配置,发生异常就取消消费,显然不符合预期;因此,我们设置为 false
-            container.register(builder.build(), listener);
-            log.info("[redisStreamMessageListenerContainer][完成注册 StreamKey({}) 对应的监听器({})]",
-                    listener.getStreamKey(), listener.getClass().getName());
-        });
-        return container;
-    }
-
-    /**
-     * 构建消费者名字,使用本地 IP + 进程编号的方式。
-     * 参考自 RocketMQ clientId 的实现
-     *
-     * @return 消费者名字
-     */
-    private static String buildConsumerName() {
-        return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
-    }
-
-    /**
-     * 校验 Redis 版本号,是否满足最低的版本号要求!
-     */
-    private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {
-        // 获得 Redis 版本
-        Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
-        String version = MapUtil.getStr(info, "redis_version");
-        // 校验最低版本必须大于等于 5.0.0
-        int majorVersion = Integer.parseInt(StrUtil.subBefore(version, '.', false));
-        if (majorVersion < 5) {
-            throw new IllegalStateException(StrUtil.format("您当前的 Redis 版本为 {},小于最低要求的 5.0.0 版本!" +
-                    "请参考 {} 文档进行安装。", version, DocumentEnum.REDIS_INSTALL.getUrl()));
-        }
-    }
-
-}

+ 1 - 0
tz-framework/tz-spring-boot-starter-mybatis/src/main/java/cn/start/tz/framework/mybatis/core/dataobject/BaseDO.java

@@ -51,6 +51,7 @@ public abstract class BaseDO implements Serializable, TransPojo {
      * 是否删除
      */
     @TableLogic
+    @TableField(fill = FieldFill.INSERT)
     private Boolean deleted;
 
 }

+ 5 - 0
tz-framework/tz-spring-boot-starter-mybatis/src/main/java/cn/start/tz/framework/mybatis/core/handler/DefaultDBFieldHandler.java

@@ -41,6 +41,11 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
             if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
                 baseDO.setUpdater(userId);
             }
+
+            // deleted 为空,则设置为 false (未删除)
+            if (Objects.isNull(baseDO.getDeleted())) {
+                baseDO.setDeleted(false);
+            }
         }
     }
 

+ 0 - 63
tz-framework/tz-spring-boot-starter-protection/src/main/java/cn/start/tz/framework/idempotent/core/annotation/Idempotent.java

@@ -1,63 +0,0 @@
-package cn.start.tz.framework.idempotent.core.annotation;
-
-import cn.start.tz.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
-import cn.start.tz.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
-import cn.start.tz.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
-import cn.start.tz.framework.idempotent.core.keyresolver.impl.UserIdempotentKeyResolver;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 幂等注解
- *
- * @author 芋道源码
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Idempotent {
-
-    /**
-     * 幂等的超时时间,默认为 1 秒
-     *
-     * 注意,如果执行时间超过它,请求还是会进来
-     */
-    int timeout() default 1;
-    /**
-     * 时间单位,默认为 SECONDS 秒
-     */
-    TimeUnit timeUnit() default TimeUnit.SECONDS;
-
-    /**
-     * 提示信息,正在执行中的提示
-     */
-    String message() default "重复请求,请稍后重试";
-
-    /**
-     * 使用的 Key 解析器
-     *
-     * @see DefaultIdempotentKeyResolver 全局级别
-     * @see UserIdempotentKeyResolver 用户级别
-     * @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算
-     */
-    Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;
-    /**
-     * 使用的 Key 参数
-     */
-    String keyArg() default "";
-
-    /**
-     * 删除 Key,当发生异常时候
-     *
-     * 问题:为什么发生异常时,需要删除 Key 呢?
-     * 回答:发生异常时,说明业务发生错误,此时需要删除 Key,避免下次请求无法正常执行。
-     *
-     * 问题:为什么不搞 deleteWhenSuccess 执行成功时,需要删除 Key 呢?
-     * 回答:这种情况下,本质上是分布式锁,推荐使用 @Lock4j 注解
-     */
-    boolean deleteKeyWhenException() default true;
-
-}

+ 0 - 62
tz-framework/tz-spring-boot-starter-protection/src/main/java/cn/start/tz/framework/ratelimiter/core/annotation/RateLimiter.java

@@ -1,62 +0,0 @@
-package cn.start.tz.framework.ratelimiter.core.annotation;
-
-import cn.start.tz.framework.common.exception.enums.GlobalErrorCodeConstants;
-import cn.start.tz.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
-import cn.start.tz.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
-import cn.start.tz.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver;
-import cn.start.tz.framework.ratelimiter.core.keyresolver.impl.DefaultRateLimiterKeyResolver;
-import cn.start.tz.framework.ratelimiter.core.keyresolver.impl.ServerNodeRateLimiterKeyResolver;
-import cn.start.tz.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 限流注解
- *
- * @author 芋道源码
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RateLimiter {
-
-    /**
-     * 限流的时间,默认为 1 秒
-     */
-    int time() default 1;
-    /**
-     * 时间单位,默认为 SECONDS 秒
-     */
-    TimeUnit timeUnit() default TimeUnit.SECONDS;
-
-    /**
-     * 限流次数
-     */
-    int count() default 100;
-
-    /**
-     * 提示信息,请求过快的提示
-     *
-     * @see GlobalErrorCodeConstants#TOO_MANY_REQUESTS
-     */
-    String message() default ""; // 为空时,使用 TOO_MANY_REQUESTS 错误提示
-
-    /**
-     * 使用的 Key 解析器
-     *
-     * @see DefaultRateLimiterKeyResolver 全局级别
-     * @see UserRateLimiterKeyResolver 用户 ID 级别
-     * @see ClientIpRateLimiterKeyResolver 用户 IP 级别
-     * @see ServerNodeRateLimiterKeyResolver 服务器 Node 级别
-     * @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算
-     */
-    Class<? extends RateLimiterKeyResolver> keyResolver() default DefaultRateLimiterKeyResolver.class;
-    /**
-     * 使用的 Key 参数
-     */
-    String keyArg() default "";
-
-}

+ 0 - 59
tz-framework/tz-spring-boot-starter-protection/src/main/java/cn/start/tz/framework/signature/core/annotation/ApiSignature.java

@@ -1,59 +0,0 @@
-package cn.start.tz.framework.signature.core.annotation;
-
-import cn.start.tz.framework.common.exception.enums.GlobalErrorCodeConstants;
-
-import java.lang.annotation.*;
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * HTTP API 签名注解
- *
- * @author Zhougang
- */
-@Inherited
-@Documented
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ApiSignature {
-
-    /**
-     * 同一个请求多长时间内有效 默认 60 秒
-     */
-    int timeout() default 60;
-
-    /**
-     * 时间单位,默认为 SECONDS 秒
-     */
-    TimeUnit timeUnit() default TimeUnit.SECONDS;
-
-    // ========================== 签名参数 ==========================
-
-    /**
-     * 提示信息,签名失败的提示
-     *
-     * @see GlobalErrorCodeConstants#BAD_REQUEST
-     */
-    String message() default "签名不正确"; // 为空时,使用 BAD_REQUEST 错误提示
-
-    /**
-     * 签名字段:appId 应用ID
-     */
-    String appId() default "appId";
-
-    /**
-     * 签名字段:timestamp 时间戳
-     */
-    String timestamp() default "timestamp";
-
-    /**
-     * 签名字段:nonce 随机数,10 位以上
-     */
-    String nonce() default "nonce";
-
-    /**
-     * sign 客户端签名
-     */
-    String sign() default "sign";
-
-}

+ 8 - 0
tz-framework/tz-spring-boot-starter-security/pom.xml

@@ -73,6 +73,14 @@
             <groupId>io.github.mouzt</groupId>
             <artifactId>bizlog-sdk</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-alibaba-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-redis</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 2 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/config/TzSecurityAutoConfiguration.java

@@ -15,6 +15,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfigureOrder;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
@@ -32,6 +33,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
 @AutoConfiguration
 @AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
 @EnableConfigurationProperties(SecurityProperties.class)
+@ComponentScan({"cn.start.tz.framework.security.core.service"}) // 扫描 service 包
 public class TzSecurityAutoConfiguration {
 
     @Resource

+ 51 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/LoginUser.java

@@ -21,8 +21,24 @@ public class LoginUser {
     public static final String INFO_KEY_NICKNAME = "nickname";
     public static final String INFO_KEY_ID = "userId";
     public static final String INFO_KEY_DEPT_ID = "deptId";
+
+    // 单位类型   1个人用户  2使用单位 3 生产单位  4 监管单位
+    public static final String INFO_KEY_UNIT_TYPE = "unitType";
+    public static final String INFO_KEY_UNIT_ID = "unitId";
+    public static final String INFO_KEY_UNIT_CONTACT = "unitContact";
+    public static final String INFO_KEY_UNIT = "unit";
+
+    public static final String INFO_KEY_UNIT_ACCOUNT_ID = "unitAccountId";
+
     public static final String MOBILE = "mobile";
     public static final String IS_SUPER_ADMIN = "is_super_admin";
+    // 是否小程序审核人员
+    public static final String IS_AUDIT_ADMIN = "is_audit_admin";
+
+    // 登录类型常量
+    public static final String LOGIN_TYPE = "loginType";
+    public static final String LOGIN_TYPE_LOCAL = "local";
+    public static final String LOGIN_TYPE_CAS = "cas";
     /**
      * 用户编号
      */
@@ -33,6 +49,10 @@ public class LoginUser {
      * 关联 {@link UserTypeEnum}
      */
     private Integer userType;
+
+    private String unitId;
+
+    private Integer unitType;
     /**
      * 额外的用户信息
      */
@@ -59,6 +79,14 @@ public class LoginUser {
     @JsonIgnore
     private Map<String, Object> context;
 
+    public void setInfo(String key, String value) {
+        if (info == null) {
+            info = new HashMap<>();
+        }
+        info.put(key, value);
+    }
+
+
     public void setContext(String key, Object value) {
         if (context == null) {
             context = new HashMap<>();
@@ -70,4 +98,27 @@ public class LoginUser {
         return MapUtil.get(context, key, type);
     }
 
+    public Integer getUnitType() {
+        return  MapUtil.get(context,LoginUser.INFO_KEY_UNIT_TYPE, Integer.class);
+    }
+
+    public String getUnitId() {
+        return  MapUtil.get(context,LoginUser.INFO_KEY_UNIT_ID, String.class);
+    }
+
+    public String getUnitAccountId() {
+        return  MapUtil.get(context,LoginUser.INFO_KEY_UNIT_ACCOUNT_ID, String.class);
+    }
+
+    /**
+     * 获取登录类型
+     * @return "local"-本地登录, "cas"-CAS统一平台登录, 默认为"local"
+     */
+    public String getLoginType() {
+        if (info != null && info.containsKey(LOGIN_TYPE)) {
+            return info.get(LOGIN_TYPE);
+        }
+        return LOGIN_TYPE_LOCAL; // 默认为本地登录
+    }
+
 }

+ 41 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/enums/LoginUnitTypeEnum.java

@@ -0,0 +1,41 @@
+package cn.start.tz.framework.security.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum LoginUnitTypeEnum {
+
+    PERSON(1, "用户"),
+    USE_UNIT(2, "使用单位"),
+    MAKE_UNIT(3, "制造单位"),
+    SUPER_VISION_UNIT(4, "监管单位")
+    ;
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    /**
+     * 根据status获取对应的枚举实例
+     * @param status 状态值
+     * @return 对应的枚举实例
+     */
+    public static LoginUnitTypeEnum getByStatus(Integer status) {
+        if (status == null) {
+            return null;
+        }
+        for (LoginUnitTypeEnum value : values()) {
+            if (status.equals(value.getStatus())) {
+                return value;
+            }
+        }
+        return null;
+    }
+}

+ 17 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/filter/TokenAuthenticationFilter.java

@@ -74,6 +74,17 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
         // 设置当前用户
         if (loginUser != null) {
             SecurityFrameworkUtils.setLoginUser(loginUser, request);
+        } else {
+            // 如果没有登录用户,且是RPC API调用(如定时任务),则使用默认system用户
+            String requestUri = request.getRequestURI();
+            if (requestUri != null && requestUri.startsWith("/rpc-api/")) {
+                log.debug("[TokenAuthenticationFilter][RPC API调用无token,使用默认system用户,uri={}]", requestUri);
+                // 创建默认system用户
+                LoginUser systemUser = new LoginUser()
+                        .setId("1")
+                        .setUserType(2);
+                SecurityFrameworkUtils.setLoginUser(systemUser, request);
+            }
         }
         // 继续过滤链
         chain.doFilter(request, response);
@@ -82,6 +93,12 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
     private LoginUser buildLoginUserByToken(String token, Integer userType) {
         try {
             // 校验访问令牌
+            // 如果token是无效值(如 "undefined", "null"),直接返回null
+            if (StrUtil.isEmpty(token) || "undefined".equals(token) || "null".equals(token)) {
+                log.debug("接收到无效的token,跳过验证: {}", token);
+                return null;
+            }
+
             OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData();
             if (accessToken == null) {
                 return null;

+ 34 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/service/MiniAuthService.java

@@ -0,0 +1,34 @@
+package cn.start.tz.framework.security.core.service;
+
+import cn.start.tz.module.system.api.clientunit.dto.ClientUnitDTO;
+
+/**
+ * 小程序认证 Service 接口
+ *
+ * 提供平台用户的认证相关功能,如获取当前用户的单位信息等
+ *
+ * @author 特种管理员
+ */
+public interface MiniAuthService {
+
+    /**
+     * 获取当前登录用户的单位ID
+     *
+     * @return 单位ID
+     */
+    String getCurrentUserUnitId();
+
+    /**
+     * 获取当前登录用户的单位编码
+     *
+     * @return 单位编码
+     */
+    String getCurrentUserUnitCode();
+
+    /**
+     * 获取当前登录用户的单位信息
+     *
+     * @return 单位信息DTO
+     */
+    ClientUnitDTO getCurrentUserUnitInfo();
+}

+ 86 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/service/MiniAuthServiceImpl.java

@@ -0,0 +1,86 @@
+package cn.start.tz.framework.security.core.service;
+
+import cn.start.tz.framework.security.core.LoginUser;
+import cn.start.tz.module.system.api.clientunit.dto.ClientUnitDTO;
+import com.alibaba.cloud.commons.lang.StringUtils;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+import static cn.start.tz.framework.security.core.LoginUser.INFO_KEY_UNIT;
+import static cn.start.tz.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.start.tz.module.infra.enums.RedisKeyConstants.MIMI_TOKEN_KEY;
+
+/**
+ * 小程序认证 Service 实现类
+ *
+ * @author 特种管理员
+ */
+@Slf4j
+@Service
+public class MiniAuthServiceImpl implements MiniAuthService {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Resource
+    private ObjectMapper objectMapper;
+
+    @Override
+    public String getCurrentUserUnitId() {
+        try {
+            ClientUnitDTO unitInfo = getCurrentUserUnitInfo();
+            if (unitInfo != null && StringUtils.isNotBlank(unitInfo.getId())) {
+                return unitInfo.getId();
+            }
+            return "-1";
+        } catch (Exception e) {
+            log.error("获取用户单位ID失败", e);
+            return "-1";
+        }
+    }
+
+    @Override
+    public String getCurrentUserUnitCode() {
+        try {
+            ClientUnitDTO unitInfo = getCurrentUserUnitInfo();
+            if (unitInfo != null && StringUtils.isNotBlank(unitInfo.getCode())) {
+                return unitInfo.getCode();
+            }
+            return "-1";
+        } catch (Exception e) {
+            log.error("获取用户单位编码失败", e);
+            return "-1";
+        }
+    }
+
+    @Override
+    public ClientUnitDTO getCurrentUserUnitInfo() {
+        String userId = getLoginUserId();
+        String key = MIMI_TOKEN_KEY + userId;
+        String obj = stringRedisTemplate.opsForValue().get(key);
+
+        try {
+            LoginUser loginUser = objectMapper.readValue(obj, LoginUser.class);
+            Map<String, String> infoMap = loginUser.getInfo();
+            String unitJson = infoMap.get(INFO_KEY_UNIT);
+
+            log.info("unitJson:{}", unitJson);
+            if (StringUtils.isBlank(unitJson)) {
+                Map map = objectMapper.readValue(obj, Map.class);
+                unitJson = map.get("unitInfo").toString();
+            }
+
+            if (StringUtils.isNotBlank(unitJson)) {
+                return objectMapper.readValue(unitJson, ClientUnitDTO.class);
+            }
+        } catch (Exception e) {
+            log.error("获取用户单位信息失败", e);
+        }
+        return new ClientUnitDTO();
+    }
+}

+ 7 - 0
tz-framework/tz-spring-boot-starter-security/src/main/java/cn/start/tz/framework/security/core/util/SecurityFrameworkUtils.java

@@ -93,6 +93,13 @@ public class SecurityFrameworkUtils {
         return loginUser != null ? loginUser.getId() : null;
     }
 
+    @Nullable
+    public static String getLoginUserMobile() {
+        LoginUser loginUser = getLoginUser();
+        return loginUser != null ? loginUser.getInfo().get("mobile") : null;
+    }
+
+
     /**
      * 获得当前用户的昵称,从上下文中
      *

+ 0 - 65
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/apilog/core/annotation/ApiAccessLog.java

@@ -1,65 +0,0 @@
-package cn.start.tz.framework.apilog.core.annotation;
-
-import cn.start.tz.framework.apilog.core.enums.OperateTypeEnum;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * 访问日志注解
- *
- * @author 芋道源码
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ApiAccessLog {
-
-    // ========== 开关字段 ==========
-
-    /**
-     * 是否记录访问日志
-     */
-    boolean enable() default true;
-    /**
-     * 是否记录请求参数
-     *
-     * 默认记录,主要考虑请求数据一般不大。可手动设置为 false 进行关闭
-     */
-    boolean requestEnable() default true;
-    /**
-     * 是否记录响应结果
-     *
-     * 默认不记录,主要考虑响应数据可能比较大。可手动设置为 true 进行打开
-     */
-    boolean responseEnable() default false;
-    /**
-     * 敏感参数数组
-     *
-     * 添加后,请求参数、响应结果不会记录该参数
-     */
-    String[] sanitizeKeys() default {};
-
-    // ========== 模块字段 ==========
-
-    /**
-     * 操作模块
-     *
-     * 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.tags.Tag#name()} 属性
-     */
-    String operateModule() default "";
-    /**
-     * 操作名
-     *
-     * 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.Operation#summary()} 属性
-     */
-    String operateName() default "";
-    /**
-     * 操作分类
-     *
-     * 实际并不是数组,因为枚举不能设置 null 作为默认值
-     */
-    OperateTypeEnum[] operateType() default {};
-
-}

+ 0 - 103
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/apilog/core/interceptor/ApiAccessLogInterceptor.java

@@ -1,103 +0,0 @@
-package cn.start.tz.framework.apilog.core.interceptor;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.resource.ResourceUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.start.tz.framework.common.util.servlet.ServletUtils;
-import cn.start.tz.framework.common.util.spring.SpringUtils;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.StopWatch;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.HandlerInterceptor;
-
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.IntStream;
-
-/**
- * API 访问日志 Interceptor
- *
- * 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。
- *
- * @author 芋道源码
- */
-@Slf4j
-public class ApiAccessLogInterceptor implements HandlerInterceptor {
-
-    public static final String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD";
-
-    private static final String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch";
-
-    @Override
-    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
-        // 记录 HandlerMethod,提供给 ApiAccessLogFilter 使用
-        HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null;
-        if (handlerMethod != null) {
-            request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);
-        }
-
-        // 打印 request 日志
-        if (!SpringUtils.isProd()) {
-            Map<String, String> queryString = ServletUtils.getParamMap(request);
-            String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
-            if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {
-                log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
-            } else {
-                log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
-                        StrUtil.blankToDefault(requestBody, queryString.toString()));
-            }
-            // 计时
-            StopWatch stopWatch = new StopWatch();
-            stopWatch.start();
-            request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);
-            // 打印 Controller 路径
-            printHandlerMethodPosition(handlerMethod);
-        }
-        return true;
-    }
-
-    @Override
-    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
-        // 打印 response 日志
-        if (!SpringUtils.isProd()) {
-            StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
-            stopWatch.stop();
-            log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]",
-                    request.getRequestURI(), stopWatch.getTotalTimeMillis());
-        }
-    }
-
-    /**
-     * 打印 Controller 方法路径
-     */
-    private void printHandlerMethodPosition(HandlerMethod handlerMethod) {
-        if (handlerMethod == null) {
-            return;
-        }
-        Method method = handlerMethod.getMethod();
-        Class<?> clazz = method.getDeclaringClass();
-        try {
-            // 获取 method 的 lineNumber
-            List<String> clazzContents = FileUtil.readUtf8Lines(
-                    ResourceUtil.getResource(null, clazz).getPath().replace("/target/classes/", "/src/main/java/")
-                            + clazz.getSimpleName() + ".java");
-            Optional<Integer> lineNumber = IntStream.range(0, clazzContents.size())
-                    .filter(i -> clazzContents.get(i).contains(" " + method.getName() + "(")) // 简单匹配,不考虑方法重名
-                    .mapToObj(i -> i + 1) // 行号从 1 开始
-                    .findFirst();
-            if (!lineNumber.isPresent()) {
-                return;
-            }
-            // 打印结果
-            System.out.printf("\tController 方法路径:%s(%s.java:%d)\n", clazz.getName(), clazz.getSimpleName(), lineNumber.get());
-        } catch (Exception ignore) {
-            // 忽略异常。原因:仅仅打印,非重要逻辑
-        }
-    }
-
-}

+ 0 - 28
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/base/annotation/DesensitizeBy.java

@@ -1,28 +0,0 @@
-package cn.start.tz.framework.desensitize.core.base.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.handler.DesensitizationHandler;
-import cn.start.tz.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
-import java.lang.annotation.*;
-
-/**
- * 顶级脱敏注解,自定义注解需要使用此注解
- *
- * @author gaibu
- */
-@Documented
-@Target(ElementType.ANNOTATION_TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
-@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
-public @interface DesensitizeBy {
-
-    /**
-     * 脱敏处理器
-     */
-    @SuppressWarnings("rawtypes")
-    Class<? extends DesensitizationHandler> handler();
-
-}

+ 0 - 40
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/regex/annotation/EmailDesensitize.java

@@ -1,40 +0,0 @@
-package cn.start.tz.framework.desensitize.core.regex.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 邮箱脱敏注解
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = EmailDesensitizationHandler.class)
-public @interface EmailDesensitize {
-
-    /**
-     * 匹配的正则表达式
-     */
-    String regex() default "(^.)[^@]*(@.*$)";
-
-    /**
-     * 替换规则,邮箱;
-     *
-     * 比如:example@gmail.com 脱敏之后为 e****@gmail.com
-     */
-    String replacer() default "$1****$2";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 42
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/regex/annotation/RegexDesensitize.java

@@ -1,42 +0,0 @@
-package cn.start.tz.framework.desensitize.core.regex.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 正则脱敏注解
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class)
-public @interface RegexDesensitize {
-
-    /**
-     * 匹配的正则表达式(默认匹配所有)
-     */
-    String regex() default "^[\\s\\S]*$";
-
-    /**
-     * 替换规则,会将匹配到的字符串全部替换成 replacer
-     *
-     * 例如:regex=123; replacer=******
-     * 原始字符串 123456789
-     * 脱敏后字符串 ******456789
-     */
-    String replacer() default "******";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 43
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/BankCardDesensitize.java

@@ -1,43 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.BankCardDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 银行卡号
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = BankCardDesensitization.class)
-public @interface BankCardDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 6;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 2;
-
-    /**
-     * 替换规则,银行卡号; 比如:9988002866797031 脱敏之后为 998800********31
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 43
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java

@@ -1,43 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 车牌号
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = CarLicenseDesensitization.class)
-public @interface CarLicenseDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 3;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 1;
-
-    /**
-     * 替换规则,车牌号;比如:粤A66666 脱敏之后为粤A6***6
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 43
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java

@@ -1,43 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 中文名
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = ChineseNameDesensitization.class)
-public @interface ChineseNameDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 1;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 0;
-
-    /**
-     * 替换规则,中文名;比如:刘子豪脱敏之后为刘**
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 43
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java

@@ -1,43 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 固定电话
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = FixedPhoneDesensitization.class)
-public @interface FixedPhoneDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 4;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 2;
-
-    /**
-     * 替换规则,固定电话;比如:01086551122 脱敏之后为 0108*****22
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 43
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/IdCardDesensitize.java

@@ -1,43 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.IdCardDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 身份证
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = IdCardDesensitization.class)
-public @interface IdCardDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 6;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 2;
-
-    /**
-     * 替换规则,身份证号码;比如:530321199204074611 脱敏之后为 530321**********11
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 43
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/MobileDesensitize.java

@@ -1,43 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.MobileDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 手机号
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = MobileDesensitization.class)
-public @interface MobileDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 3;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 4;
-
-    /**
-     * 替换规则,手机号;比如:13248765917 脱敏之后为 132****5917
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 45
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/PasswordDesensitize.java

@@ -1,45 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.PasswordDesensitization;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 密码
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = PasswordDesensitization.class)
-public @interface PasswordDesensitize {
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 0;
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 0;
-
-    /**
-     * 替换规则,密码;
-     *
-     * 比如:123456 脱敏之后为 ******
-     */
-    String replacer() default "*";
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 0 - 47
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/desensitize/core/slider/annotation/SliderDesensitize.java

@@ -1,47 +0,0 @@
-package cn.start.tz.framework.desensitize.core.slider.annotation;
-
-import cn.start.tz.framework.desensitize.core.base.annotation.DesensitizeBy;
-import cn.start.tz.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-
-import java.lang.annotation.*;
-
-/**
- * 滑动脱敏注解
- *
- * @author gaibu
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
-@DesensitizeBy(handler = DefaultDesensitizationHandler.class)
-public @interface SliderDesensitize {
-
-    /**
-     * 后缀保留长度
-     */
-    int suffixKeep() default 0;
-
-    /**
-     * 替换规则,会将前缀后缀保留后,全部替换成 replacer
-     *
-     * 例如:prefixKeep = 1; suffixKeep = 2; replacer = "*";
-     * 原始字符串  123456
-     * 脱敏后     1***56
-     */
-    String replacer() default "*";
-
-    /**
-     * 前缀保留长度
-     */
-    int prefixKeep() default 0;
-
-    /**
-     * 是否禁用脱敏
-     *
-     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
-     */
-    String disable() default "";
-
-}

+ 5 - 5
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/web/config/TzWebAutoConfiguration.java

@@ -109,11 +109,11 @@ public class TzWebAutoConfiguration implements WebMvcConfigurer {
     /**
      * 创建 DemoFilter Bean,演示模式
      */
-    @Bean
-    @ConditionalOnProperty(value = "tz.demo", havingValue = "true")
-    public FilterRegistrationBean<DemoFilter> demoFilter() {
-        return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
-    }
+//    @Bean
+//    @ConditionalOnProperty(value = "tz.demo", havingValue = "true")
+//    public FilterRegistrationBean<DemoFilter> demoFilter() {
+//        return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
+//    }
 
     public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
         FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);

+ 26 - 0
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/web/core/util/WebFrameworkUtils.java

@@ -28,6 +28,9 @@ public class WebFrameworkUtils {
 
     public static final String HEADER_TENANT_ID = "tenant-id";
 
+    public static final String REQUEST_ATTRIBUTE_LOGIN_UNIT_TYPE = "login_user_unit_type";
+    public static final String REQUEST_ATTRIBUTE_LOGIN_UNIT_ID = "login_user_unit_id";
+
     /**
      * 终端的 Header
      *
@@ -67,6 +70,14 @@ public class WebFrameworkUtils {
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
     }
 
+    public static void setLoginUnitType(HttpServletRequest request, String userType) {
+        request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_UNIT_TYPE, userType);
+    }
+
+    public static void setLoginUnitId(HttpServletRequest request, String userType) {
+        request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_UNIT_ID, userType);
+    }
+
     /**
      * 获得当前用户的编号,从请求中
      * 注意:该方法仅限于 framework 框架使用!!!
@@ -81,6 +92,20 @@ public class WebFrameworkUtils {
         return (String) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);
     }
 
+    public static String getLoginUnitType(HttpServletRequest request) {
+        if (request == null) {
+            return null;
+        }
+        return (String) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_UNIT_TYPE);
+    }
+
+    public static String getLoginUnitId(HttpServletRequest request) {
+        if (request == null) {
+            return null;
+        }
+        return (String) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_UNIT_ID);
+    }
+
     /**
      * 获得当前用户的类型
      * 注意:该方法仅限于 web 相关的 framework 组件使用!!!
@@ -169,4 +194,5 @@ public class WebFrameworkUtils {
         return className.endsWith("Api");
     }
 
+
 }

+ 0 - 0
tz-framework/tz-spring-boot-starter-web/src/main/java/cn/start/tz/framework/xss/core/clean/JsoupXssCleaner.java


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