Selaa lähdekoodia

首次提交 AI CODING BASE

崔涛涛 2 päivää sitten
vanhempi
sitoutus
d64a45d1ed
100 muutettua tiedostoa jossa 8156 lisäystä ja 0 poistoa
  1. 17 0
      .ci-build_script.dev.sh
  2. 17 0
      .ci-build_script.sh
  3. 2 0
      .dockerignore
  4. 0 0
      .env.example
  5. 41 0
      .gitignore
  6. 119 0
      .gitlab-ci-help.sh
  7. 43 0
      .gitlab-ci.yml
  8. 175 0
      .gitlab-ci.yml.bak
  9. 3 0
      .gitmodules
  10. 109 0
      Dockerfile
  11. 88 0
      Dockerfile-jar
  12. 338 0
      TECH-DOC.md
  13. 82 0
      docker-compose.yml
  14. 18 0
      jar-deploy.sh
  15. 14 0
      jar-quick-delivery.bat
  16. 1 0
      jar-quick-delivery.sh
  17. 259 0
      mvnw
  18. 149 0
      mvnw.cmd
  19. 4 0
      opencode.json
  20. 407 0
      pom.xml
  21. 92 0
      readme.md
  22. 311 0
      settings-hz3r.xml
  23. 310 0
      settings.xml
  24. 47 0
      src/main/java/com/hz3r/aop/IVoLogInfo.java
  25. 40 0
      src/main/java/com/hz3r/aop/ProjDocItem.java
  26. 25 0
      src/main/java/com/hz3r/authorization/app/AppTokenManager.java
  27. 126 0
      src/main/java/com/hz3r/authorization/app/AppTokenManagerImpl.java
  28. 36 0
      src/main/java/com/hz3r/authorization/app/AppTokenModel.java
  29. 68 0
      src/main/java/com/hz3r/authorization/app/CheckAppToken.java
  30. 66 0
      src/main/java/com/hz3r/authorization/front/CheckFrontToken.java
  31. 25 0
      src/main/java/com/hz3r/authorization/front/FrontTokenManager.java
  32. 126 0
      src/main/java/com/hz3r/authorization/front/FrontTokenManagerImpl.java
  33. 38 0
      src/main/java/com/hz3r/authorization/front/FrontTokenModel.java
  34. 358 0
      src/main/java/com/hz3r/config/BusiRedisConfig.java
  35. 61 0
      src/main/java/com/hz3r/config/BusiRedisFastJson2JsonSerializer.java
  36. 39 0
      src/main/java/com/hz3r/config/LocalDateTimeConfiguration.java
  37. 110 0
      src/main/java/com/hz3r/config/MybatisPlusConfiguration.java
  38. 47 0
      src/main/java/com/hz3r/config/RestTemplateConfig.java
  39. 47 0
      src/main/java/com/hz3r/config/SpringDocConfig.java
  40. 58 0
      src/main/java/com/hz3r/config/WebConfiguration.java
  41. 22 0
      src/main/java/com/hz3r/config/ds/DsSelector.java
  42. 21 0
      src/main/java/com/hz3r/config/log/pattern/CustomPatternLayout.java
  43. 21 0
      src/main/java/com/hz3r/config/log/pattern/converters/CustomAppNameConverter.java
  44. 31 0
      src/main/java/com/hz3r/config/log/pattern/converters/CustomContextConverter.java
  45. 94 0
      src/main/java/com/hz3r/config/log/pattern/converters/CustomOneLineExceptionConverter.java
  46. 73 0
      src/main/java/com/hz3r/config/log/pattern/converters/CustomRequestIdConverter.java
  47. 17 0
      src/main/java/com/hz3r/config/log/pattern/converters/CustomThreadIdConverter.java
  48. 7 0
      src/main/java/com/hz3r/domain/common/CodeEnum.java
  49. 23 0
      src/main/java/com/hz3r/domain/constant/Constants.java
  50. 89 0
      src/main/java/com/hz3r/domain/constant/system/UserBizTypeEnum.java
  51. 56 0
      src/main/java/com/hz3r/domain/ctx/BusMeterBillCtxData.java
  52. 80 0
      src/main/java/com/hz3r/domain/ctx/BusiSvcCtxData.java
  53. 47 0
      src/main/java/com/hz3r/domain/entity/BaseEntity.java
  54. 47 0
      src/main/java/com/hz3r/domain/entity/EntitySearchConfig.java
  55. 12 0
      src/main/java/com/hz3r/domain/page/Paginable.java
  56. 27 0
      src/main/java/com/hz3r/domain/page/Paginator.java
  57. 30 0
      src/main/java/com/hz3r/domain/result/PageResult.java
  58. 69 0
      src/main/java/com/hz3r/domain/result/Result.java
  59. 17 0
      src/main/java/com/hz3r/domain/sort/SortMetadata.java
  60. 19 0
      src/main/java/com/hz3r/domain/sort/SortMetadataBuilder.java
  61. 8 0
      src/main/java/com/hz3r/domain/sort/Sortable.java
  62. 40 0
      src/main/java/com/hz3r/domain/sort/Sorter.java
  63. 14 0
      src/main/java/com/hz3r/domain/tree/TreeNode.java
  64. 235 0
      src/main/java/com/hz3r/domain/vo/BasePageQueryVo.java
  65. 25 0
      src/main/java/com/hz3r/domain/vo/BaseSortQueryVo.java
  66. 40 0
      src/main/java/com/hz3r/domain/vo/FileContentExchangeVo.java
  67. 11 0
      src/main/java/com/hz3r/domain/vo/MapRequest.java
  68. 10 0
      src/main/java/com/hz3r/domain/vo/MapResponse.java
  69. 85 0
      src/main/java/com/hz3r/domain/vo/PageVo.java
  70. 27 0
      src/main/java/com/hz3r/domain/vo/TreeVo.java
  71. 51 0
      src/main/java/com/hz3r/exception/ClientRuntimeException.java
  72. 24 0
      src/main/java/com/hz3r/exception/ServerRuntimeException.java
  73. 60 0
      src/main/java/com/hz3r/infrastructure/park/BusinessWebapiApplication.java
  74. 23 0
      src/main/java/com/hz3r/infrastructure/park/client/ArtemisGAClient.java
  75. 236 0
      src/main/java/com/hz3r/infrastructure/park/client/ArtemisGAClientImpl.java
  76. 11 0
      src/main/java/com/hz3r/infrastructure/park/client/HikvisionClient.java
  77. 85 0
      src/main/java/com/hz3r/infrastructure/park/client/HikvisionClientImpl.java
  78. 26 0
      src/main/java/com/hz3r/infrastructure/park/client/OaClient.java
  79. 522 0
      src/main/java/com/hz3r/infrastructure/park/client/OaClientImpl.java
  80. 160 0
      src/main/java/com/hz3r/infrastructure/park/config/DataOps.java
  81. 32 0
      src/main/java/com/hz3r/infrastructure/park/config/LoginLimitConfig.java
  82. 11 0
      src/main/java/com/hz3r/infrastructure/park/config/MybatisConfiguration.java
  83. 76 0
      src/main/java/com/hz3r/infrastructure/park/config/WebSecurityConfiguration.java
  84. 22 0
      src/main/java/com/hz3r/infrastructure/park/domain/beb/agc/Ba1attATemplateTableBEB.java
  85. 101 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/InvoiceTypeEnum.java
  86. 111 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/JeTypeEnum.java
  87. 106 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/MchCDDRecordTypeEnum.java
  88. 101 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/MchCarInventoryEeRecordEnum.java
  89. 108 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/OrderStatusEnum.java
  90. 114 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/OrderTypeEnum.java
  91. 103 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PaFreasonEnum.java
  92. 108 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/ParkingOrderingRecordTypeEnum.java
  93. 104 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayIfCodeEnum.java
  94. 101 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayServiceEnum.java
  95. 102 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayStatusEnum.java
  96. 118 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayWayEnum.java
  97. 23 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RedisKeyConstants.java
  98. 100 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RefundPaymentResultEnum.java
  99. 103 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RefundStatusEnum.java
  100. 101 0
      src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RefundTypeEnum.java

+ 17 - 0
.ci-build_script.dev.sh

@@ -0,0 +1,17 @@
+#! /bin/bash
+set -e
+
+#for i in "$@"
+#do
+#  echo "参数: $i"
+#done
+
+(podman rm business-webapi) || echo '移除容器失败!'
+(podman rmi business-webapi) || echo '移除镜像失败!'
+(podman build -t localhost/business-webapi . && echo '构建镜像成功!') || echo '构建镜像失败!'
+podman run -d \
+      -p 172.16.10.150:8105:8105 \
+      -e PROFILE=test --name business-webapi \
+      localhost/business-webapi
+
+echo '成功:构建镜像完成!'

+ 17 - 0
.ci-build_script.sh

@@ -0,0 +1,17 @@
+#! /bin/bash
+set -e
+
+#for i in "$@"
+#do
+#  echo "参数: $i"
+#done
+
+(podman rm business-webapi) || echo '移除容器失败!'
+(podman rmi business-webapi) || echo '移除镜像失败!'
+(podman build -t business-webapi . && echo '构建镜像成功!') || echo '构建镜像失败!'
+podman run -d \
+      -p 172.16.10.150:8105:8105 \
+      -e PROFILE=prod --name business-webapi \
+      business-webapi
+
+echo '成功:构建镜像完成!'

+ 2 - 0
.dockerignore

@@ -0,0 +1,2 @@
+
+**/target

+ 0 - 0
.env.example


+ 41 - 0
.gitignore

@@ -0,0 +1,41 @@
+HELP.md
+close-app
+close-app-*
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+src/main/resources/application-local.yml
+LOGS_PATH_IS_UNDEFINED/
+hik_sdk_log/
+.opencode
+
+

+ 119 - 0
.gitlab-ci-help.sh

@@ -0,0 +1,119 @@
+#!/bin/bash
+set -e
+
+MAIN_IMAGE_NAME=${PROJECT}
+
+# 定义 prepareDockerComposeFile:准备 docker-compose.yml 定义文件。
+prepareDockerComposeFile() {
+    echo "[INFO] 准备 docker-compose.yml 文件 (prepareDockerComposeFile)..."
+    sh -c "sed -i 's|image:\s*${MAIN_IMAGE_NAME}:|image: ${IMAGE_NAME}|g' docker-compose.yml"
+    echo "✅ docker-compose.yml 文件准备完成"
+}
+
+# 定义 waitDockerComposeUp:启动后操作
+waitDockerComposeUp() {
+    echo "[INFO] 检查并等待 docker compose 启动完成 (waitDockerComposeUp)..."
+    echo "[INFO] 等待 60s 启动 (waitDockerComposeUp)..."
+    sleep 60
+
+    for i in {1..3};
+    do
+        echo "[INFO] 等待 30s 启动 (waitDockerComposeUp)..."
+        sleep 30
+
+        ALL_STARTED=1
+
+        # 检查服务1 是否已启动
+        DOCKER_SVC=$((docker compose -f docker-compose.yml  ps ${PROJECT} --format '{{.Status}}' | grep -E '(up|Up)\s+[0-9]+' | tail -n 1) || echo '')
+        if [ "${DOCKER_SVC}" = '' ]; then
+            echo ${PROJECT}' 未启动!'
+            ALL_STARTED=0
+        else
+            echo ${PROJECT}' 已启动!'
+        fi
+
+        if [ ${ALL_STARTED} -eq 1 ]; then
+            echo '所有服务已启动!'
+            exit 0
+        fi
+
+    done;
+
+    echo "✅ docker compose 启动完成"
+}
+
+# 定义 triggerK8SUpdateImageVersion:触发 K8S 更新镜像版本
+triggerK8SUpdateImageVersion() {
+    echo "[INFO] 触发 k8s kind 更新镜像版本 (triggerK8SUpdateImageVersion)..."
+    echo "[INFO] 调用 K8S API 触发更新镜像版本 "${LOCAL_REGISTRY_FULL_NAME}
+
+    # 设置快捷命令
+    alias ls_di='docker image ls --format="{{.ID}} {{.Repository}}:{{.Tag}}" | grep ${LOCAL_REGISTRY_GROUP} | sort -V -r | head -n 3'
+
+    CURRENT_IMAGE=$(ls_di | head -n 2 | head -n 1)
+    echo "当前版本"$CURRENT_IMAGE
+    CURRENT_IMAGE_ID=$(echo $CURRENT_IMAGE | awk -F' ' '{print $1}')
+
+    LAST_IMAGE=$(ls_di | head -n 2 | tail -n 1)
+    echo "前一个版本"$LAST_IMAGE
+    LAST_IMAGE_ID=$(echo $LAST_IMAGE | awk -F' ' '{print $1}')
+
+    #if [ "$condition1" = true ] && ([ "$condition2" = true ] || [ "$condition2" = false ]); then
+    #    echo "条件满足"
+    #else
+    #    echo "条件不满足"
+    #fi
+    if [ ${FORCE_DEPLOY: false} = false ] && [ "${CURRENT_IMAGE}" != "${LAST_IMAGE}" ] && [ "${CURRENT_IMAGE_ID}" = "${LAST_IMAGE_ID}" ]; then
+      echo "[INFO] 没有发现递送内容存在变化,跳过发版!${LAST_IMAGE_ID} -> ${CURRENT_IMAGE_ID}"
+      echo $(ls_di)
+      echo " "
+      exit 0
+    fi
+
+    # 更新镜像版本的脚本
+    K8S_ACCESS_KEY='7bpmjzpcbw8c.y72hy3m26d7y6aw6j45nx5m66tcnws33'
+    K8S_API_BODY='{"kind":"deployments","namespace":"kube-public","name":"'${K8S_deployment_NAME}'","images":{"'${LOCAL_REGISTRY_NAME}'":"'${LOCAL_REGISTRY_FULL_NAME}'"}}'
+    curl -v --fail -X PUT \
+        -H "content-type: application/json" \
+        -H "Cookie: KuboardUsername=admin; KuboardAccessKey=${K8S_ACCESS_KEY}" \
+        -d ${K8S_API_BODY} \
+        "http://k8s-ui.hz3r.cn/kuboard-api/cluster/hz3r-k8s-kind/kind/CICDApi/admin/resource/updateImageTag"  \
+     || (echo '调用 Kuboard CI/CD API 失败!request body = '${K8S_API_BODY} && exit 9)
+
+    # curl -X PUT \
+    #     -H "content-type: application/json" \
+    #     -H "Cookie: KuboardUsername=admin; KuboardAccessKey=7bpmjzpcbw8c.y72hy3m26d7y6aw6j45nx5m66tcnws33" \
+    #     -d '{"kind":"deployments","namespace":"kube-public","name":"business-webapi","images":{"localhost:5001/business-webapi":"localhost:5001/business-webapi:yourNewVersion","localhost:5001/zjjhyiwuyq-webapi":"localhost:5001/zjjhyiwuyq-webapi:yourNewVersion"}}' \
+    #     "http://k8s-ui.hz3r.cn/kuboard-api/cluster/hz3r-k8s-kind/kind/CICDApi/admin/resource/updateImageTag"
+
+    echo "[INFO] 触发更新镜像版本成功!\n"
+}
+
+# 主逻辑:根据传入参数决定调用哪个函数
+if [ "$#" -ne 1 ]; then
+    echo "用法: $0 {prebuild|postup|k8s}"
+    echo "      prebuild 构建前准备"
+    echo "      postup docker compose up -d 后处理"
+    echo "      k8s 触发 k8s(kind|k3d) 更新镜像版本"
+    exit 1
+fi
+
+case "$1" in
+    prebuild)
+        prepareDockerComposeFile
+        ;;
+    postup)
+        waitDockerComposeUp
+        ;;
+    k8s)
+        triggerK8SUpdateImageVersion
+        ;;
+    *)
+        echo "错误: 无效参数 '$1'。请使用 'prebuild' 或 'postup'"
+        echo "用法: $0 {prebuild|postup|k8s}"
+        echo "      prebuild 构建前准备"
+        echo "      postup docker compose up -d 后处理"
+        echo "      k8s 触发 k8s(kind|k3d) 更新镜像版本"
+        exit 1
+        ;;
+esac

+ 43 - 0
.gitlab-ci.yml

@@ -0,0 +1,43 @@
+stages:
+  - build
+  - deploy
+
+build:
+  stage: build
+  tags:
+    - "srv100"
+  only:
+    - "auto-deploy"
+  script:
+    - export PROJECT='business-webapi'
+    - export VERSION='v1.0.0'
+    - export TAG=${VERSION}'.'$(date +%y%m%d%H%M)
+    - export LFS_JAR=${PROJECT}'-1.0.0.jar' # ${VERSION}'.'$(date -d '5 minutes ago' +'%y%m%d%H%M')
+    - export IMAGE_NAME='registry.cn-hangzhou.aliyuncs.com/ggcimages/hzhs-deploy:'${PROJECT}'--'
+    - (sh -c "echo 'PROJECT='${PROJECT}', TAG='${TAG}', IMAGE_NAME='${IMAGE_NAME}")
+    # 通过 docker compose 编译和构建容器镜像
+    - chmod +x .gitlab-ci-help.sh && sh ./.gitlab-ci-help.sh prebuild
+    - (sh -c "docker compose -f docker-compose.yml -p ${PROJECT} build")    || echo '构建容器失败!'${PWD}
+    # 通过 docker compose 停止在运行容器
+    - (sh -c "docker compose -f docker-compose.yml -p ${PROJECT} down ")    || echo '移除容器失败!'
+    # 通过 docker compose 重启和发布容器
+    #- (sh -c "docker compose -f docker-compose.yml -p ${PROJECT} up -d ")   || (echo '启动容器失败!' && exit -1)
+    #- (sh ./.gitlab-ci-help.sh postup)
+    # 发布容器镜像到阿里云
+    #- ( (sh -c "docker push ${IMAGE_NAME}${TAG}")  || echo '推送镜像到阿里云失败!' ) & echo '后台推送镜像到阿里云 registry !结束构建。'${PWD}
+    - export LOCAL_REGISTRY_NAME='localhost:5001/'${PROJECT}':'${TAG}
+    - docker tag ${IMAGE_NAME}${TAG} ${LOCAL_REGISTRY_NAME}
+    - export K8S_deployment_NAME='business-webapi'
+    - (sh -c "docker push ${LOCAL_REGISTRY_NAME}  || echo '推送镜像到本地 registry 失败!'")
+    - (sh ./.gitlab-ci-help.sh k8s)
+    # 提示结束构建
+    - echo '结束构建。'${PWD}
+  #artifacts:
+    #paths:
+      #- target/*.jar
+
+
+
+
+
+

+ 175 - 0
.gitlab-ci.yml.bak

@@ -0,0 +1,175 @@
+stages:
+  - build
+  - deploy
+
+build:
+  stage: build
+  tags:
+    - "srv100"
+  only:
+    - "auto-deploy"
+  script:
+    - export PROJECT='business-webapi'
+    - export TAG='v1.0.0.'$(date +%s)
+    - export IMAGE_NAME='registry.cn-hangzhou.aliyuncs.com/ggcimages/hzhs-deploy:business-webapi--'
+    - (sh -c "echo 'PROJECT='${PROJECT}', TAG='${TAG}', IMAGE_NAME='${IMAGE_NAME}")
+    # 通过 docker compose 编译和构建容器镜像
+    - chmod +x .gitlab-ci-help.sh && sh ./.gitlab-ci-help.sh
+    - (sh -c "docker compose -f docker-compose.yml -p ${PROJECT} build")    || echo '构建容器失败!'
+    # 通过 docker compose 停止在运行容器
+    - (sh -c "docker compose -f docker-compose.yml -p ${PROJECT} down ")    || echo '移除容器失败!'
+    # 通过 docker compose 重启和发布容器
+    #- (sh -c "docker compose -f docker-compose.yml -p ${PROJECT} up -d ")  || echo '启动容器失败!'
+    # 发布容器镜像到阿里云
+    - (sh -c "docker push ${IMAGE_NAME}${TAG}")    || echo '推送镜像到阿里云失败!'
+  #artifacts:
+    #paths:
+      #- target/*.jar
+
+
+#build:
+#  stage: build
+#  tags:
+#    - srv150
+#  only:
+#    - 1.0.X
+#  script:
+#    - export PROJECT='business-webapi'
+#    - export TAG='v1.0.0.'$(date +%s)
+#    - export CONTAINTER='business-webapi'
+#    - (sh -c "sudo podman stop business-webapi")   || echo '停止容器失败!'
+#    - (sh -c "sudo podman rm business-webapi" && rm /root/podman_cron.d/${CONTAINTER})  || echo '移除容器失败!'
+#    - (sh -c "sudo podman rmi business-webapi")  || echo '移除镜像失败!'
+#    - (sh -c "sudo podman build -t localhost/business-webapi . && echo '构建镜像成功!'")  || echo '构建镜像失败!'
+#    - (sh -c "sudo podman run -d -p 172.16.10.150:8105:8105 -e PROFILE=test --name ${CONTAINTER} localhost/business-webapi >${CONTAINTER}")
+#    - echo '当前容器ID:'$(cat ${CONTAINTER})
+#    - (sh -c "sudo mv ${CONTAINTER} /root/podman_cron.d/${CONTAINTER}")
+#    - (sh -c "sudo podman logs --tail 200 ${CONTAINTER}")
+#      #artifacts:
+#      #paths:
+#    #- target/*.jar
+#
+#deploy:
+#  stage: deploy
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - rm -rf /data/img-data/business-webapi.img
+#    - podman save business-webapi:latest -o /data/img-data/business-webapi.img
+#
+#webgis-geoserver-build:
+#  stage: build
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - systemctl disable container-business-webgis-geoserver.service
+#    - systemctl stop container-business-webgis-geoserver.service
+#    - rm -rf /etc/systemd/system/container-business-webgis-geoserver.service
+#    - podman rm business-webgis-geoserver
+#    - podman rmi business-webgis-geoserver:test
+#    - podman build -t business-webgis-geoserver:test -f /data/projects/business/business-webgis-geoserver/Dockerfile-test /data/projects/business/business-webgis-geoserver
+#    - podman run -d -p 172.16.10.100:8098:8888 --name business-webgis-geoserver business-webgis-geoserver:test
+#    - podman logs --tail 200 business-webgis-geoserver
+#    - podman stop -t 60 business-webgis-geoserver
+#    - podman generate systemd -n -t 60 -f business-webgis-geoserver
+#    - mv container-business-webgis-geoserver.service /etc/systemd/system
+#    - systemctl enable container-business-webgis-geoserver.service
+#    - systemctl start container-business-webgis-geoserver.service
+#    - podman logs --tail 200 business-webgis-geoserver
+#
+#webgis-geoserver-deploy:
+#  stage: deploy
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - podman build -t business-webgis-geoserver:prod -f /data/projects/business/business-webgis-geoserver/Dockerfile-prod /data/projects/business/business-webgis-geoserver
+#    - rm -rf /data/img-data/business-webgis-geoserver.img
+#    - podman save business-webgis-geoserver:prod -o /data/img-data/business-webgis-geoserver.img
+#
+#webgis-service-build:
+#  stage: build
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - systemctl disable container-business-webgis-service.service
+#    - systemctl stop container-business-webgis-service.service
+#    - rm -rf /etc/systemd/system/container-business-webgis-service.service
+#    - podman rm business-webgis-service
+#    - podman rmi business-webgis-service:test
+#    - podman build -t business-webgis-service:test -f /data/projects/business/business-webgis-service/Dockerfile-test /data/projects/business/business-webgis-service
+#    - podman run -d -p 172.16.10.100:19598:19598 --name business-webgis-service business-webgis-service:test
+#    - podman logs --tail 200 business-webgis-service
+#    - podman stop -t 60 business-webgis-service
+#    - podman generate systemd -n -t 60 -f business-webgis-service
+#    - mv container-business-webgis-service.service /etc/systemd/system
+#    - systemctl enable container-business-webgis-service.service
+#    - systemctl start container-business-webgis-service.service
+#    - podman logs --tail 200 business-webgis-service
+#
+#webgis-service-deploy:
+#  stage: deploy
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - podman build -t business-webgis-service:prod -f /data/projects/business/business-webgis-service/Dockerfile-prod /data/projects/business/business-webgis-service
+#    - rm -rf /data/img-data/business-webgis-service.img
+#    - podman save business-webgis-service:prod -o /data/img-data/business-webgis-service.img
+#
+#webgis-spatial-build:
+#  stage: build
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - systemctl disable container-business-webgis-spatial.service
+#    - systemctl stop container-business-webgis-spatial.service
+#    - rm -rf /etc/systemd/system/container-business-webgis-spatial.service
+#    - podman rm business-webgis-spatial
+#    - podman rmi business-webgis-spatial:test
+#    - podman build -t business-webgis-spatial:test -f /data/projects/business/business-webgis-spatial/Dockerfile-test /data/projects/business/business-webgis-spatial
+#    - podman run -d -p 172.16.10.100:8099:8099 --name business-webgis-spatial business-webgis-spatial:test
+#    - podman logs --tail 200 business-webgis-spatial
+#    - podman stop -t 60 business-webgis-spatial
+#    - podman generate systemd -n -t 60 -f business-webgis-spatial
+#    - mv container-business-webgis-spatial.service /etc/systemd/system
+#    - systemctl enable container-business-webgis-spatial.service
+#    - systemctl start container-business-webgis-spatial.service
+#    - podman logs --tail 200 business-webgis-spatial
+#
+#webgis-spatial-deploy:
+#  stage: deploy
+#  when: manual
+#  tags:
+#    - hz3r
+#  only:
+#    - main
+#    - 1.0.x
+#  script:
+#    - podman build -t business-webgis-spatial:prod -f /data/projects/business/business-webgis-spatial/Dockerfile-prod /data/projects/business/business-webgis-spatial
+#    - rm -rf /data/img-data/business-webgis-spatial.img
+#    - podman save business-webgis-spatial:prod -o /data/img-data/business-webgis-spatial.img
+
+

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "src/main/resources/apijs/agc"]
+	path = src/main/resources/apijs/agc
+	url = http://gitlab.hz3r.cn/carplatform/business-apijs.git

+ 109 - 0
Dockerfile

@@ -0,0 +1,109 @@
+######################### phase one #########################
+FROM registry.cn-hangzhou.aliyuncs.com/ggcimages/dev-env:maven--3-java-21-debian-bookworm AS compile_stage
+
+RUN mkdir /opt/build
+WORKDIR /opt/build
+
+# 复制项目代码到构建目录: /opt/build/pom.xml, /opt/build/src, /opt/build/src/main/java, /opt/build/src/main/resources。
+COPY . /opt/build/
+
+# 复制 MAVEN 设置文件到默认的配置目录,如果 settings.xml 依赖源配置失效,注释掉下面 COPY 代码,改用项目中 pom.xml 文件设置的依赖源。
+#COPY settings-hz3r.xml /usr/share/maven/conf/settings.xml
+
+# 清理 -> 构建 -> 打包, -X 打印调试信息。
+RUN mvn clean package -U -DskipTests --color auto
+#RUN mvn clean package -U -DskipTests --color auto -f pom.jar.xml
+#RUN mvn clean package -U -DskipTests -X -e --color auto -f pom.jar.xml
+
+######################### phase two #########################
+#FROM registry.cn-hangzhou.aliyuncs.com/ggcimages/dev-env:openjdk--21-ea-buster
+FROM registry.cn-hangzhou.aliyuncs.com/ggcimages/dev-env:eclipse-temurin--21-jre-noble
+
+WORKDIR /app
+
+ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 TZ=Asia/Shanghai
+ENV JAVA_OPTS=""
+ENV APP_NAME='zjjhyiwuyq'
+ENV LOGS_PATH=/var/log/app/${APP_NAME} LOG_FILE="app"
+RUN mkdir -p /var/log/app
+
+# 应用信息配置
+ENV APP_JAR_NAME=business-webapi-1.0.0
+ENV PROFILE=dev
+ENV API_PORT=8100
+ENV API_PATH=api
+
+# AES 等保要求等安全加密配置
+ENV AB_SECRET_KEY==zSuAV19ef1e59B3F884C9B2024920==
+ENV AB_SECRET_IV=zSUAVPT119ef9f==
+
+# 运行配置
+ENV SQL_LOGGING_LEVEL=false
+ENV SQL_AGC_LOGGING_LEVEL=false
+ENV SQL_SHARE_LOGGING_LEVEL=false
+ENV REDIS_ENABLED=false
+ENV REDIS_HOST=172.16.10.109
+ENV REDIS_PORT=6379
+ENV REDIS_PASSWD=''
+ENV MYSQL_HOST=172.16.10.109
+ENV MYSQL_PORT=3306
+ENV MYSQL_USER=zjjhyiwuyq
+ENV MYSQL_PASSWD=zjjhyiwuyq123456
+ENV JOB_SERVER_IP='snail-job.domain.com'
+ENV JOB_SERVER_PORT=50051
+ENV JOB_NAMESPACE=764d604ec6fc45f68cd92514c40e9e1a
+ENV JOB_GROUP=zjjhyiwuyq_group
+ENV JOB_TOKEN=SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj
+ENV JWT_KEY='L3jB6cR1sN5xZ8a'
+ENV LOCAL_OSS_DIRECTORY=''
+ENV OSS_BUCKET_NAME='zjjhyiwuyq'
+ENV OSS_SERVER='http://172.16.10.100:9192/'
+ENV OSS_ACCESS_KEY='CMBoDkjkRb457oxCj7ys'
+ENV OSS_ACCESS_SECRET='8uJmULkUFORiTYNfDtj3VBXEwgAgBLNmIYOFi6sN'
+ENV OSS_PUBLIC_ENTRY='http://zjjhyiwuyq.hz3r.cn/'
+ENV ENABLE_TASK_PAYMENT=true
+
+ENV LD_LIBRARY_PATH /app/lib/linux64/lib:/app/lib/linux64/lib/HCNetSDKCom:${LD_LIBRARY_PATH}
+
+
+# 拷贝依赖库到系统,使用系统默认的加载库路径,在系统的/usr/lib文件中加入你Java工程所需要使用的so文件,然后将HCNetSDKCom文件夹下的组件库也复制到/usr/lib目录,HCNetSDKCom文件夹中的组件库不要随意更换路径进行替换。CentOS 64位需拷贝/usr/lib64下。
+COPY ./lib /app/lib
+
+COPY --from=compile_stage /opt/build/target/business-webapi-1.0.0.jar .
+RUN ls -lith | grep business-webapi
+
+EXPOSE 8100
+
+
+ENTRYPOINT [ "sh", "-c", "java -Duser.timezone=GMT+08 \
+ -DLOGS_PATH=${LOGS_PATH} \
+ -DSQL_LOGGING_LEVEL=${SQL_LOGGING_LEVEL} \
+ -DSQL_AGC_LOGGING_LEVEL=${SQL_AGC_LOGGING_LEVEL} \
+ -DSQL_SHARE_LOGGING_LEVEL=${SQL_SHARE_LOGGING_LEVEL} \
+ -DREDIS_ENABLED=${REDIS_ENABLED} \
+ -DREDIS_PORT=${REDIS_PORT} \
+ -DREDIS_PASSWD=${REDIS_PASSWD} \
+ -DMYSQL_HOST=${MYSQL_HOST} \
+ -DMYSQL_PORT=${MYSQL_PORT} \
+ -DMYSQL_USER=${MYSQL_USER} \
+ -DMYSQL_PASSWD=${MYSQL_PASSWD} \
+ -DJOB_SERVER_IP=${JOB_SERVER_IP} \
+ -DJOB_SERVER_PORT=${JOB_SERVER_PORT} \
+ -DJOB_NAMESPACE=${JOB_NAMESPACE} \
+ -DJOB_GROUP=${JOB_GROUP} \
+ -DJOB_TOKEN=${JOB_TOKEN} \
+ -DJWT_KEY=${JWT_KEY} \
+ -DLOCAL_OSS_DIRECTORY=${LOCAL_OSS_DIRECTORY} \
+ -DOSS_BUCKET_NAME=${OSS_BUCKET_NAME} \
+ -DOSS_SERVER=${OSS_SERVER} \
+ -DOSS_ACCESS_KEY=${OSS_ACCESS_KEY} \
+ -DOSS_ACCESS_SECRET=${OSS_ACCESS_SECRET} \
+ -DOSS_PUBLIC_ENTRY=${OSS_PUBLIC_ENTRY} \
+ -DENABLE_TASK_PAYMENT=${ENABLE_TASK_PAYMENT} \
+ -DAPI_PATH=${API_PATH} \
+ -DAB_SECRET_KEY=${AB_SECRET_KEY} \
+ -Dspring.profiles.active=${PROFILE} \
+ -Dserver.port=${API_PORT} \
+ -jar /app/${APP_JAR_NAME}.jar " ]
+
+

+ 88 - 0
Dockerfile-jar

@@ -0,0 +1,88 @@
+FROM registry.cn-hangzhou.aliyuncs.com/ggcimages/dev-env:openjdk--21-ea-buster
+
+WORKDIR /app
+
+ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 TZ=Asia/Shanghai
+ENV JAVA_OPTS=""
+ENV APP_NAME='zjjhyiwuyq'
+ENV LOGS_PATH=/var/log/app/${APP_NAME} LOG_FILE="app"
+RUN mkdir -p /var/log/app
+
+ENV APP_JAR_NAME=business-webapi-1.0.0
+ENV PROFILE=dev
+ENV API_PORT=8100
+ENV API_PATH=api
+
+# AES 等保要求等安全加密配置
+ENV AB_SECRET_KEY==zSuAV19ef1e59B3F884C9B2024920==
+ENV AB_SECRET_IV=zSUAVPT119ef9f==
+
+ENV SQL_LOGGING_LEVEL=false
+ENV SQL_AGC_LOGGING_LEVEL=false
+ENV SQL_SHARE_LOGGING_LEVEL=false
+ENV REDIS_ENABLED=false
+ENV REDIS_HOST=172.16.10.109
+ENV REDIS_PORT=6379
+ENV REDIS_PASSWD=''
+ENV MYSQL_HOST=172.16.10.109
+ENV MYSQL_PORT=3306
+ENV MYSQL_USER=zjjhyiwuyq
+ENV MYSQL_PASSWD=zjjhyiwuyq123456
+ENV JOB_SERVER_IP='snail-job.domain.com'
+ENV JOB_SERVER_PORT=50051
+ENV JOB_NAMESPACE=764d604ec6fc45f68cd92514c40e9e1a
+ENV JOB_GROUP=zjjhyiwuyq_group
+ENV JOB_TOKEN=SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj
+ENV JWT_KEY='L3jB6cR1sN5xZ8a'
+ENV LOCAL_OSS_DIRECTORY=''
+ENV OSS_BUCKET_NAME='zjjhyiwuyq'
+ENV OSS_SERVER='http://172.16.10.100:9192/'
+ENV OSS_ACCESS_KEY='CMBoDkjkRb457oxCj7ys'
+ENV OSS_ACCESS_SECRET='8uJmULkUFORiTYNfDtj3VBXEwgAgBLNmIYOFi6sN'
+ENV OSS_PUBLIC_ENTRY='http://zjjhyiwuyq.hz3r.cn/'
+ENV ENABLE_TASK_PAYMENT=true
+
+ENV LD_LIBRARY_PATH /app/lib/linux64/lib:/app/lib/linux64/lib/HCNetSDKCom:${LD_LIBRARY_PATH}
+
+
+# 拷贝依赖库到系统,使用系统默认的加载库路径,在系统的/usr/lib文件中加入你Java工程所需要使用的so文件,然后将HCNetSDKCom文件夹下的组件库也复制到/usr/lib目录,HCNetSDKCom文件夹中的组件库不要随意更换路径进行替换。CentOS 64位需拷贝/usr/lib64下。
+COPY ./lib /app/lib
+
+COPY ./target/business-webapi-1.0.0.jar .
+RUN ls -lith | grep business-webapi
+
+EXPOSE 8100
+
+
+ENTRYPOINT [ "sh", "-c", "java -Duser.timezone=GMT+08 -jar \
+ -DLOGS_PATH=${LOGS_PATH} \
+ -DSQL_LOGGING_LEVEL=${SQL_LOGGING_LEVEL} \
+ -DSQL_AGC_LOGGING_LEVEL=${SQL_AGC_LOGGING_LEVEL} \
+ -DSQL_SHARE_LOGGING_LEVEL=${SQL_SHARE_LOGGING_LEVEL} \
+ -DREDIS_ENABLED=${REDIS_ENABLED} \
+ -DREDIS_PORT=${REDIS_PORT} \
+ -DREDIS_PASSWD=${REDIS_PASSWD} \
+ -DMYSQL_HOST=${MYSQL_HOST} \
+ -DMYSQL_PORT=${MYSQL_PORT} \
+ -DMYSQL_USER=${MYSQL_USER} \
+ -DMYSQL_PASSWD=${MYSQL_PASSWD} \
+ -DJOB_SERVER_IP=${JOB_SERVER_IP} \
+ -DJOB_SERVER_PORT=${JOB_SERVER_PORT} \
+ -DJOB_NAMESPACE=${JOB_NAMESPACE} \
+ -DJOB_GROUP=${JOB_GROUP} \
+ -DJOB_TOKEN=${JOB_TOKEN} \
+ -DJWT_KEY=${JWT_KEY} \
+ -DLOCAL_OSS_DIRECTORY=${LOCAL_OSS_DIRECTORY} \
+ -DOSS_BUCKET_NAME=${OSS_BUCKET_NAME} \
+ -DOSS_SERVER=${OSS_SERVER} \
+ -DOSS_ACCESS_KEY=${OSS_ACCESS_KEY} \
+ -DOSS_ACCESS_SECRET=${OSS_ACCESS_SECRET} \
+ -DOSS_PUBLIC_ENTRY=${OSS_PUBLIC_ENTRY} \
+ -DENABLE_TASK_PAYMENT=${ENABLE_TASK_PAYMENT} \
+ -DAPI_PATH=${API_PATH} \
+ -DAB_SECRET_KEY=${AB_SECRET_KEY} \
+ -Dspring.profiles.active=${PROFILE} \
+ -Dserver.port=${API_PORT} \
+ /app/${APP_JAR_NAME}.jar " ]
+
+

+ 338 - 0
TECH-DOC.md

@@ -0,0 +1,338 @@
+# 业务后台系统技术说明文档
+
+> 本文档由 Sisyphus AI 助手生成,用于帮助 AI 代理快速理解本项目结构、架构和关键实现细节。
+
+## 1. 项目概述
+
+| 属性        | 值                                         |
+| ----------- | ------------------------------------------ |
+| 项目名称    | business-webapi                            |
+| Artifact ID | business-webapi                            |
+| Group ID    | com.hz3r.infrastructure                    |
+| 版本        | 1.0.0                                      |
+| 描述        | 园区管理系统后端 API 服务                  |
+| 基础框架    | Spring Boot 3.1.2                          |
+| Java 版本   | 17                                         |
+| 打包方式    | Spring Boot Jar                            |
+| 端口        | 8030                                       |
+| API 文档    | `/manager/doc.html` (SpringDoc Swagger UI) |
+
+## 2. 技术栈
+
+### 2.1 核心框架
+
+- **Spring Boot 3.1.2** - 应用程序框架
+- **Spring Web** - REST API
+- **Spring Security** - 认证授权
+- **Spring Data Redis** - 缓存
+- **MyBatis Plus 3.5.14** - ORM (支持 Spring Boot 3)
+- **MySQL Connector 8.4.0** - 数据库驱动
+
+### 2.2 认证相关
+
+- **java-jwt 4.5.0** (Auth0) - JWT 解析
+- **jjwt 0.11.5** (io.jsonwebtoken) - JWT 生成
+- **Kaptcha 2.3.2** - 图形验证码
+
+### 2.3 工具库
+
+- **Hutool 5.8.8** - Java 工具集
+- **FastJSON 2.0.53** - JSON 序列化
+- **FastJSON2 2.0.53** - JSON 序列化 (新版)
+- **EasyExcel 3.1.1** - Excel 读写
+- **Apache POI 5.5.1** - Office 文档
+- **Apache Commons** (Lang3, Collections4, Text, Pool2) - Apache 工具集
+- **Vavr 0.10.4** - 函数式编程
+
+### 2.4 第三方 SDK
+
+- **MinIO 8.5.2** - 对象存储
+- **Alipay SDK 4.40.576.ALL** - 支付宝支付
+- **阿里云短信 Dysmsapi 4.1.1** - SMS
+- **海康威视 Artemis HTTP Client 1.1.13** - 视频监控
+- **海康威视 AKSK SDK 1.2.1 / 2.0.0** - 设备接入
+- **JNA 3.0.9** - Java Native Access
+
+### 2.5 数据库迁移
+
+- **Flyway** (通过 Spring Boot 内置) - 数据库版本化管理
+
+## 3. 项目结构
+
+```
+src/main/java/com/hz3r/
+├── infrastructure/park/              # 主包 - 园区管理系统核心
+│   ├── BusinessWebapiApplication.java   # Spring Boot 启动类
+│   ├── client/                          # 外部客户端调用
+│   ├── config/                          # 配置类
+│   ├── domain/                          # 领域模型
+│   │   ├── beb/                         # 业务实体(?)
+│   │   ├── constant/                    # 常量
+│   │   ├── entity/                     # 数据库实体
+│   │   ├── enums/                     # 枚举
+│   │   └── vo/                         # 值对象 DTO
+│   ├── excel/                          # Excel 工具
+│   ├── hikvision/                      # 海康威视集成
+│   ├── job/                            # 定时任务
+│   ├── kafka/                          # Kafka 消息
+│   ├── listener/                        # Spring 事件监听器
+│   ├── mapper/                         # MyBatis Mapper
+│   ├── security/                       # 安全相关 (TokenAuthenticationFilter, UserDetails)
+│   ├── service/                        # 业务服务层
+│   │   ├── access/                    # 访客通行
+│   │   ├── adc/                       # 广告/支付聚合
+│   │   ├── agc/                       # (AGC 相关)
+│   │   ├── app/                       # APP 端
+│   │   ├── artemis/                    # 海康 Artemis
+│   │   ├── business/                  # 通用业务
+│   │   ├── hik/                      # 海康
+│   │   ├── icCard/                   # IC 卡
+│   │   ├── meter/                    # 水电表
+│   │   ├── meterbillnew/              # 水电账单(新)
+│   │   ├── payment/                  # 支付
+│   │   ├── share/                    # 分享
+│   │   ├── tenant/                   # 租户管理
+│   │   ├── visitor/                  # 访客
+│   │   ├── sys/                     # 系统管理
+│   │   └── wehcat/                  # 微信(?)
+│   └── web/                          # Web 层 (Controller)
+│       ├── controller/
+│       │   ├── access/               # 通行 Controller
+│       │   ├── adc/                 # ADC Controller (聚合支付/广告)
+│       │   ├── agc/                 # AGC Controller
+│       │   ├── artemis/             # Artemis Controller
+│       │   ├── front/               # 前端 Controller
+│       │   ├── hikvision/          # 海康 Controller
+│       │   ├── icCard/            # IC 卡 Controller
+│       │   ├── index/              # 首页
+│       │   ├── lease/             # 租赁 Controller
+│       │   ├── login/             # 登录
+│       │   ├── merchant/          # 商户
+│       │   ├── meter/            # 水电表
+│       │   ├── meterapp/         # 水电表 APP
+│       │   ├── openapi/          # 开放 API
+│       │   ├── payment/          # 支付
+│       │   ├── sys/              # 系统
+│       │   ├── tenant/           # 租户
+│       │   └── visitor/           # 访客
+│       ├── event/                  # 事件控制器
+│       └── scheduler/             # 调度器
+├── authorization/                   # 认证授权模块
+│   ├── app/                       # APP Token
+│   │   ├── AppTokenManager.java
+│   │   ├── AppTokenManagerImpl.java
+│   │   └── AppTokenModel.java
+│   └── front/                     # 前端 Token
+│       ├── FrontTokenManager.java
+│       ├── FrontTokenManagerImpl.java
+│       └── FrontTokenModel.java
+├── config/                         # 全局配置
+│   ├── BusiRedisConfig.java
+│   ├── BusiRedisFastJson2JsonSerializer.java
+│   ├── LocalDateTimeConfiguration.java
+│   ├── MybatisPlusConfiguration.java
+│   ├── RestTemplateConfig.java
+│   ├── SpringDocConfig.java
+│   └── WebConfiguration.java
+├── domain/                          # 通用领域模型
+├── exception/                      # 全局异常
+├── schedule/                        # 调度任务
+├── utils/                          # 工具类
+│   ├── BusiServiceUtils.java       # 业务服务工具
+│   ├── BusiSvcCUDUtils.java       # 增删改工具
+│   ├── BusiSvcQueryUtils.java     # 查询工具
+│   ├── CaptchaUtils.java          # 验证码工具
+│   ├── DPCUtils.java              # 数据处理
+│   ├── EnumGroupingUtils.java      # 枚举分组
+│   ├── JwtTokenProvider.java     # JWT Token Provider
+│   ├── JwtUtil.java             # JWT 工具
+│   ├── LocalDateTimeUtil.java    # 日期时间工具
+│   ├── LocalOrderNoGenerator.java # 订单号生成
+│   ├── MeterDevOnlineUtils.java   # 设备在线状态
+│   ├── MiscUtils.java            # 杂项工具
+│   ├── MyDbOpsUtils.java        # 数据库操作
+│   ├── MyEntityUtils.java       # 实体工具
+│   ├── MyLocalDateTimeUtils.java # 日期时间
+│   ├── MyNumberUtils.java       # 数字工具
+│   ├── MyOssUtils.java       # OSS 工具
+│   ├── MyShortIdUtils.java   # 短 ID
+│   ├── MyStrUtils.java      # 字符串
+│   ├── ObjectConversion.java # 对象转换
+│   ├── PercentageUtils.java # 百分比
+│   ├── RedisCache.java      # Redis 缓存
+│   ├── RedisUtil.java       # Redis 工具
+│   ├── RedisUtils.java     # Redis 工具
+│   ├── RequestUtil.java    # 请求工具
+│   ├── RestTemplateUtil.java # HTTP 客户端
+│   ├── SecurityUtils.java   # 安全工具
+│   ├── SpringBeanUtils.java # Spring Bean
+│   ├── TenantSortUtils.java # 租户排序
+│   ├── ThreadLocalUtil.java # ThreadLocal
+│   └── meter/              # 水电表相关工具
+│       ├── Base64.java
+│       ├── CaptchaConfig.java
+│       ├── DateDimensionUtils.java
+│       ├── JwtUtils.java
+│       └── RedisCache.java
+├── web/                            # Web 通用层
+│   ├── advice/                    # 异常拦截 (ThrowableAdvice)
+│   ├── controller/
+│   │   ├── FileContentExchangeController.java  # 文件交换
+│   │   └── OssController.java               # OSS
+│   └── interceptor/
+│       ├── AppTokenInterceptor.java   # APP Token 拦截
+│       ├── FrontTokenInterceptor.java # 前端 Token 拦截
+│       └── RequestIdInterceptor.java # 请求 ID 拦截
+└── aop/                           # AOP 切面
+    ├── IVoLogInfo.java
+    └── ProjDocItem.java
+```
+
+## 4. 数据库
+
+- **引擎**: MySQL 8.x
+- **ORM**: MyBatis Plus 3.5.14 (支持 Spring Boot 3)
+- **连接池**: Druid (Spring Boot 内置 HikariCP)
+- **Mapper 扫描路径**: `com.hz3r.infrastructure.park.**.mapper`
+- **表名/列名格式**: 反引号包裹 (处理数据库关键词如 `type`, `index`)
+- **迁移工具**: Flyway
+- **Flyway 脚本路径**: `src/main/resources/db.flyway/`
+
+### 4.1 配置文件优先级 (profile)
+
+| 文件                    | 用途                |
+| ----------------------- | ------------------- |
+| `application.yml`       | 主配置              |
+| `application-dev.yml`   | 开发环境 (默认激活) |
+| `application-test.yml`  | 测试环境            |
+| `application-prod.yml`  | 生产环境            |
+| `application-local.yml` | 本地环境            |
+| `application-ut.yml`    | 单元测试            |
+
+## 5. 核心功能模块
+
+### 5.1 认证授权
+
+| 模块                  | 说明          | Token 存储 |
+| --------------------- | ------------- | ---------- |
+| `authorization.app`   | APP 端认证    | Redis      |
+| `authorization.front` | 前端 Web 认证 | Redis      |
+
+**登录限制**: 5 次失败后锁定 15 分钟。
+
+### 5.2 业务模块
+
+| 模块     | Controller 包           | 说明                 |
+| -------- | ----------------------- | -------------------- |
+| 租户管理 | `tenant/`               | 租户、部门、员工管理 |
+| 访客管理 | `visitor/`              | 预约、通行权限       |
+| 水电表   | `meter/`                | 水电表账单、缴费     |
+| 支付     | `adc/`                  | 支付宝聚合支付       |
+| 通行     | `access/`               | 门禁通行             |
+| IC 卡    | `icCard/`               | IC 卡管理            |
+| 海康视频 | `artemis/` `hikvision/` | 视频监控集成         |
+
+### 5.3 开放接口
+
+- `springdoc-openapi`: `/v3/api-docs` → `/manager/doc.html`
+- OpenAPI 3.0 规范
+
+## 6. 配置文件 (application.yml)
+
+```yaml
+server:
+  port: 8030
+spring:
+  application.name: business-webapi
+  profiles.active: dev
+  jackson.property-naming-strategy: LOWER_CAMEL_CASE
+mybatis-plus:
+  global-config.db-config.column-format: "`%s`"
+park.api.title: 园区管理系统 API文档
+data.appName: 义乌蓝宇数码园区
+data.sms-cloud: # 阿里云短信配置
+data.login-limit.max-fail-count: 5
+data.login-limit.lock-minutes: 15
+```
+
+## 7. 构建与部署
+
+### 7.1 Maven
+
+```bash
+./mvnw clean package    # 构建
+./mvnw spring-boot:run  # 运行
+```
+
+### 7.2 Docker
+
+- `Dockerfile` - 标准 Spring Boot 镜像
+- `Dockerfile-jar` - Fat JAR 镜像
+- `docker-compose.yml` - 本地开发环境
+
+### 7.3 CI/CD
+
+- `.gitlab-ci.yml` - GitLab CI 流水线
+- `.ci-build_script.sh` - Linux 构建脚本
+- `.ci-build_script.dev.sh` - 开发环境构建
+
+### 7.4 部署脚本
+
+| 文件                     | 用途             |
+| ------------------------ | ---------------- |
+| `jar-quick-delivery.sh`  | Linux 一键���署  |
+| `jar-quick-delivery.bat` | Windows 一键部署 |
+| `jar-deploy.sh`          | JAR 部署脚本     |
+
+### 7.5 本地原生库
+
+- `lib/win32/` - Windows 原生 DLL (海康 SDK)
+  - `HCNetSDK.dll`, `HCCore.dll` - 海康 SDK
+  - `hpr.dll`, `hlog.dll` - 海康日志
+  - `libcrypto`, `libssl` - OpenSSL
+
+## 8. 依赖仓库
+
+```
+nexus.hz3r.cn/repository/maven-public/
+```
+
+(杭州幻视科技公司内部 Maven 仓库)
+
+## 9. 启动日志
+
+```
+--------------------------启动成功--------------------------------
+  Application 'business-webapi' is running!
+  Login:   http://<host>:8030/manager
+  Doc:     http://<host>:8030/manager/doc.html
+```
+
+## 10. 关键设计模式
+
+| 设计        | 说明                                             |
+| ----------- | ------------------------------------------------ |
+| 分层架构    | Controller → Service → Mapper                    |
+| Redis Token | APP/前端 Token 管理,存储在 Redis                |
+| 聚合支付    | `adc/` 下多个支付通道聚合 (支付宝)               |
+| 海康集成    | `artemis/` HTTP API + 本地 JNA SDK               |
+| 请求追踪    | `RequestIdInterceptor` 为每个请求生成唯一请求 ID |
+| 统一异常    | `ThrowableAdvice` 全局异常拦截                   |
+| AOP 日志    | `ProjDocItem` + `IVoLogInfo` 切面日志            |
+
+## 11. 数据库表生成代码说明
+
+| 代码                     | 来源                          | 说明                                                                                                                                                                                                                                                                                                                             |
+| ------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 代码模板                 | Ba1att或IBa1att开头的代码文件 | Ba1attATemplateTable.java,Ba1attATemplateTableVo.java,Ba1attATemplateTableBEB.java,Ba1attATemplateTableMapperBEB.java,Ba1attATemplateTableMapper.java,IBa1attATemplateTableService.java,Ba1attATemplateTableServiceImpl.java,Ba1attATemplateTableController.java,Ba1attATemplateTableMapper.xml |
+| 类型属性命名             | 表字段名称                    | 首字母小写驼峰命名策略                                                                                                                                                                                                                                                                                                           |
+| 静态属性DATA_CODE        | 表名                          | 数据代码,表名第一个单词小写                                                                                                                                                                                                                                                                                                     |
+| 静态属性REL_UP           | 表名                          | 上级数据引用当前数据的KEY,表名第一个单词小写 + `_id`                                                                                                                                                                                                                                                                            |
+| 实体类型属性自动填充策略 | 表字段描述                    | 检查表字段描述中//${xxx}//部分,如果其中包含 IF,则 insert 时自动填充,包含 IFU,则 update 时自动填充,包含 IFIU,则 insert 和 update 时自动填充                                                                                                                                                                                 |
+| VO类型属性必填校验       | 表字段描述                    | 检查表字段描述中//${xxx}//部分,如果其中包含 NR,则忽略必填,否则更具数据中字段是否必填设置 springboot 的 validation 规则                                                                                                                                                                                                        |
+
+---
+
+> 本文档版本: 1.0.0
+> 生成时间: 2026-04-30
+> 适用 AI: Sisyphus (OhMyOpenCode)

+ 82 - 0
docker-compose.yml

@@ -0,0 +1,82 @@
+#
+#
+services:
+  #
+  business-webapi:
+    build:
+      context: .
+      dockerfile: Dockerfile
+      no_cache: false
+      args:
+        - LFS_JAR=${LFS_JAR}
+      labels:
+        com.hzhs.description: "业务后台"
+        com.hzhs.image:version: "1.0.0"
+        com.hzhs.app.version: "1.0.0"
+    image: business-webapi:${TAG}
+    labels:
+      - "traefik.enable=true"
+      - "traefik.http.middlewares.gzip.compress=true"
+      #- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
+      #
+      # Web服务入口 - www
+      - "traefik.http.routers.http-business-webapi.entryPoints=http" # 其它可能配置的名称是 web
+      - "traefik.http.routers.http-business-webapi.middlewares=gzip"
+      - "traefik.http.routers.http-business-webapi.rule=Host(`zjjhyiwuyq-api.hz3r.cn`) && PathPrefix(`/dev-api`)"
+      - "traefik.http.routers.http-business-webapi.service=http-business-webapi"
+      # 负载均衡配置。
+      - "traefik.http.services.http-business-webapi.loadbalancer.server.port=8100"
+      #
+    environment:
+      - "LOG_FILE=business-webapi"
+      - "PROFILE=test"
+      - "APP_JAR_NAME=business-webapi-1.0.0"
+      - "API_PORT=8100"
+      - "API_PATH=dev-api"
+      #- "REDIS_ENABLED=false"
+      #- "REDIS_HOST="
+      #- "REDIS_PORT="
+      #- "REDIS_PASSWD="
+      #- "MYSQL_HOST="
+      #- "MYSQL_PORT="
+      #- "MYSQL_USER="
+      #- "MYSQL_PASSWD="
+      #- "LOCAL_OSS_DIRECTORY="
+      #- "OSS_BUCKET_NAME=zjjhyiwuyq"
+      #- "OSS_SERVER=http://172.16.10.100:9192/"
+      #- "OSS_ACCESS_KEY=CMBoDkjkRb457oxCj7ys"
+      #- "OSS_ACCESS_SECRET=8uJmULkUFORiTYNfDtj3VBXEwgAgBLNmIYOFi6sN"
+      #- "OSS_PUBLIC_ENTRY=http://zjjhyiwuyq.hz3r.cn/"
+      #- "ENABLE_TASK_PAYMENT=true"
+    volumes:
+      - "/var/log/service/:/var/log/app/"
+      #- "./application-test.yml:/app/application-test.yml"
+      #- "./business-webapi-1.0.0-2601231221.jar:/app/business-webapi-1.0.0-host.jar"
+    expose:
+      - "8100"
+    ports:
+      - "172.16.10.100::8100"
+    networks:
+      - "coolify"
+    deploy:
+      resources:
+        limits:
+          memory: 2G
+          cpus: '3.00'
+          #pids: 1
+        reservations:
+          cpus: '0.20'
+          memory: 1G
+    restart: unless-stopped
+    healthcheck: # HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1
+       test: [ "CMD-SHELL", "(curl --fail http://127.0.0.1:8100/dev-api/actuator/health | grep -e '\\\"status\\\"\\:[\\s]*\\\"[uU][pP]\\\"') || exit 1" ]
+       start_period: "30s"
+       interval: "15s"
+       timeout: "15s"
+       retries: 5
+  #
+networks:
+  coolify:
+    external: true
+  #
+#

+ 18 - 0
jar-deploy.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+set -e
+
+ls 1.dockerignore && mv .dockerignore 1.dockerignore
+
+export PROJECT='business-webapi'
+export VERSION='v1.0.0'
+export TAG=${VERSION}'.'$(date +%y%m%d%H%M)
+export LFS_JAR=${PROJECT}'-1.0.0.jar' # ${VERSION}'.'$(date -d '5 minutes ago' +'%y%m%d%H%M')
+export LOCAL_REGISTRY_GROUP='localhost:5001/'${PROJECT}':'${VERSION}
+export LOCAL_REGISTRY_NAME='localhost:5001/'${PROJECT}
+export LOCAL_REGISTRY_FULL_NAME='localhost:5001/'${PROJECT}':'${TAG}
+
+docker buildx build . -f Dockerfile-jar -t ${LOCAL_REGISTRY_FULL_NAME}
+
+export K8S_deployment_NAME='business-webapi'
+(sh -c "docker push ${LOCAL_REGISTRY_FULL_NAME}  || echo '推送镜像到本地 registry 失败!'")
+(sh ./.gitlab-ci-help.sh k8s)

+ 14 - 0
jar-quick-delivery.bat

@@ -0,0 +1,14 @@
+:: 先打包
+:: del /S /Q target\*
+:: mvn clean package -U -DskipTests --color auto
+:: RUN mvn clean package -U -DskipTests --color auto -f pom.jar.xml
+:: RUN mvn clean package -U -DskipTests -X -e --color auto -f pom.jar.xml
+
+:: 使用 ssh 复制本地 jar 包到远程服务器,并在远程服务器中打包镜像和发布。
+:: 在 %UserProfile%\.ssh\config 文件中配置 ssh 请求(ssh 要用完整的文件路径进行引用)
+C:\Windows\System32\OpenSSH\ssh.exe srv100 "cd /root/compose-files/park/business-webapi && rm -f target/*" || echo "删除已存在 jar 文件失败!"
+C:\Windows\System32\OpenSSH\scp.exe target\business-webapi-1.0.0.jar  srv100:/root/compose-files/park/business-webapi/target/ || echo "复制 jar 文件到远程服务器失败!"
+C:\Windows\System32\OpenSSH\ssh.exe srv100 "cd /root/compose-files/park/business-webapi && ./jar-deploy.sh" || echo "发布到服务容器失败!"
+:: 
+::
+::

+ 1 - 0
jar-quick-delivery.sh

@@ -0,0 +1 @@
+

+ 259 - 0
mvnw

@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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
+#
+#    http://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+#   JAVA_HOME - location of a JDK home dir, required when download maven via java source
+#   MVNW_REPOURL - repo url base for downloading maven distribution
+#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+  [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+  native_path() { cygpath --path --windows "$1"; }
+  ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+  if [ -n "${JAVA_HOME-}" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+      JAVACCMD="$JAVA_HOME/jre/sh/javac"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+      JAVACCMD="$JAVA_HOME/bin/javac"
+
+      if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+        echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+        echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+        return 1
+      fi
+    fi
+  else
+    JAVACMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v java
+    )" || :
+    JAVACCMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v javac
+    )" || :
+
+    if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+      echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+      return 1
+    fi
+  fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+  str="${1:-}" h=0
+  while [ -n "$str" ]; do
+    char="${str%"${str#?}"}"
+    h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+    str="${str#?}"
+  done
+  printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+  printf %s\\n "$1" >&2
+  exit 1
+}
+
+trim() {
+  # MWRAPPER-139:
+  #   Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+  #   Needed for removing poorly interpreted newline sequences when running in more
+  #   exotic environments such as mingw bash on Windows.
+  printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+  case "${key-}" in
+  distributionUrl) distributionUrl=$(trim "${value-}") ;;
+  distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+  esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+  case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+  *)
+    echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+    distributionPlatform=linux-amd64
+    ;;
+  esac
+  distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+  ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+  exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+  verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+  clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+  trap clean HUP INT TERM EXIT
+else
+  die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+  distributionUrl="${distributionUrl%.zip}.tar.gz"
+  distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+  verbose "Found wget ... using wget"
+  wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+  verbose "Found curl ... using curl"
+  curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+  verbose "Falling back to use Java to download"
+  javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+  targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+  cat >"$javaSource" <<-END
+	public class Downloader extends java.net.Authenticator
+	{
+	  protected java.net.PasswordAuthentication getPasswordAuthentication()
+	  {
+	    return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+	  }
+	  public static void main( String[] args ) throws Exception
+	  {
+	    setDefault( new Downloader() );
+	    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+	  }
+	}
+	END
+  # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+  verbose " - Compiling Downloader.java ..."
+  "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+  verbose " - Running Downloader.java ..."
+  "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+  distributionSha256Result=false
+  if [ "$MVN_CMD" = mvnd.sh ]; then
+    echo "Checksum validation is not supported for maven-mvnd." >&2
+    echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  elif command -v sha256sum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  elif command -v shasum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+    echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  fi
+  if [ $distributionSha256Result = false ]; then
+    echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+    echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+  unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+  tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"

+ 149 - 0
mvnw.cmd

@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+  $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

+ 4 - 0
opencode.json

@@ -0,0 +1,4 @@
+{
+  "$schema": "https://opencode.ai/config.json",
+  "plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]
+}

+ 407 - 0
pom.xml

@@ -0,0 +1,407 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>3.1.2</version>
+		<relativePath/> <!-- lookup parent from repository -->
+	</parent>
+
+	<groupId>com.hz3r.infrastructure</groupId>
+	<artifactId>business-webapi</artifactId>
+	<version>1.0.0</version>
+	<name>business-webapi</name>
+	<description>Business WebAPI</description>
+
+	<properties>
+		<java.version>17</java.version>
+		<springboot.version>3.1.2</springboot.version>
+		<swagger.version>2.9.2</swagger.version>
+		<swagger-ui.version>2.9.2</swagger-ui.version>
+		<springdoc.version>2.1.0</springdoc.version>
+		<jwt.version>0.11.5</jwt.version>
+		<fastjson.version>2.0.53</fastjson.version>
+		<fastjson2.version>2.0.53</fastjson2.version>
+	</properties>
+
+	<dependencies>
+		<!--<dependency>-->
+		<!--	<groupId>org.springframework.boot</groupId>-->
+		<!--	<artifactId>spring-boot-dependencies</artifactId>-->
+		<!--	<version>${springboot.version}</version>-->
+		<!--</dependency>-->
+
+		<!--<dependency>-->
+		<!--	<groupId>org.springframework.boot</groupId>-->
+		<!--	<artifactId>spring-boot-devtools</artifactId>-->
+		<!--	<scope>runtime</scope>-->
+		<!--</dependency>-->
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-validation</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-redis</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-junit-jupiter</artifactId>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.baomidou</groupId>
+			<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+			<version>3.5.14</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.baomidou</groupId>
+			<artifactId>mybatis-plus-jsqlparser</artifactId>
+			<version>3.5.14</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.mybatis</groupId>
+			<artifactId>mybatis-spring</artifactId>
+			<version>3.0.5</version>
+		</dependency>
+		<dependency>
+			<groupId>com.mysql</groupId>
+			<artifactId>mysql-connector-j</artifactId>
+			<version>8.4.0</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpmime</artifactId>
+			<version>4.5.14</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.17.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-pool2</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-collections4</artifactId>
+			<version>4.4</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>${fastjson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba.fastjson2</groupId>
+			<artifactId>fastjson2</artifactId>
+			<version>${fastjson2.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.github.mwiede</groupId>
+			<artifactId>jsch</artifactId>
+			<version>0.2.8</version>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.hutool</groupId>
+			<artifactId>hutool-all</artifactId>
+			<version>5.8.8</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+			<version>5.5.1</version>
+		</dependency>
+		<dependency>
+			<groupId>com.googlecode.json-simple</groupId>
+			<artifactId>json-simple</artifactId>
+			<version>1.1.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.sun.jna</groupId>
+			<artifactId>jna</artifactId>
+			<version>3.0.9</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-text</artifactId>
+			<version>1.9</version>
+		</dependency>
+		<dependency>
+			<groupId>org.json</groupId>
+			<artifactId>org.json</artifactId>
+			<version>chargebee-1.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.jna</groupId>
+			<artifactId>jna-examples</artifactId>
+			<version>1.0.0</version>
+		</dependency>
+
+		<dependency>
+			<groupId>io.minio</groupId>
+			<artifactId>minio</artifactId>
+			<version>8.5.2</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.sun.mail</groupId>
+			<artifactId>jakarta.mail</artifactId>
+			<version>2.0.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.alipay.sdk</groupId>
+			<artifactId>alipay-sdk-java</artifactId>
+			<version>4.40.576.ALL</version>
+		</dependency>
+
+		<!-- JWT -->
+		<dependency>
+			<groupId>com.auth0</groupId>
+			<artifactId>java-jwt</artifactId>
+			<version>4.5.0</version>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt-api</artifactId>
+			<version>0.11.5</version>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt-impl</artifactId>
+			<version>0.11.5</version>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt-jackson</artifactId>
+			<version>0.11.5</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>easyexcel</artifactId>
+			<version>3.1.1</version>
+			<type>pom</type>
+		</dependency>
+		<!-- Vavr 是一个功能强大的函数式编程库,为 Java 提供了丰富的元组、不可变集合和多参函数式接口。例如 Function6<Integer, Integer, Integer, Integer, Integer, Integer, Integer> sum6; -->
+		<dependency>
+			<groupId>io.vavr</groupId>
+			<artifactId>vavr</artifactId>
+			<version>0.10.4</version>
+		</dependency>
+		<dependency>
+			<groupId>com.aliyun</groupId>
+			<artifactId>dysmsapi20170525</artifactId>
+			<version>4.1.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<version>1.18.36</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springdoc</groupId>
+			<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+			<version>${springdoc.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.fasterxml.jackson.dataformat</groupId>
+			<artifactId>jackson-dataformat-xml</artifactId>
+		</dependency>
+
+		<!-- 验证码 -->
+		<dependency>
+			<groupId>com.github.penggle</groupId>
+			<artifactId>kaptcha</artifactId>
+			<version>2.3.2</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpmime</artifactId>
+			<version>4.5.14</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.hikvision.ga</groupId>
+			<artifactId>artemis-http-client</artifactId>
+			<version>1.1.13</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.hikvision.bigdata</groupId>
+			<artifactId>app-framework-support-aksk-sdk</artifactId>
+			<version>1.2.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.hikvision.bigdata</groupId>
+			<artifactId>gateway-aksk-sdk</artifactId>
+			<version>2.0.0</version>
+		</dependency>
+<!--		<dependency>-->
+<!--			<groupId>com.weaver.openapi</groupId>-->
+<!--			<artifactId>weaver-openapi-sdk</artifactId>-->
+<!--			<version>10.0.2409.01</version>-->
+<!--		</dependency>-->
+
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<configuration>
+					<excludes>
+						<exclude>
+							<groupId>org.projectlombok</groupId>
+							<artifactId>lombok</artifactId>
+						</exclude>
+					</excludes>
+					<!--<executable>true</executable>-->
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-antrun-plugin</artifactId>
+				<version>3.1.0</version>
+				<executions>
+					<execution>
+						<id>deploy-after-package</id>
+						<phase>package</phase>   <!-- 绑定到 package 阶段 -->
+						<goals>
+							<goal>run</goal>
+						</goals>
+						<configuration>
+							<target>
+								<!-- 根据操作系统选择脚本 -->
+								<condition property="script.ext" value=".bat" else=".sh">
+									<os family="windows"/>
+								</condition>
+								<!-- 获取 Jar 包路径 -->
+								<property name="jar.path"
+										  value="${project.build.directory}/${project.build.finalName}.jar" >
+								</property>
+
+								<!-- 执行脚本,传递 jar 路径作为参数 -->
+								<exec executable="${basedir}/jar-quick-delivery${script.ext}"
+									  dir="${basedir}"
+									  failonerror="true">
+									<arg value="${jar.path}"/>
+									<arg value="${project.version}"/>
+								</exec>
+							</target>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+	<!-- 项目独立依赖员配置。 杭州幻视科技公司
+	     需要注释掉默认 maven settings.xml 中的 mirrors 配置节点。 -->
+	<repositories>
+		<repository>
+			<id>central</id>
+			<name>huanshi nexus public repository</name>
+			<url>https://nexus.hz3r.cn/repository/maven-public/</url>
+			<layout>default</layout>
+			<releases>
+				<enabled>true</enabled>
+				<updatePolicy>always</updatePolicy>
+				<checksumPolicy>warn</checksumPolicy>
+			</releases>
+			<snapshots>
+				<enabled>true</enabled>
+				<updatePolicy>never</updatePolicy>
+				<checksumPolicy>fail</checksumPolicy>
+			</snapshots>
+		</repository>
+	</repositories>
+	<pluginRepositories>
+		<pluginRepository>
+			<id>central</id>
+			<name>My Plugins repo</name>
+			<url>https://nexus.hz3r.cn/repository/maven-public/</url>
+			<releases>
+				<enabled>true</enabled>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</pluginRepository>
+	</pluginRepositories>
+	<!-- 项目独立依赖员配置。大庆个人包管理
+	     需要注释掉默认 maven settings.xml 中的 mirrors 配置节点。
+	<repositories>
+		<repository>
+			<id>central</id>
+			<name>cihang nexus public repository</name>
+			<url>https://nexus.cihangtech.com/repository/maven-public/</url>
+			<layout>default</layout>
+			<releases>
+				<enabled>true</enabled>
+				<updatePolicy>always</updatePolicy>
+				<checksumPolicy>warn</checksumPolicy>
+			</releases>
+			<snapshots>
+				<enabled>true</enabled>
+				<updatePolicy>never</updatePolicy>
+				<checksumPolicy>fail</checksumPolicy>
+			</snapshots>
+		</repository>
+	</repositories>
+	<pluginRepositories>
+		<pluginRepository>
+			<id>central</id>
+			<name>My Plugins repo</name>
+			<url>https://nexus.cihangtech.com/repository/maven-public/</url>
+			<releases>
+				<enabled>true</enabled>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</pluginRepository>
+	</pluginRepositories> -->
+</project>

+ 92 - 0
readme.md

@@ -0,0 +1,92 @@
+# 业务后台
+
+
+
+## Getting started
+
+To make it easy for you to get started with GitLab, here's a list of recommended next steps.
+
+Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
+
+## Add your files
+
+- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
+- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
+
+```
+cd existing_repo
+git remote add origin http://gitlab.hz3r.cn/intelligent-park/buiness-webapi.git
+git branch -M main
+git push -uf origin main
+```
+
+## Integrate with your tools
+
+- [ ] [Set up project integrations](http://gitlab.hz3r.cn/intelligent-park/buiness-webapi/-/settings/integrations)
+
+## Collaborate with your team
+
+- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
+- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
+- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
+- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
+- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
+
+## Test and Deploy
+
+Use the built-in continuous integration in GitLab.
+
+- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
+- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
+- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
+- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
+- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
+
+***
+
+# Editing this README
+
+When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
+
+## Suggestions for a good README
+Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
+
+## Name
+Choose a self-explaining name for your project.
+
+## Description
+Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
+
+## Badges
+On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
+
+## Visuals
+Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
+
+## Installation
+Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
+
+## Usage
+Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
+
+## Support
+Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
+
+## Roadmap
+If you have ideas for releases in the future, it is a good idea to list them in the README.
+
+## Contributing
+State if you are open to contributions and what your requirements are for accepting them.
+
+For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
+
+You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
+
+## Authors and acknowledgment
+Show your appreciation to those who have contributed to the project.
+
+## License
+For open source projects, say how it is licensed.
+
+## Project status
+If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

+ 311 - 0
settings-hz3r.xml

@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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
+
+    http://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.
+-->
+
+<!--
+ | This is the configuration file for Maven. It can be specified at two levels:
+ |
+ |  1. User Level. This settings.xml file provides configuration for a single user,
+ |                 and is normally provided in ${user.home}/.m2/settings.xml.
+ |
+ |                 NOTE: This location can be overridden with the CLI option:
+ |
+ |                 -s /path/to/user/settings.xml
+ |
+ |  2. Global Level. This settings.xml file provides configuration for all Maven
+ |                 users on a machine (assuming they're all using the same Maven
+ |                 installation). It's normally provided in
+ |                 ${maven.conf}/settings.xml.
+ |
+ |                 NOTE: This location can be overridden with the CLI option:
+ |
+ |                 -gs /path/to/global/settings.xml
+ |
+ | The sections in this sample file are intended to give you a running start at
+ | getting the most out of your Maven installation. Where appropriate, the default
+ | values (values used when the setting is not specified) are provided.
+ |
+ |-->
+<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
+  <!-- localRepository
+   | The path to the local repository maven will use to store artifacts.
+   |
+   | Default: ${user.home}/.m2/repository
+  <localRepository>/path/to/local/repo</localRepository>
+  -->
+  <localRepository>D:/app/repository</localRepository>
+
+  <!-- interactiveMode
+   | This will determine whether maven prompts you when it needs input. If set to false,
+   | maven will use a sensible default value, perhaps based on some other setting, for
+   | the parameter in question.
+   |
+   | Default: true
+  <interactiveMode>true</interactiveMode>
+  -->
+
+  <!-- offline
+   | Determines whether maven should attempt to connect to the network when executing a build.
+   | This will have an effect on artifact downloads, artifact deployment, and others.
+   |
+   | Default: false
+  <offline>false</offline>
+  -->
+
+  <!-- pluginGroups
+   | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
+   | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
+   | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
+   |-->
+  <pluginGroups>
+    <!-- pluginGroup
+     | Specifies a further group identifier to use for plugin lookup.
+    <pluginGroup>com.your.plugins</pluginGroup>
+    -->
+  </pluginGroups>
+
+  <!-- TODO Since when can proxies be selected as depicted? -->
+  <!-- proxies
+   | This is a list of proxies which can be used on this machine to connect to the network.
+   | Unless otherwise specified (by system property or command-line switch), the first proxy
+   | specification in this list marked as active will be used.
+   |-->
+  <proxies>
+    <!-- proxy
+     | Specification for one proxy, to be used in connecting to the network.
+     |
+    <proxy>
+      <id>optional</id>
+      <active>true</active>
+      <protocol>http</protocol>
+      <username>proxyuser</username>
+      <password>proxypass</password>
+      <host>proxy.host.net</host>
+      <port>80</port>
+      <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
+    </proxy>
+    -->
+  </proxies>
+
+  <!-- servers
+   | This is a list of authentication profiles, keyed by the server-id used within the system.
+   | Authentication profiles can be used whenever maven must make a connection to a remote server.
+   |-->
+  <servers>
+    <!-- server
+     | Specifies the authentication information to use when connecting to a particular server, identified by
+     | a unique name within the system (referred to by the 'id' attribute below).
+     |
+     | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
+     |       used together.
+     |
+    <server>
+      <id>deploymentRepo</id>
+      <username>repouser</username>
+      <password>repopwd</password>
+    </server>
+    -->
+
+    <server>
+      <id>huanshi</id>
+      <username>developer</username>
+      <password>passw0rd</password>
+    </server>
+
+    <!-- Another sample, using keys to authenticate.
+    <server>
+      <id>siteServer</id>
+      <privateKey>/path/to/private/key</privateKey>
+      <passphrase>optional; leave empty if not used.</passphrase>
+    </server>
+    -->
+  </servers>
+
+  <!-- mirrors
+   | This is a list of mirrors to be used in downloading artifacts from remote repositories.
+   |
+   | It works like this: a POM may declare a repository to use in resolving certain artifacts.
+   | However, this repository may have problems with heavy traffic at times, so people have mirrored
+   | it to several places.
+   |
+   | That repository definition will have a unique id, so we can create a mirror reference for that
+   | repository, to be used as an alternate download site. The mirror site will be the preferred
+   | server for that repository.
+   |-->
+  <mirrors>
+    <!-- mirror
+     | Specifies a repository mirror site to use instead of a given repository. The repository that
+     | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
+     | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
+     |
+    <mirror>
+      <id>mirrorId</id>
+      <mirrorOf>repositoryId</mirrorOf>
+      <name>Human Readable Name for this Mirror.</name>
+      <url>http://my.repository.com/repo/path</url>
+    </mirror>
+     -->
+    <mirror>
+      <id>huanshi</id>
+      <mirrorOf>*</mirrorOf>
+      <name>huanshi nexus public repository</name>
+      <url>http://nexus.hz3r.cn/repository/maven-public/</url>
+    </mirror>
+  </mirrors>
+
+  <!-- profiles
+   | This is a list of profiles which can be activated in a variety of ways, and which can modify
+   | the build process. Profiles provided in the settings.xml are intended to provide local machine-
+   | specific paths and repository locations which allow the build to work in the local environment.
+   |
+   | For example, if you have an integration testing plugin - like cactus - that needs to know where
+   | your Tomcat instance is installed, you can provide a variable here such that the variable is
+   | dereferenced during the build process to configure the cactus plugin.
+   |
+   | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
+   | section of this document (settings.xml) - will be discussed later. Another way essentially
+   | relies on the detection of a property, either matching a particular value for the property,
+   | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
+   | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
+   | Finally, the list of active profiles can be specified directly from the command line.
+   |
+   | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
+   |       repositories, plugin repositories, and free-form properties to be used as configuration
+   |       variables for plugins in the POM.
+   |
+   |-->
+  <profiles>
+    <!-- profile
+     | Specifies a set of introductions to the build process, to be activated using one or more of the
+     | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
+     | or the command line, profiles have to have an ID that is unique.
+     |
+     | An encouraged best practice for profile identification is to use a consistent naming convention
+     | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
+     | This will make it more intuitive to understand what the set of introduced profiles is attempting
+     | to accomplish, particularly when you only have a list of profile id's for debug.
+     |
+     | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
+    <profile>
+      <id>jdk-1.4</id>
+
+      <activation>
+        <jdk>1.4</jdk>
+      </activation>
+
+      <repositories>
+        <repository>
+          <id>jdk14</id>
+          <name>Repository for JDK 1.4 builds</name>
+          <url>http://www.myhost.com/maven/jdk14</url>
+          <layout>default</layout>
+          <snapshotPolicy>always</snapshotPolicy>
+        </repository>
+      </repositories>
+    </profile>
+    -->
+
+    <profile>
+      <id>huanshi</id>
+      <repositories>
+        <repository>
+          <id>huanshi</id>
+          <name>huanshi nexus public repository</name>
+          <url>http://nexus.hz3r.cn/repository/maven-public/</url>
+          <layout>default</layout>
+          <releases>
+            <enabled>true</enabled>
+            <updatePolicy>always</updatePolicy>
+            <checksumPolicy>warn</checksumPolicy>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+            <updatePolicy>never</updatePolicy>
+            <checksumPolicy>fail</checksumPolicy>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>huanshi</id>
+          <name>My Plugins repo</name>
+          <url>http://nexus.hz3r.cn/repository/maven-public/</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>false</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+
+    <!--
+     | Here is another profile, activated by the property 'target-env' with a value of 'dev', which
+     | provides a specific path to the Tomcat instance. To use this, your plugin configuration might
+     | hypothetically look like:
+     |
+     | ...
+     | <plugin>
+     |   <groupId>org.myco.myplugins</groupId>
+     |   <artifactId>myplugin</artifactId>
+     |
+     |   <configuration>
+     |     <tomcatLocation>${tomcatPath}</tomcatLocation>
+     |   </configuration>
+     | </plugin>
+     | ...
+     |
+     | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
+     |       anything, you could just leave off the <value/> inside the activation-property.
+     |
+    <profile>
+      <id>env-dev</id>
+
+      <activation>
+        <property>
+          <name>target-env</name>
+          <value>dev</value>
+        </property>
+      </activation>
+
+      <properties>
+        <tomcatPath>/path/to/tomcat/instance</tomcatPath>
+      </properties>
+    </profile>
+    -->
+  </profiles>
+
+  <!-- activeProfiles
+   | List of profiles that are active for all builds.
+   |
+  <activeProfiles>
+    <activeProfile>alwaysActiveProfile</activeProfile>
+    <activeProfile>anotherAlwaysActiveProfile</activeProfile>
+  </activeProfiles>
+  -->
+
+  <activeProfiles>
+    <activeProfile>huanshi</activeProfile>
+  </activeProfiles>
+
+</settings>

+ 310 - 0
settings.xml

@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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
+
+    http://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.
+-->
+
+<!--
+ | This is the configuration file for Maven. It can be specified at two levels:
+ |
+ |  1. User Level. This settings.xml file provides configuration for a single user,
+ |                 and is normally provided in ${user.home}/.m2/settings.xml.
+ |
+ |                 NOTE: This location can be overridden with the CLI option:
+ |
+ |                 -s /path/to/user/settings.xml
+ |
+ |  2. Global Level. This settings.xml file provides configuration for all Maven
+ |                 users on a machine (assuming they're all using the same Maven
+ |                 installation). It's normally provided in
+ |                 ${maven.conf}/settings.xml.
+ |
+ |                 NOTE: This location can be overridden with the CLI option:
+ |
+ |                 -gs /path/to/global/settings.xml
+ |
+ | The sections in this sample file are intended to give you a running start at
+ | getting the most out of your Maven installation. Where appropriate, the default
+ | values (values used when the setting is not specified) are provided.
+ |
+ |-->
+<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
+  <!-- localRepository
+   | The path to the local repository maven will use to store artifacts.
+   |
+   | Default: ${user.home}/.m2/repository
+  <localRepository>/path/to/local/repo</localRepository>
+  -->
+
+  <!-- interactiveMode
+   | This will determine whether maven prompts you when it needs input. If set to false,
+   | maven will use a sensible default value, perhaps based on some other setting, for
+   | the parameter in question.
+   |
+   | Default: true
+  <interactiveMode>true</interactiveMode>
+  -->
+
+  <!-- offline
+   | Determines whether maven should attempt to connect to the network when executing a build.
+   | This will have an effect on artifact downloads, artifact deployment, and others.
+   |
+   | Default: false
+  <offline>false</offline>
+  -->
+
+  <!-- pluginGroups
+   | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
+   | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
+   | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
+   |-->
+  <pluginGroups>
+    <!-- pluginGroup
+     | Specifies a further group identifier to use for plugin lookup.
+    <pluginGroup>com.your.plugins</pluginGroup>
+    -->
+  </pluginGroups>
+
+  <!-- TODO Since when can proxies be selected as depicted? -->
+  <!-- proxies
+   | This is a list of proxies which can be used on this machine to connect to the network.
+   | Unless otherwise specified (by system property or command-line switch), the first proxy
+   | specification in this list marked as active will be used.
+   |-->
+  <proxies>
+    <!-- proxy
+     | Specification for one proxy, to be used in connecting to the network.
+     |
+    <proxy>
+      <id>optional</id>
+      <active>true</active>
+      <protocol>http</protocol>
+      <username>proxyuser</username>
+      <password>proxypass</password>
+      <host>proxy.host.net</host>
+      <port>80</port>
+      <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
+    </proxy>
+    -->
+  </proxies>
+
+  <!-- servers
+   | This is a list of authentication profiles, keyed by the server-id used within the system.
+   | Authentication profiles can be used whenever maven must make a connection to a remote server.
+   |-->
+  <servers>
+    <!-- server
+     | Specifies the authentication information to use when connecting to a particular server, identified by
+     | a unique name within the system (referred to by the 'id' attribute below).
+     |
+     | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
+     |       used together.
+     |
+    <server>
+      <id>deploymentRepo</id>
+      <username>repouser</username>
+      <password>repopwd</password>
+    </server>
+    -->
+
+    <server>
+      <id>cihang</id>
+      <username>developer</username>
+      <password>CH2025@cihangtech.com</password>
+    </server>
+    
+    <!-- Another sample, using keys to authenticate.
+    <server>
+      <id>siteServer</id>
+      <privateKey>/path/to/private/key</privateKey>
+      <passphrase>optional; leave empty if not used.</passphrase>
+    </server>
+    -->
+  </servers>
+
+  <!-- mirrors
+   | This is a list of mirrors to be used in downloading artifacts from remote repositories.
+   |
+   | It works like this: a POM may declare a repository to use in resolving certain artifacts.
+   | However, this repository may have problems with heavy traffic at times, so people have mirrored
+   | it to several places.
+   |
+   | That repository definition will have a unique id, so we can create a mirror reference for that
+   | repository, to be used as an alternate download site. The mirror site will be the preferred
+   | server for that repository.
+   |-->
+  <mirrors>
+    <!-- mirror
+     | Specifies a repository mirror site to use instead of a given repository. The repository that
+     | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
+     | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
+     |
+    <mirror>
+      <id>mirrorId</id>
+      <mirrorOf>repositoryId</mirrorOf>
+      <name>Human Readable Name for this Mirror.</name>
+      <url>http://my.repository.com/repo/path</url>
+    </mirror>
+     -->
+    <mirror>
+      <id>cihang</id>
+      <mirrorOf>*</mirrorOf>
+      <name>cihang nexus public repository</name>
+      <url>https://nexus.cihangtech.com/repository/maven-public/</url>
+    </mirror>
+  </mirrors>
+
+  <!-- profiles
+   | This is a list of profiles which can be activated in a variety of ways, and which can modify
+   | the build process. Profiles provided in the settings.xml are intended to provide local machine-
+   | specific paths and repository locations which allow the build to work in the local environment.
+   |
+   | For example, if you have an integration testing plugin - like cactus - that needs to know where
+   | your Tomcat instance is installed, you can provide a variable here such that the variable is
+   | dereferenced during the build process to configure the cactus plugin.
+   |
+   | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
+   | section of this document (settings.xml) - will be discussed later. Another way essentially
+   | relies on the detection of a property, either matching a particular value for the property,
+   | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
+   | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
+   | Finally, the list of active profiles can be specified directly from the command line.
+   |
+   | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
+   |       repositories, plugin repositories, and free-form properties to be used as configuration
+   |       variables for plugins in the POM.
+   |
+   |-->
+  <profiles>
+    <!-- profile
+     | Specifies a set of introductions to the build process, to be activated using one or more of the
+     | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
+     | or the command line, profiles have to have an ID that is unique.
+     |
+     | An encouraged best practice for profile identification is to use a consistent naming convention
+     | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
+     | This will make it more intuitive to understand what the set of introduced profiles is attempting
+     | to accomplish, particularly when you only have a list of profile id's for debug.
+     |
+     | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
+    <profile>
+      <id>jdk-1.4</id>
+
+      <activation>
+        <jdk>1.4</jdk>
+      </activation>
+
+      <repositories>
+        <repository>
+          <id>jdk14</id>
+          <name>Repository for JDK 1.4 builds</name>
+          <url>http://www.myhost.com/maven/jdk14</url>
+          <layout>default</layout>
+          <snapshotPolicy>always</snapshotPolicy>
+        </repository>
+      </repositories>
+    </profile>
+    -->
+    
+    <profile>
+      <id>cihang</id>
+      <repositories>
+        <repository>
+          <id>cihang</id>
+          <name>cihang nexus public repository</name>
+          <url>https://nexus.cihangtech.com/repository/maven-public/</url>
+          <layout>default</layout>
+          <releases>
+            <enabled>true</enabled>
+            <updatePolicy>always</updatePolicy>
+            <checksumPolicy>warn</checksumPolicy>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+            <updatePolicy>never</updatePolicy>
+            <checksumPolicy>fail</checksumPolicy>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>cihang</id>
+          <name>My Plugins repo</name>
+          <url>https://nexus.cihangtech.com/repository/maven-public/</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>false</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+
+    <!--
+     | Here is another profile, activated by the property 'target-env' with a value of 'dev', which
+     | provides a specific path to the Tomcat instance. To use this, your plugin configuration might
+     | hypothetically look like:
+     |
+     | ...
+     | <plugin>
+     |   <groupId>org.myco.myplugins</groupId>
+     |   <artifactId>myplugin</artifactId>
+     |
+     |   <configuration>
+     |     <tomcatLocation>${tomcatPath}</tomcatLocation>
+     |   </configuration>
+     | </plugin>
+     | ...
+     |
+     | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
+     |       anything, you could just leave off the <value/> inside the activation-property.
+     |
+    <profile>
+      <id>env-dev</id>
+
+      <activation>
+        <property>
+          <name>target-env</name>
+          <value>dev</value>
+        </property>
+      </activation>
+
+      <properties>
+        <tomcatPath>/path/to/tomcat/instance</tomcatPath>
+      </properties>
+    </profile>
+    -->
+  </profiles>
+
+  <!-- activeProfiles
+   | List of profiles that are active for all builds.
+   |
+  <activeProfiles>
+    <activeProfile>alwaysActiveProfile</activeProfile>
+    <activeProfile>anotherAlwaysActiveProfile</activeProfile>
+  </activeProfiles>
+  -->
+  
+  <activeProfiles>
+    <activeProfile>cihang</activeProfile>
+  </activeProfiles>
+  
+</settings>

+ 47 - 0
src/main/java/com/hz3r/aop/IVoLogInfo.java

@@ -0,0 +1,47 @@
+package com.hz3r.aop;
+
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.core.util.ReflectUtil;
+import com.hz3r.utils.MyStrUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 视图对下日志信息接口
+ *
+ */
+public interface IVoLogInfo {
+
+
+    /**
+     * 获取操作参数数据
+     *
+     * @return
+     */
+    default Object findOpParameterData() { return this; }
+
+    /**
+     * 构建操作参数日志
+     *
+     *
+     * @return
+     */
+    default List<String> buildOpParametersLog() {
+
+        return Stream.of(ReflectUtil.getFieldsDirectly(findOpParameterData().getClass(), false))
+                .filter(e -> ReflectUtil.getFieldValue(findOpParameterData(), e.getName()) != null)
+                .map(e -> {
+                    Schema[] schemas = e.getAnnotationsByType(Schema.class);
+                    if (schemas == null || schemas.length == 0) {
+                        return StrFormatter.format("{}:{}", e.getName(), ReflectUtil.getFieldValue(findOpParameterData(), e.getName()));
+                    }
+                    return StrFormatter.format("{}:{}", schemas[0].description(), ReflectUtil.getFieldValue(findOpParameterData(), e.getName()));
+                })
+                .filter(e -> MyStrUtils.isNotBlank(e))
+                .collect(Collectors.toList());
+    }
+
+}

+ 40 - 0
src/main/java/com/hz3r/aop/ProjDocItem.java

@@ -0,0 +1,40 @@
+package com.hz3r.aop;
+
+
+import java.lang.annotation.*;
+
+/**
+ * 项目文档项注解
+ *
+ */
+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ProjDocItem {
+
+    /**
+     * 操作名称
+     *
+     * @return
+     */
+    String name();
+
+    String description() default "";
+
+    /**
+     * 操作类型
+     *
+     * @return
+     */
+    String opType() default "";
+
+
+    /**
+     * 请求参数名称列,与方法种的参数要一一对应。
+     *
+     * @return
+     */
+    String[] reqParamNames() default { };
+
+
+}

+ 25 - 0
src/main/java/com/hz3r/authorization/app/AppTokenManager.java

@@ -0,0 +1,25 @@
+package com.hz3r.authorization.app;
+
+
+
+/**
+ * @author liu
+ * @since 1.0.0
+ */
+public interface AppTokenManager {
+
+    AppTokenModel createToken(String value, String modelName);
+
+    boolean checkToken(String token, String modelName);
+
+    AppTokenModel getToken(String token, String modelName);
+
+    void deleteToken(String token, String modelName);
+
+    String getLocalInfoException(String key);
+
+    Object getLocalInfo(String key);
+
+    Long getLocalUserId();
+
+}

+ 126 - 0
src/main/java/com/hz3r/authorization/app/AppTokenManagerImpl.java

@@ -0,0 +1,126 @@
+package com.hz3r.authorization.app;
+
+import com.hz3r.utils.RedisUtil;
+import com.hz3r.utils.ThreadLocalUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author liu
+ * @since 1.0.0
+ */
+@Component
+public class AppTokenManagerImpl implements AppTokenManager {
+
+    @Autowired
+    protected RedisUtil redisUtil;
+
+
+    /**
+     * 生成Token
+     * @param value String 存储value
+     * @param modelName String 模块
+     */
+    @Override
+    public AppTokenModel createToken(String value, String modelName) {
+        String _token = UUID.randomUUID().toString().replace("-", "");
+        AppTokenModel token = new AppTokenModel(_token);
+        token.setLastAccessedTime(System.currentTimeMillis());
+
+        String existedIdKey = modelName + value;
+        String tokenKey = modelName + _token;
+
+//        Object existedToken = redisUtil.get(existedIdKey);
+
+        //当前用户对应的token存在,刷新token
+//        if(existedToken != null){
+//            //删除上一个token
+//            redisUtil.remove(modelName + existedToken);
+//        }
+//
+//        redisUtil.set(existedIdKey, _token);
+        redisUtil.set(tokenKey, value,
+                AppTokenModel.TOKEN_EXPRESS_MINUTES, TimeUnit.MINUTES);
+
+        Map<String, Object> hashedMap = new HashMap<>();
+        hashedMap.put(modelName, value);
+        ThreadLocalUtil.set(hashedMap);
+
+        return token;
+    }
+
+    /**
+     * 获取本地存储的实际
+     * @param key String 模块
+     */
+    @Override
+    public String getLocalInfoException(String key) {
+        Object value = ThreadLocalUtil.get(key);
+        if(value == null){
+            throw new RuntimeException("登录信息已过期,请重新登录!");
+        }
+        return value.toString();
+    }
+
+    /**
+     * 获取本地存储的实际
+     * @param key String 模块
+     */
+    @Override
+    public Object getLocalInfo(String key) {
+        if(StringUtils.isNotBlank(key)){
+            return ThreadLocalUtil.get(key);
+        }
+        return null;
+    }
+
+    /**
+     * 获取用户id
+     */
+    @Override
+    public Long getLocalUserId() {
+        return Long.valueOf(getLocalInfoException(AppTokenModel.APP_TOKEN_THREAD_LOCAL_KEY));
+    }
+
+
+
+    /**
+     * 检测Token
+     * @param token String token
+     * @param modelName String 模块
+     */
+    @Override
+    public boolean checkToken(String token, String modelName) {
+        return redisUtil.exists(modelName + token);
+    }
+
+    /**
+     * 检测Token
+     * @param token String token
+     * @param modelName String 模块
+     */
+    @Override
+    public AppTokenModel getToken(String token, String modelName) {
+        Object o = redisUtil.get(modelName + token);
+        AppTokenModel tokenModel = new AppTokenModel();
+        tokenModel.setUserId(Integer.parseInt(o.toString()));
+        return tokenModel;
+    }
+
+    /**
+     * 删除Token
+     * @param token String token
+     * @param modelName String 模块
+     */
+    @Override
+    public void deleteToken(String token, String modelName) {
+        redisUtil.remove(modelName +token);
+    }
+
+}

+ 36 - 0
src/main/java/com/hz3r/authorization/app/AppTokenModel.java

@@ -0,0 +1,36 @@
+package com.hz3r.authorization.app;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * token实体类
+ */
+@Data
+public class AppTokenModel implements Serializable {
+
+    //客户端token redis存储前缀
+    public static final String APP_TOKEN_REDIS_KEY_PREFIX = "APP_TOKEN:";
+
+    //用户打开场景threadLocal存储前缀
+    public static final String APP_TOKEN_THREAD_LOCAL_KEY = "id";
+
+    // 过期时间
+    public static final long TOKEN_EXPRESS_MINUTES = (60 * 3); //3小时
+    //用户ID
+    private Integer userId;
+    //用户token
+    private String token;
+    // 最后访问时间
+    private long lastAccessedTime = System.currentTimeMillis();
+    // 过期时间
+    private long expirationTime;
+
+    public AppTokenModel(String token){
+        this.token = token;
+    }
+
+    public AppTokenModel() {
+    }
+}

+ 68 - 0
src/main/java/com/hz3r/authorization/app/CheckAppToken.java

@@ -0,0 +1,68 @@
+package com.hz3r.authorization.app;
+
+import com.hz3r.domain.constant.Constants;
+import com.hz3r.utils.RedisUtil;
+import com.hz3r.utils.RequestUtil;
+import com.hz3r.utils.ThreadLocalUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 检测token是否过期
+ */
+@Component
+public class CheckAppToken {
+
+    @Autowired
+    protected RedisUtil redisUtil;
+
+    public Boolean check(String token, HttpServletRequest request){
+
+        try {
+            boolean exists = redisUtil.exists(AppTokenModel.APP_TOKEN_REDIS_KEY_PREFIX + token);
+
+            if(exists){
+                Object value = redisUtil.get(AppTokenModel.APP_TOKEN_REDIS_KEY_PREFIX + token);
+
+                Map<String, Object> hashedMap = new HashMap<>();
+                hashedMap.put(AppTokenModel.APP_TOKEN_THREAD_LOCAL_KEY, value);
+                ThreadLocalUtil.set(hashedMap);
+
+                //刷新token有效期
+                redisUtil.set(AppTokenModel.APP_TOKEN_REDIS_KEY_PREFIX +token, value, AppTokenModel.TOKEN_EXPRESS_MINUTES, TimeUnit.MINUTES);
+            }else{
+                //判断路由,部分路由不管用户是否登录/token过期都可以访问
+                exists = checkRouter(RequestUtil.getUri(request));
+            }
+
+            return exists;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    //路由在此处,则返回true,无论用户是否登录都可以访问
+    public boolean checkRouter(String uri) {
+        String[] routerList = {
+                "api/app/auth/login",
+                "api/app/auth/login/phone",
+                "api/app/tenants/list",
+                "api/app/park/list",
+                "api/app/register",
+                "api/app/captcha/captchaImage"
+        };
+        AntPathMatcher matcher = new AntPathMatcher();
+        return Arrays.asList(routerList).stream().parallel().anyMatch(arg -> matcher.match(arg, uri));
+    }
+
+    public String getTokenFormRequest(HttpServletRequest request){
+        return request.getHeader(Constants.HEADER_AUTHORIZATION_KEY);
+    }
+}

+ 66 - 0
src/main/java/com/hz3r/authorization/front/CheckFrontToken.java

@@ -0,0 +1,66 @@
+package com.hz3r.authorization.front;
+
+import com.hz3r.domain.constant.Constants;
+import com.hz3r.utils.RedisUtil;
+import com.hz3r.utils.RequestUtil;
+import com.hz3r.utils.ThreadLocalUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 检测token是否过期
+ */
+@Component
+public class CheckFrontToken {
+
+    @Autowired
+    protected RedisUtil redisUtil;
+//    @Autowired
+//    protected RequestUtil requestUtil;
+
+    public Boolean check(String token, HttpServletRequest request){
+
+        try {
+            boolean exists = redisUtil.exists(FrontTokenModel.FRONT_TOKEN_REDIS_KEY_PREFIX + token);
+
+            if(exists){
+                Object value = redisUtil.get(FrontTokenModel.FRONT_TOKEN_REDIS_KEY_PREFIX + token);
+
+                Map<String, Object> hashedMap = new HashMap<>();
+                hashedMap.put(FrontTokenModel.FRONT_TOKEN_THREAD_LOCAL_KEY, value);
+                ThreadLocalUtil.set(hashedMap);
+
+                //刷新token有效期
+                redisUtil.set(FrontTokenModel.FRONT_TOKEN_REDIS_KEY_PREFIX +token, value, FrontTokenModel.TOKEN_EXPRESS_MINUTES, TimeUnit.MINUTES);
+            }else{
+                //判断路由,部分路由不管用户是否登录/token过期都可以访问
+                exists = checkRouter(RequestUtil.getUri(request));
+            }
+
+            return exists;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    //路由在此处,则返回true,无论用户是否登录都可以访问
+    public boolean checkRouter(String uri) {
+        String[] routerList = {
+                "api/front/auth/wechat-login",
+                "api/front/auth/logout"
+        };
+        AntPathMatcher matcher = new AntPathMatcher();
+        return Arrays.asList(routerList).stream().parallel().anyMatch(arg -> matcher.match(arg, uri));
+    }
+
+    public String getTokenFormRequest(HttpServletRequest request){
+        return request.getHeader(Constants.HEADER_AUTHORIZATION_KEY);
+    }
+}

+ 25 - 0
src/main/java/com/hz3r/authorization/front/FrontTokenManager.java

@@ -0,0 +1,25 @@
+package com.hz3r.authorization.front;
+
+
+
+/**
+ * @author liu
+ * @since 1.0.0
+ */
+public interface FrontTokenManager {
+
+    FrontTokenModel createToken(String value, String modelName);
+
+    boolean checkToken(String token, String modelName);
+
+    FrontTokenModel getToken(String token, String modelName);
+
+    void deleteToken(String token, String modelName);
+
+    String getLocalInfoException(String key);
+
+    Object getLocalInfo(String key);
+
+    Long getLocalUserId();
+
+}

+ 126 - 0
src/main/java/com/hz3r/authorization/front/FrontTokenManagerImpl.java

@@ -0,0 +1,126 @@
+package com.hz3r.authorization.front;
+
+import com.hz3r.utils.RedisUtil;
+import com.hz3r.utils.ThreadLocalUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author liu
+ * @since 1.0.0
+ */
+@Component
+public class FrontTokenManagerImpl implements FrontTokenManager {
+
+    @Autowired
+    protected RedisUtil redisUtil;
+
+
+    /**
+     * 生成Token
+     * @param value String 存储value
+     * @param modelName String 模块
+     */
+    @Override
+    public FrontTokenModel createToken(String value, String modelName) {
+        String _token = UUID.randomUUID().toString().replace("-", "");
+        FrontTokenModel token = new FrontTokenModel(_token);
+        token.setLastAccessedTime(System.currentTimeMillis());
+
+        String existedIdKey = modelName + value;
+        String tokenKey = modelName + _token;
+
+//        Object existedToken = redisUtil.get(existedIdKey);
+
+        //当前用户对应的token存在,刷新token
+//        if(existedToken != null){
+//            //删除上一个token
+//            redisUtil.remove(modelName + existedToken);
+//        }
+//
+//        redisUtil.set(existedIdKey, _token);
+        redisUtil.set(tokenKey, value,
+                FrontTokenModel.TOKEN_EXPRESS_MINUTES, TimeUnit.MINUTES);
+
+        Map<String, Object> hashedMap = new HashMap<>();
+        hashedMap.put(modelName, value);
+        ThreadLocalUtil.set(hashedMap);
+
+        return token;
+    }
+
+    /**
+     * 获取本地存储的实际
+     * @param key String 模块
+     */
+    @Override
+    public String getLocalInfoException(String key) {
+        Object value = ThreadLocalUtil.get(key);
+        if(value == null){
+            throw new RuntimeException("登录信息已过期,请重新登录!");
+        }
+        return value.toString();
+    }
+
+    /**
+     * 获取本地存储的实际
+     * @param key String 模块
+     */
+    @Override
+    public Object getLocalInfo(String key) {
+        if(StringUtils.isNotBlank(key)){
+            return ThreadLocalUtil.get(key);
+        }
+        return null;
+    }
+
+    /**
+     * 获取用户id
+     */
+    @Override
+    public Long getLocalUserId() {
+        return Long.valueOf(getLocalInfoException(FrontTokenModel.FRONT_TOKEN_THREAD_LOCAL_KEY));
+    }
+
+
+
+    /**
+     * 检测Token
+     * @param token String token
+     * @param modelName String 模块
+     */
+    @Override
+    public boolean checkToken(String token, String modelName) {
+        return redisUtil.exists(modelName + token);
+    }
+
+    /**
+     * 检测Token
+     * @param token String token
+     * @param modelName String 模块
+     */
+    @Override
+    public FrontTokenModel getToken(String token, String modelName) {
+        Object o = redisUtil.get(modelName + token);
+        FrontTokenModel tokenModel = new FrontTokenModel();
+        tokenModel.setUserId(Integer.parseInt(o.toString()));
+        return tokenModel;
+    }
+
+    /**
+     * 删除Token
+     * @param token String token
+     * @param modelName String 模块
+     */
+    @Override
+    public void deleteToken(String token, String modelName) {
+        redisUtil.remove(modelName +token);
+    }
+
+}

+ 38 - 0
src/main/java/com/hz3r/authorization/front/FrontTokenModel.java

@@ -0,0 +1,38 @@
+package com.hz3r.authorization.front;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * token实体类
+ */
+@Data
+public class FrontTokenModel implements Serializable {
+
+    private static final long serialVersionUID = 4903514237492573024L;
+
+    //客户端token redis存储前缀
+    public static final String FRONT_TOKEN_REDIS_KEY_PREFIX = "FRONT_TOKEN:";
+
+    //用户打开场景threadLocal存储前缀
+    public static final String FRONT_TOKEN_THREAD_LOCAL_KEY = "id";
+
+    // 过期时间
+    public static final long TOKEN_EXPRESS_MINUTES = (60 * 3); //3小时
+    //用户ID
+    private Integer userId;
+    //用户token
+    private String token;
+    // 最后访问时间
+    private long lastAccessedTime = System.currentTimeMillis();
+    // 过期时间
+    private long expirationTime;
+
+    public FrontTokenModel(String token){
+        this.token = token;
+    }
+
+    public FrontTokenModel() {
+    }
+}

+ 358 - 0
src/main/java/com/hz3r/config/BusiRedisConfig.java

@@ -0,0 +1,358 @@
+package com.hz3r.config;
+
+import com.hz3r.utils.DPCUtils;
+import com.hz3r.utils.MyStrUtils;
+import com.hz3r.utils.SpringBeanUtils;
+import jakarta.annotation.Resource;
+import lombok.Data;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.connection.*;
+import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
+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.serializer.StringRedisSerializer;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Redis 配置
+ *
+ */
+@Configuration
+public class BusiRedisConfig {
+
+    /**
+     * Redis Key 应用前缀
+     */
+    static public final String APP_PREFIX = "ZJJHYIWUYQ";
+    /**
+     * 缓存Key前缀
+     */
+    static public final String KEY_PREFIX = APP_PREFIX + ":";
+    public static final String AUTHORIZATION = "userAuthRedis";
+    protected static final String AUTHORIZATION_FACTORY = "userAuthRedisFactory";
+
+    @Resource
+    private RedisProperties redisProperties;
+
+    @Resource
+    private Environment env;
+
+    /**
+     * 创建自定义的连接工厂。
+     *
+     * @return LettuceConnectionFactory.class
+     * @throws Exception
+     */
+    @Primary
+    @Bean
+    public LettuceConnectionFactory lettuceConnectionFactory() throws Exception {
+
+        // 生成 Redis 密码。
+        String decPwd = makeRedisPwd(redisProperties);
+        // 生成 Redis 配置。
+        RedisConfiguration config = makeRedisConfig(redisProperties, decPwd, null);
+        // 生成 Redis Client 配置,主要是连接池和超时配置。
+        LettuceClientConfiguration clientConfig = makeRedisClientConfig(redisProperties);
+
+        // 创建Factory对象
+        LettuceConnectionFactory factory = new LettuceConnectionFactory(config, clientConfig);
+        factory.afterPropertiesSet();
+        return factory;
+    }
+
+    /**
+     * 创建自定义的连接工厂。
+     *
+     * @return LettuceConnectionFactory.class
+     * @throws Exception
+     */
+    @Bean(AUTHORIZATION_FACTORY)
+    public LettuceConnectionFactory lettuceConnectionFactory4Auth() throws Exception {
+
+        // 生成 Redis 密码。
+        String decPwd = makeRedisPwd(redisProperties);
+        // 生成 Redis 配置。
+        RedisConfiguration config = makeRedisConfig(redisProperties, decPwd, 1);
+        // 生成 Redis Client 配置,主要是连接池和超时配置。
+        LettuceClientConfiguration clientConfig = makeRedisClientConfig(redisProperties);
+
+        // 创建Factory对象
+        LettuceConnectionFactory factory = new LettuceConnectionFactory(config, clientConfig);
+        factory.afterPropertiesSet();
+        return factory;
+    }
+
+    /**
+     * 创建 Redis 通用操作模块 Bean 对象。
+     *
+     * @param connectionFactory
+     * @return
+     */
+    @Primary
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        //
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 数据对象序列化器
+        com.hz3r.config.BusiRedisFastJson2JsonSerializer serializer = new com.hz3r.config.BusiRedisFastJson2JsonSerializer(Object.class);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer() {
+
+            @Override
+            public byte[] serialize(String value) {
+                //
+                value = MyStrUtils.defaultString(value);
+                boolean hasAppPrefix = MyStrUtils.startsWithIgnoreCase(value, APP_PREFIX);
+                String fullKey = hasAppPrefix ? value : KEY_PREFIX.concat(value);
+                return super.serialize(fullKey);
+            }
+
+        });
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    /**
+     * 用户凭证保存 Redis
+     *
+     * @param connectionFactory
+     * @return
+     */
+    @Bean(AUTHORIZATION)
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate4Auth(@Qualifier(AUTHORIZATION_FACTORY) RedisConnectionFactory connectionFactory) {
+        //
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 数据对象序列化器
+        com.hz3r.config.BusiRedisFastJson2JsonSerializer serializer = new com.hz3r.config.BusiRedisFastJson2JsonSerializer(Object.class);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer() {
+
+            @Override
+            public byte[] serialize(String value) {
+                //
+                return super.serialize(KEY_PREFIX.concat(MyStrUtils.defaultString(value)));
+            }
+
+        });
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    /**
+     * 生成 Redis 密码
+     *
+     * @param properties Redis 配置数据
+     * @return String
+     */
+    protected String makeRedisPwd(RedisProperties properties) {
+        // 使用 AES 解密器解密 Redis 密码
+
+        String encPwd = properties.getPassword();
+        return DPCUtils.tryDecodePass(env, encPwd);
+    }
+
+    /**
+     * 生成 Redis 配置。
+     *
+     * @param properties Redis 配置信息
+     * @param password Redis 密码
+     * @return org.springframework.data.redis.connection.RedisConfiguration.class
+     */
+    protected RedisConfiguration makeRedisConfig(RedisProperties properties, String password, Integer dbIndex) {
+        //cn.dev33.satoken.dao.SaTokenDaoRedisJackson;
+
+        //  RedisStandaloneConfiguration, RedisSocketConfiguration, RedisSentinelConfiguration, RedisStaticMasterReplicaConfiguration, or RedisClusterConfiguration.
+        RedisConfiguration config;
+        if (properties.getSentinel() != null) {
+
+            // 哨兵集群模式
+            RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
+            // 低版本没有 username 属性,捕获异常给个提示即可,无需退出程序
+            try {
+                redisSentinelConfiguration.setUsername(properties.getUsername());
+            } catch (NoSuchMethodError e){
+                System.err.println(e.getMessage());
+            }
+            // 设置密码
+            redisSentinelConfiguration.setPassword(RedisPassword.of(password));
+            redisSentinelConfiguration.setDatabase(dbIndex == null ? properties.getDatabase() : dbIndex);
+
+            RedisProperties.Sentinel sentinel = properties.getSentinel();
+            redisSentinelConfiguration.setMaster(sentinel.getMaster());
+            redisSentinelConfiguration.setSentinelPassword(sentinel.getPassword());
+            List<RedisNode> serverList = sentinel.getNodes().stream().map(node -> {
+                String[] ipAndPort = node.split(":");
+                return new RedisNode(ipAndPort[0].trim(), Integer.parseInt(ipAndPort[1]));
+            }).collect(Collectors.toList());
+            redisSentinelConfiguration.setSentinels(serverList);
+
+            config = redisSentinelConfiguration;
+        } else if (properties.getCluster() != null) {
+
+            // 普通集群模式
+            RedisClusterConfiguration redisClusterConfig = new RedisClusterConfiguration();
+            // 低版本没有 username 属性,捕获异常给个提示即可,无需退出程序
+            try {
+                redisClusterConfig.setUsername(properties.getUsername());
+            } catch (NoSuchMethodError e){
+                System.err.println(e.getMessage());
+            }
+            // 设置密码
+            redisClusterConfig.setPassword(RedisPassword.of(password));
+
+            RedisProperties.Cluster cluster = properties.getCluster();
+            List<RedisNode> serverList = cluster.getNodes().stream().map(node -> {
+                String[] ipAndPort = node.split(":");
+                return new RedisNode(ipAndPort[0].trim(), Integer.parseInt(ipAndPort[1]));
+            }).collect(Collectors.toList());
+            redisClusterConfig.setClusterNodes(serverList);
+            redisClusterConfig.setMaxRedirects(cluster.getMaxRedirects());
+
+            config = redisClusterConfig;
+        } else {
+
+            // 单体模式
+            RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
+            redisConfig.setHostName(properties.getHost());
+            redisConfig.setPort(properties.getPort());
+            redisConfig.setPassword(RedisPassword.of(password));
+            redisConfig.setDatabase(dbIndex == null ? properties.getDatabase() : dbIndex);
+            // 低版本没有 username 属性,捕获异常给个提示即可,无需退出程序
+            try {
+                redisConfig.setUsername(properties.getUsername());
+            } catch (NoSuchMethodError e){
+                System.err.println(e.getMessage());
+            }
+            config = redisConfig;
+        }
+        return config;
+    }
+
+    /**
+     * 生成 Redis Client 配置,主要是连接池和超时配置。
+     *
+     * @param properties Redis 配置信息
+     * @return org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.class
+     */
+    protected LettuceClientConfiguration makeRedisClientConfig(RedisProperties properties) {
+
+        // 2. 连接池配置
+        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
+        // pool配置
+        RedisProperties.Lettuce lettuce = properties.getLettuce();
+        if(lettuce.getPool() != null) {
+            RedisProperties.Pool pool = properties.getLettuce().getPool();
+            // 连接池最大连接数
+            poolConfig.setMaxTotal(pool.getMaxActive());
+            // 连接池中的最大空闲连接
+            poolConfig.setMaxIdle(pool.getMaxIdle());
+            // 连接池中的最小空闲连接
+            poolConfig.setMinIdle(pool.getMinIdle());
+            // 连接池最大阻塞等待时间(使用负值表示没有限制)
+            poolConfig.setMaxWait(pool.getMaxWait());
+        }
+        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
+
+        // 设置超时
+        if(properties.getTimeout() != null) {
+            builder.commandTimeout(properties.getTimeout());
+        }
+        // 设置关闭超时
+        if(lettuce.getShutdownTimeout() != null) {
+            builder.shutdownTimeout(lettuce.getShutdownTimeout());
+        }
+
+        // 构建 Redis Client Config 实例。
+        return builder.poolConfig(poolConfig).build();
+    }
+
+
+
+
+
+
+
+    /**
+     * Redis 消息监听器容器配置。
+     *
+     * @param connectionFactory
+     * @return
+     */
+    @Bean
+    public RedisMessageListenerContainer busiRedisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
+        RedisMessageListenerContainer container = null;
+        if (container == null) {
+            container = new RedisMessageListenerContainer();
+            container.setConnectionFactory(connectionFactory);
+        }
+
+        // 订阅频道并添加监听器
+        MyRedisMessageSubscriber myRedisMessageSubscriber = new MyRedisMessageSubscriber();
+        container.addMessageListener(myRedisMessageSubscriber, new ChannelTopic(myRedisMessageSubscriber.getChannelName()));
+
+        // 订阅 MyRedisMessageSubscriber Bean 对象
+        List<MyRedisMessageSubscriber> myRedisMessageSubscriberList = SpringBeanUtils.getBeanList(MyRedisMessageSubscriber.class);
+        for (MyRedisMessageSubscriber subscriber : myRedisMessageSubscriberList) {
+            container.addMessageListener(subscriber, new ChannelTopic(subscriber.getChannelName()));
+        }
+        //container.addMessageListener(new MyRedisMessageSubscriber(), new ChannelTopic(MyRedisMessageSubscriber.CHANNEL_NAME));
+        return container;
+    }
+
+    /**
+     * Redis 消息订阅器
+     *
+     */
+    @Data
+    static public class MyRedisMessageSubscriber implements MessageListener {
+        private String channelName = "my-channel";
+
+        /**
+         * 有消息到达时处理
+         *
+         * @param message
+         * @param pattern
+         */
+        @Override
+        public void onMessage(Message message, byte[] pattern) {
+            String channel = new String(message.getChannel());
+            String msg = new String(message.getBody());
+            System.out.println("Received message from channel '" + channel + "': " + msg);
+            // 在这里处理接收到的消息
+        }
+    }
+
+
+
+}

+ 61 - 0
src/main/java/com/hz3r/config/BusiRedisFastJson2JsonSerializer.java

@@ -0,0 +1,61 @@
+package com.hz3r.config;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.filter.Filter;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.nio.charset.Charset;
+
+/**
+ * Redis使用FastJson序列化
+ */
+public class BusiRedisFastJson2JsonSerializer<T> implements RedisSerializer<T> {
+    /**
+     * 默认字符编码集
+     *
+     */
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    static final Filter AUTO_TYPE_FILTER = JSONReader
+            .autoTypeFilter(new String[] { "org.springframework", "com.hz3r.xb" });
+
+    private Class<T> clazz;
+
+    public BusiRedisFastJson2JsonSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    /**
+     *
+     *
+     * @param t
+     * @return
+     * @throws SerializationException
+     */
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
+    }
+
+//    static public class CaseInsensitiveDeserializer implements ObjectReader {
+//
+//    }
+
+}

+ 39 - 0
src/main/java/com/hz3r/config/LocalDateTimeConfiguration.java

@@ -0,0 +1,39 @@
+package com.hz3r.config;
+
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+@Configuration
+public class LocalDateTimeConfiguration {
+
+    @Value("${spring.jackson.date-format}")
+    private String dateFormat;
+    @Value("${spring.jackson.locale}")
+    private Locale locale;
+
+    @Bean
+    public LocalDateTimeSerializer localDateTimeSerializer() {
+        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateFormat, locale));
+    }
+
+    @Bean
+    public LocalDateTimeDeserializer localDateTimeDeserializer() {
+        return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateFormat, locale));
+    }
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            builder.serializerByType(LocalDateTime.class, localDateTimeSerializer());
+            builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer());
+        };
+    }
+}

+ 110 - 0
src/main/java/com/hz3r/config/MybatisPlusConfiguration.java

@@ -0,0 +1,110 @@
+package com.hz3r.config;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
+import cn.hutool.log.Log;
+import cn.hutool.log.LogFactory;
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.hz3r.infrastructure.park.security.UserDetailsImpl;
+import com.hz3r.utils.AuthUtils;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.ObjectUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Configuration
+public class MybatisPlusConfiguration {
+
+    // 日志
+    static protected final Log LOG = LogFactory.getCurrentLogFactory().getLog(MybatisPlusConfiguration.class);
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
+        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return mybatisPlusInterceptor;
+    }
+
+    @Bean
+    public MetaObjectHandler metaObjectHandler() {
+        return new MetaObjectHandler() {
+
+            @Override
+            public void insertFill(MetaObject metaObject) {
+                // Ba1 业务
+                try {
+                    Object currentValue;
+
+                    strictInsertFill(metaObject, "isDel", () -> BigDecimal.ZERO.shortValue(), Short.class);
+                    strictInsertFill(metaObject, "delFlag", () -> BigDecimal.ZERO.toString(), String.class);
+
+                    // 设置统一ID
+                    currentValue = getFieldValByName("uid", metaObject);
+                    if (currentValue == null) {
+                        strictInsertFill(metaObject, "uid", () -> IdUtil.fastSimpleUUID(), String.class);
+                        strictInsertFill(metaObject, "suid", () -> IdUtil.fastSimpleUUID(), String.class);
+                    }
+                    currentValue = getFieldValByName("ba1miUid", metaObject);
+                    if (currentValue == null) {
+                        strictInsertFill(metaObject, "ba1miUid", () -> "PT", String.class);
+                    }
+
+                    // 创建信息
+                    currentValue = getFieldValByName("createdTime", metaObject);
+                    if (currentValue == null) strictInsertFill(metaObject, "createdTime", LocalDateTime::now, LocalDateTime.class);
+                    currentValue = getFieldValByName("createdBy", metaObject);
+                    if (currentValue == null) strictInsertFill(metaObject, "createdBy", () -> AuthUtils.currentUsername(), String.class);
+
+                    // 更新信息
+                    currentValue = getFieldValByName("updatedTime", metaObject);
+                    if (currentValue == null) strictInsertFill(metaObject, "updatedTime", LocalDateTime::now, LocalDateTime.class);
+                    currentValue = getFieldValByName("updatedBy", metaObject);
+                    if (currentValue == null) strictInsertFill(metaObject, "updatedBy", () -> AuthUtils.currentUsername(), String.class);
+                } catch (Exception ex) {
+                    LOG.error("Mybatis 执行 MetaObjectHandler 处理 insertFill 实体中自动填充值失败!错误消息:{},目标数据: {}", ex.getLocalizedMessage(), metaObject.getOriginalObject() == null ? null : JSONUtil.toJsonStr(metaObject.getOriginalObject()));
+                }
+                // 遗留业务或集成的其他框架的业务
+                {
+                    strictInsertFill(metaObject, "deleted", () -> Boolean.FALSE, Boolean.class);
+                    strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
+                    strictUpdateFill(metaObject, "modifyTime", LocalDateTime.class, LocalDateTime.now());
+
+                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+                    if(!ObjectUtils.isEmpty(authentication)){
+                        UserDetailsImpl principal = (UserDetailsImpl) authentication.getPrincipal();
+                        strictInsertFill(metaObject, "creatorId", Long.class, principal.getUser().getId());
+                    }
+                }
+            }
+
+            @Override
+            public void updateFill(MetaObject metaObject) {
+                // Ba1 业务
+                try {
+                    Object currentValue;
+
+                    // 更新信息
+                    currentValue = getFieldValByName("updatedTime", metaObject);
+                    if (currentValue == null) strictInsertFill(metaObject, "updatedTime", LocalDateTime::now, LocalDateTime.class);
+                    currentValue = getFieldValByName("updatedBy", metaObject);
+                    if (currentValue == null) strictInsertFill(metaObject, "updatedBy", () -> AuthUtils.currentUsername(), String.class);
+                } catch (Exception ex) {
+                    LOG.error("Mybatis 执行 MetaObjectHandler 处理 updateFill 实体中自动填充值失败!错误消息:{},目标数据: {}", ex.getLocalizedMessage(), metaObject.getOriginalObject() == null ? null : JSONUtil.toJsonStr(metaObject.getOriginalObject()));
+                }
+                // 遗留业务或集成的其他框架的业务
+                {
+                    strictUpdateFill(metaObject, "modifyTime", LocalDateTime.class, LocalDateTime.now());
+                }
+            }
+        };
+    }
+
+}

+ 47 - 0
src/main/java/com/hz3r/config/RestTemplateConfig.java

@@ -0,0 +1,47 @@
+package com.hz3r.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * RestTemplate配置组件
+
+ */
+@Configuration
+public class RestTemplateConfig {
+    @Bean
+    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
+        RestTemplate restTemplate = new RestTemplate(factory);
+        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+        restTemplate.getMessageConverters().add(new WxMappingJackson2HttpMessageConverter());
+        return restTemplate;
+    }
+
+    @Bean
+    public ClientHttpRequestFactory httpRequestFactory() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setReadTimeout(10000);//ms
+        factory.setConnectTimeout(15000);//ms
+        return factory;
+    }
+
+    //解决微信返回json Content-Type 值却是 text/plain 的问题
+    public class WxMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
+        public WxMappingJackson2HttpMessageConverter(){
+            List<MediaType> mediaTypes = new ArrayList<>();
+            mediaTypes.add(MediaType.TEXT_PLAIN);
+            mediaTypes.add(MediaType.TEXT_HTML);
+            setSupportedMediaTypes(mediaTypes);
+        }
+    }
+}

+ 47 - 0
src/main/java/com/hz3r/config/SpringDocConfig.java

@@ -0,0 +1,47 @@
+package com.hz3r.config;
+
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.Contact;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SpringDocConfig {
+
+    @Value("${park.api.title:园区管理系统 API文档}")
+    private String title;
+
+    @Value("${park.api.description:园区管理系统接口文档}")
+    private String description;
+
+    @Value("${park.api.version:1.0.0}")
+    private String version;
+
+    @Value("${park.api.contact.name:开发团队}")
+    private String contactName;
+
+    @Value("${park.api.contact.url:https://www.example.com}")
+    private String contactUrl;
+
+    @Value("${park.api.contact.email:contact@example.com}")
+    private String contactEmail;
+
+    @Bean
+    public OpenAPI parkManagementOpenAPI() {
+        return new OpenAPI()
+                .info(new Info()
+                        .title(title)
+                        .description(description)
+                        .version(version)
+                        .contact(new Contact()
+                                .name(contactName)
+                                .url(contactUrl)
+                                .email(contactEmail)))
+                .externalDocs(new ExternalDocumentation()
+                        .description("园区管理系统文档")
+                        .url("https://docs.example.com"));
+    }
+}

+ 58 - 0
src/main/java/com/hz3r/config/WebConfiguration.java

@@ -0,0 +1,58 @@
+package com.hz3r.config;
+
+import com.hz3r.web.interceptor.AppTokenInterceptor;
+import com.hz3r.web.interceptor.FrontTokenInterceptor;
+import com.hz3r.web.interceptor.RequestIdInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfiguration implements WebMvcConfigurer {
+
+    @Bean
+    public CorsFilter corsFilter() {
+
+        CorsConfiguration corsConfiguration = new CorsConfiguration();
+        corsConfiguration.setAllowCredentials(true);
+        corsConfiguration.addAllowedHeader("*");
+        corsConfiguration.addAllowedMethod("*");
+        corsConfiguration.addAllowedOriginPattern("*");
+
+        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
+        corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
+
+        return new CorsFilter(corsConfigurationSource);
+    }
+
+    @Bean
+    public HandlerInterceptor frontTokenInterceptor(){
+        return new FrontTokenInterceptor();
+    }
+
+    @Bean
+    public AppTokenInterceptor appTokenInterceptor(){
+        return new AppTokenInterceptor();
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+
+        registry.addInterceptor(new RequestIdInterceptor());
+
+        registry.addInterceptor(frontTokenInterceptor()).
+                addPathPatterns("/api/front/**").
+                excludePathPatterns("/v3/api-docs/**","/swagger-ui/**","/swagger-ui.html");
+
+        registry.addInterceptor(appTokenInterceptor()).
+                addPathPatterns("/api/app/**").
+                excludePathPatterns("/v3/api-docs/**","/swagger-ui/**","/swagger-ui.html");
+
+
+    }
+}

+ 22 - 0
src/main/java/com/hz3r/config/ds/DsSelector.java

@@ -0,0 +1,22 @@
+package com.hz3r.config.ds;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据源选择器
+ *
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DsSelector {
+
+    /**
+     * 本业务主库
+     *
+     * @return
+     */
+    String value() default "dataSource";
+}

+ 21 - 0
src/main/java/com/hz3r/config/log/pattern/CustomPatternLayout.java

@@ -0,0 +1,21 @@
+package com.hz3r.config.log.pattern;
+
+import ch.qos.logback.classic.PatternLayout;
+import com.hz3r.config.log.pattern.converters.*;
+
+/**
+ * 自定义日志布局
+ *
+ * 扩展 Logback 的 PatternLayout 以实现自定义日志格式
+ */
+public class CustomPatternLayout extends PatternLayout {
+
+    // 注册自定义转换器
+    static {
+        PatternLayout.defaultConverterMap.put("app",     CustomAppNameConverter.class.getName());
+        PatternLayout.defaultConverterMap.put("tid",     CustomThreadIdConverter.class.getName());
+        PatternLayout.defaultConverterMap.put("ctx",     CustomContextConverter.class.getName());
+        PatternLayout.defaultConverterMap.put("req_id",  CustomRequestIdConverter.class.getName());
+        PatternLayout.defaultConverterMap.put("ole",     CustomOneLineExceptionConverter.class.getName());
+    }
+}

+ 21 - 0
src/main/java/com/hz3r/config/log/pattern/converters/CustomAppNameConverter.java

@@ -0,0 +1,21 @@
+package com.hz3r.config.log.pattern.converters;
+
+import ch.qos.logback.classic.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import com.hz3r.infrastructure.park.config.DataOps;
+import com.hz3r.utils.SpringBeanUtils;
+
+/**
+ * 自定义应用程序名称转换器
+ *
+ * 用法:在 PatternLayout 中使用 %app 来引用此转换器
+ */
+public class CustomAppNameConverter extends ClassicConverter {
+
+    private static final String APP_NAME = System.getenv("APP_NAME");
+
+    @Override
+    public String convert(ILoggingEvent event) {
+        return APP_NAME != null ? APP_NAME : "Hz3rApp";
+    }
+}

+ 31 - 0
src/main/java/com/hz3r/config/log/pattern/converters/CustomContextConverter.java

@@ -0,0 +1,31 @@
+package com.hz3r.config.log.pattern.converters;
+
+import ch.qos.logback.classic.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * 自定义上下文信息转换器
+ *
+ * 用法:在 PatternLayout 中使用 %ctx 来引用此转换器
+ */
+@Component
+public class CustomContextConverter extends ClassicConverter implements ApplicationContextAware  {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        CustomContextConverter.applicationContext = applicationContext;
+    }
+
+    @Override
+    public String convert(ILoggingEvent event) {
+        if (applicationContext != null) {
+            return applicationContext.getId();
+        }
+        return "spring-booting";
+    }
+}

+ 94 - 0
src/main/java/com/hz3r/config/log/pattern/converters/CustomOneLineExceptionConverter.java

@@ -0,0 +1,94 @@
+package com.hz3r.config.log.pattern.converters;
+
+
+import ch.qos.logback.classic.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import ch.qos.logback.classic.spi.ThrowableProxyVO;
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.core.util.NumberUtil;
+import com.hz3r.infrastructure.park.domain.constant.share.SystemCacheType;
+import com.hz3r.infrastructure.park.service.share.SystemCacheService;
+import com.hz3r.utils.MyStrUtils;
+import com.hz3r.utils.SpringBeanUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 自定义异常堆栈合并成行转换器
+ *
+ * 用法:在 PatternLayout 中使用 %ctx 来引用此转换器
+ */
+public class CustomOneLineExceptionConverter extends ClassicConverter {
+
+    @Override
+    public String convert(ILoggingEvent event) {
+        // 异常栈合并成一行输出。
+        String oneLineMessage = MyStrUtils.EMPTY;
+        SystemCacheService SCService = SpringBeanUtils.getBean(SystemCacheService.class);
+        Boolean oneLineExceptionCallStack =  SCService.describeAll().stream()
+                .filter(e -> e.getKey() == SystemCacheType.LOG_OUT_ONE_LINE_EXCEPTION)
+                .map(e -> MyStrUtils.equalsIgnoreCase(MyStrUtils.defaultString(e.getValue()), "true"))
+                .findFirst().orElse(Boolean.TRUE);
+        // 默认行长度
+        final Integer defaultLineSize = 2000;
+        int oneLineMessageSize =  SCService.describeAll().stream()
+                .filter(e -> e.getKey() == SystemCacheType.LOG_OUT_ONE_LINE_SIZE)
+                .map(e -> NumberUtil.parseInt(MyStrUtils.defaultIfBlank(e.getValue(), String.valueOf(defaultLineSize))))
+                .findFirst().orElse(defaultLineSize);
+        if ((oneLineExceptionCallStack) && (event.getCallerData() != null && event.getCallerData().length > 0)) {
+            //if (event.getThrowableProxy() instanceof ThrowableProxy) {
+            //    ThrowableProxy throwableProxy = ((ThrowableProxy) event.getThrowableProxy());
+            //    oneLineMessage = ExceptionUtil.stacktraceToOneLineString(throwableProxy.getThrowable(), oneLineMessageSize * 3);
+            //} else if (event.getThrowableProxy() instanceof Throwable) {
+            //    Throwable throwable = ((Throwable) event.getThrowableProxy());
+            //    oneLineMessage = ExceptionUtil.stacktraceToOneLineString(throwable, oneLineMessageSize * 3);
+            //}
+
+            String hz3rRootPackageSearch = ".hz3r.";
+            String hz3rRootPackage = MyStrUtils.substring(this.getClass().getPackageName(),
+                    0, this.getClass().getPackageName().indexOf(hz3rRootPackageSearch) + hz3rRootPackageSearch.length());
+            List<String> busCodeStack = new ArrayList<>(100);
+            String splitDelimiter = ",";
+            String[] packageFilters =  SCService.describeAll().stream()
+                    .filter(e -> e.getKey() == SystemCacheType.LOG_OUT_ONE_LINE_PACKAGES)
+                    .map(e -> {
+                        String value = MyStrUtils.replace(MyStrUtils.defaultString(e.getValue()), ",", splitDelimiter);
+                        value = StrFormatter.format("{},{}", hz3rRootPackage, value);
+                        value = MyStrUtils.replace(value, ",,", splitDelimiter);
+                        return MyStrUtils.split(value, splitDelimiter);
+                    })
+                    .findFirst().orElse(new String[] { hz3rRootPackage, "org.springframework.web.servlet.mvc."});
+            Arrays.stream(event.getThrowableProxy().getStackTraceElementProxyArray())
+                    .forEach(e -> {
+                        if (MyStrUtils.startsWithAny(e.getStackTraceElement().getClassName(), packageFilters)) {
+                            busCodeStack.add(e.getSTEAsString());
+                        }
+                    });
+            oneLineMessage = MyStrUtils.join(busCodeStack, "\\n\t");
+
+            // 缩减长度,只留指定长度的字符串。
+            if (oneLineMessage.length() > oneLineMessageSize) {
+                int startSize = oneLineMessageSize / 2;
+                startSize = startSize <= 0 ? oneLineMessageSize : startSize;
+                int endSize = oneLineMessageSize - startSize;
+                oneLineMessage = StrFormatter.format("{}\\n...\\n{}",
+                        MyStrUtils.substring(oneLineMessage, 0, startSize),
+                        MyStrUtils.substring(oneLineMessage, -(endSize), oneLineMessage.length()));
+            }
+            // ch.qos.logback.classic.spi.LoggingEvent
+            if (event instanceof LoggingEvent) {
+                LoggingEvent loggingEvent = LoggingEvent.class.cast(event);
+                //loggingEvent.setCallerData(null);
+                //loggingEvent.setThrowableProxy(null);
+                //(ThrowableProxyVO.class.cast(loggingEvent.getThrowableProxy())).set()
+            }
+        }
+        final String specHead = ",#!!///ole";
+        return MyStrUtils.isBlank(oneLineMessage) ? oneLineMessage : StrFormatter.format("{}    at{}", specHead, oneLineMessage);
+    }
+}

+ 73 - 0
src/main/java/com/hz3r/config/log/pattern/converters/CustomRequestIdConverter.java

@@ -0,0 +1,73 @@
+package com.hz3r.config.log.pattern.converters;
+
+
+import ch.qos.logback.classic.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.core.util.IdUtil;
+import com.hz3r.utils.MyDbOpsUtils;
+import com.hz3r.utils.SpringBeanUtils;
+import com.hz3r.web.interceptor.RequestIdInterceptor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+/**
+ * 自定义 HTTP 请求ID转换器
+ *
+ * 用法:在 PatternLayout 中使用 %ctx 来引用此转换器
+ */
+public class CustomRequestIdConverter extends ClassicConverter {
+
+    static protected final ThreadLocal<String> CT_REQUEST_TYPE = ThreadLocal.withInitial(() -> {
+        List<Class> chkList = SpringBeanUtils.getBeanTypeList(DispatcherServlet.class);
+        return CollectionUtils.isEmpty(chkList) ? "" : "bgtask";
+    });
+    static protected final ThreadLocal<String> CT_REQUEST_ID = ThreadLocal.withInitial(() -> StrFormatter.format("{}", IdUtil.fastSimpleUUID()));
+
+    /**
+     * 更新 当前 线程的自定义日志请求类型。
+     *
+     * @param type
+     * @return
+     */
+    static public String updateRequestType(String type) {
+        synchronized (CT_REQUEST_TYPE) {
+            CT_REQUEST_TYPE.set(type);
+        }
+        return StrFormatter.format("{}{}", CT_REQUEST_TYPE.get(), CT_REQUEST_ID.get());
+    }
+
+    /**
+     * 更新 当前 线程的自定义日志请求ID。
+     *
+     * @param nId
+     * @return
+     */
+    static public String updateRequestId(String nId) {
+        synchronized (CT_REQUEST_ID) {
+            CT_REQUEST_ID.set(nId);
+        }
+        return StrFormatter.format("{}{}", CT_REQUEST_TYPE.get(), CT_REQUEST_ID.get());
+    }
+
+    @Override
+    public String convert(ILoggingEvent event) {
+        // 当前请求属性
+        RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
+        if (reqAttrs == null) {
+            return StrFormatter.format("{}{}", CT_REQUEST_TYPE.get(), CT_REQUEST_ID.get());
+        }
+        // 请求属性中保存的请求 ID
+        Object reqIdObj = reqAttrs.getAttribute(RequestIdInterceptor.REQUEST_ID_NAME, RequestAttributes.SCOPE_REQUEST);
+        //String reqId = reqIdObj == null ? CT_REQUEST_ID.get() : String.valueOf(reqIdObj);
+        //return StrFormatter.format("http{}", reqId);
+        return reqIdObj == null ? CT_REQUEST_ID.get() : String.valueOf(reqIdObj);
+    }
+}

+ 17 - 0
src/main/java/com/hz3r/config/log/pattern/converters/CustomThreadIdConverter.java

@@ -0,0 +1,17 @@
+package com.hz3r.config.log.pattern.converters;
+
+import ch.qos.logback.classic.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+/**
+ * 自定义线程ID转换器
+ *
+ * 用法:在 PatternLayout 中使用 %tid 来引用此转换器
+ */
+public class CustomThreadIdConverter extends ClassicConverter {
+
+    @Override
+    public String convert(ILoggingEvent event) {
+        return "TID-" + Thread.currentThread().getId();
+    }
+}

+ 7 - 0
src/main/java/com/hz3r/domain/common/CodeEnum.java

@@ -0,0 +1,7 @@
+package com.hz3r.domain.common;
+
+// 基础枚举接口(可选)
+public interface CodeEnum {
+    String getCode();
+    String getDescription();
+}

+ 23 - 0
src/main/java/com/hz3r/domain/constant/Constants.java

@@ -0,0 +1,23 @@
+package com.hz3r.domain.constant;
+
+public class Constants {
+
+    public static final String CODE_NAME = "Code";
+    public static final String MESSAGE_NAME = "Message";
+    public static final String REQUEST_ID_NAME = "RequestId";
+    public static final String REQUEST_ID_HEADER_NAME = "Request-Id";
+    //头部 token令牌key
+    public static final String HEADER_AUTHORIZATION_KEY = "Authorization";
+
+    //默认分页
+    public static final int DEFAULT_PAGE = 0;
+    public static final int DEFAULT_SIZE = 20;
+
+    public static final String INVALID_PARAM = "INVALID_PARAM";
+    public static final String CODE_INVALID = "CODE_INVALID";
+
+    public static final String DICT_CODE_INDUSTRY_CATEGORY = "industryCategory";
+    public static final String DICT_CODE_TENANT_TAG = "tenantTags";
+
+
+}

+ 89 - 0
src/main/java/com/hz3r/domain/constant/system/UserBizTypeEnum.java

@@ -0,0 +1,89 @@
+package com.hz3r.domain.constant.system;
+
+import cn.hutool.core.map.MapUtil;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 用户业务类型枚举
+ *
+ */
+public enum UserBizTypeEnum {
+
+    GUEST ("游客", "guest", "游客,仅浏览"),
+    NORMAL ("普通用户", "normal", "普通用户"),
+    MCH ("商家用户", "mch", "商户类型用户"),
+    PT ("平台用户", "pt", "平台系统用户")
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, UserBizTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public UserBizTypeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    UserBizTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 56 - 0
src/main/java/com/hz3r/domain/ctx/BusMeterBillCtxData.java

@@ -0,0 +1,56 @@
+package com.hz3r.domain.ctx;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 水电账单上下文数据
+ *
+ */
+@Data
+public class BusMeterBillCtxData {
+
+    /**
+     * 月账单日(月天,即当月的第几天)
+     */
+    private int monthlyBillDay;
+
+    /**
+     * 月账单日期
+     */
+    private LocalDate monthlyBillDate;
+
+    /**
+     * 月账单开始日期
+     */
+    private LocalDate monthlyBillStartDate;
+
+    /**
+     * 月账单设备数据读取开始时间,账单开始日期匹配 bus_meter_reading 中的 read_time,
+     * 为 月账单开始日期 + 1 比如月开始日期是 2026-03-25,读取 read_time= '2026-03-26 00:00:00' 的设备记录值。
+     * 26号读取的是26号前的设备最后记录值。
+     */
+    private LocalDateTime monthlyDeviceReadStartTime;
+    /**
+     * 月账单设备数据读取结束时间,账单开始日期匹配 bus_meter_reading 中的 read_time,
+     * 比如月开始日期是 2026-04-25,读取 read_time= '2026-04-25 00:00:00' 的设备记录值。
+     * 25号读取的是25号前的设备最后记录值。
+     */
+    private LocalDateTime monthlyDeviceReadEndTime;
+
+    /**
+     * 日账单日期,账单日期匹配 bus_meter_reading 中的 read_time,
+     * 比如今天 2026-04-11 日账单,读取 read_time= '2026-04-11 00:00:00' 的设备记录值。
+     * 11号读取的是11号前的设备最后记录值。
+     */
+    private LocalDate dailyBillDate;
+
+
+    /**
+     * 批处理账单号,用来标记一批账单记录
+     */
+    private String billBatchNo;
+
+}

+ 80 - 0
src/main/java/com/hz3r/domain/ctx/BusiSvcCtxData.java

@@ -0,0 +1,80 @@
+package com.hz3r.domain.ctx;
+
+import com.hz3r.domain.vo.BasePageQueryVo;
+import com.hz3r.infrastructure.park.domain.entity.AbstractEntity;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * 业务服务上下文数据
+ *
+ * @param <T>
+ */
+@Data
+public class BusiSvcCtxData<T extends AbstractEntity> {
+
+    private String dataCode;
+    /**
+     * 上游数据的 (ServiceIDName, IDValue) 列表
+     */
+    private List<Map<String, Supplier<Long>>> upRelIds;
+    /**
+     * 下游数据引用当前数据的字段名称
+     */
+    private String downRelIdName;
+    /**
+     * 当前数据
+     */
+    private List<T> dataList;
+    /**
+     * 当前服务类型
+     */
+    private Class svcKlass;
+    /**
+     * 当前数据实体类型
+     */
+    private Class<T> entityClass;
+    /**
+     * 当前实体ORM
+     */
+    private BaseMapper<T> mapper;
+
+
+
+    @Data
+    public static class QueryWrapperData {
+        /**
+         * OR 子句集合
+         *
+         */
+        private Map<String, QueryWrapper> mapNameToSubOrQWrap = new HashMap<>(5);
+        private String column;
+        private BasePageQueryVo BSQV;
+        private Supplier valSupplier;
+        private Object data;
+        private String relUp;
+        private Class fKlass;
+
+        public QueryWrapperData() {}
+
+        static public <QWT> QueryWrapperData of(String column, BasePageQueryVo BSQV,
+                            Supplier valSupplier, QWT data, String relUp, Class fKlass) {
+            QueryWrapperData qWrap = new QueryWrapperData();
+            qWrap.column = column;
+            qWrap.BSQV = BSQV;
+            qWrap.valSupplier = valSupplier;
+            qWrap.data = data;
+            qWrap.relUp = relUp;
+            qWrap.fKlass = fKlass;
+
+            return qWrap;
+        }
+
+    }
+}

+ 47 - 0
src/main/java/com/hz3r/domain/entity/BaseEntity.java

@@ -0,0 +1,47 @@
+package com.hz3r.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 基础实体对象,保存公共字段。
+ *
+ */
+@Data
+public class BaseEntity {
+
+    /**
+     * 数据主键,ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 逻辑删除字段,
+     * service::updateXX/mapper::updateXX 更新方法不会修改该值
+     * service::removeXX/mapper::deleteXX 删除方法才会影响该值
+     */
+    @TableLogic
+    private Integer deleted;
+    /**
+     * 数据创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+    /**
+     * 数据更新时间
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime modifyTime;
+    /**
+     * 数据创建人ID
+     */
+    @Schema(description = "创建人ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long creatorId;
+}

+ 47 - 0
src/main/java/com/hz3r/domain/entity/EntitySearchConfig.java

@@ -0,0 +1,47 @@
+package com.hz3r.domain.entity;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 实体查询配置
+ *
+ */
+@Data
+public class EntitySearchConfig {
+
+    @Schema(description = "匹配当前值")
+    @JsonProperty("Like")
+    String like;
+
+    @Schema(description = "排除当前值")
+    @JsonProperty("Neq")
+    Object neq;
+
+    @Schema(description = "区段开始,如开始时间")
+    @JsonProperty("Start")
+    Object start;
+
+    @Schema(description = "区段结束,如结束时间")
+    @JsonProperty("End")
+    Object end;
+
+    @Schema(description = "枚举值列表,如类型列表")
+    @JsonProperty("In")
+    List<Object> in;
+
+    @Schema(description = "数据排序指示,传入 true 表示正序排序")
+    @JsonProperty("OrderByAsc")
+    Boolean orderByAsc;
+
+    @Schema(description = "数据排序先后顺序")
+    @JsonProperty("OrderSort")
+    int orderSort = 0;
+
+    @Schema(description = "OR子集名称")
+    @JsonProperty("SubOrName")
+    String subOrName;
+}

+ 12 - 0
src/main/java/com/hz3r/domain/page/Paginable.java

@@ -0,0 +1,12 @@
+package com.hz3r.domain.page;
+
+public interface Paginable {
+
+    long getCurrent();
+
+    void setCurrent(long current);
+
+    long getSize();
+
+    void setSize(long size);
+}

+ 27 - 0
src/main/java/com/hz3r/domain/page/Paginator.java

@@ -0,0 +1,27 @@
+package com.hz3r.domain.page;
+
+import java.util.List;
+
+public interface Paginator<T> extends Paginable {
+
+    List<T> getRecords();
+
+    void setRecords(List<T> records);
+
+    long getTotal();
+
+    void setTotal(long total);
+
+    default long getPages() {
+        if (getSize() == 0) {
+            return 0L;
+        }
+        long pages = getTotal() / getSize();
+        if (getTotal() % getSize() != 0) {
+            pages++;
+        }
+        return pages;
+    }
+
+    default void setPages(long pages) {}
+}

+ 30 - 0
src/main/java/com/hz3r/domain/result/PageResult.java

@@ -0,0 +1,30 @@
+package com.hz3r.domain.result;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class PageResult<T> {
+    
+    private Long total;
+    
+    private Long size;
+    
+    private Long current;
+    
+    private Long pages;
+    
+    private List<T> records;
+    
+    public static <T> PageResult<T> success(IPage<T> page) {
+        PageResult<T> result = new PageResult<>();
+        result.setTotal(page.getTotal());
+        result.setSize(page.getSize());
+        result.setCurrent(page.getCurrent());
+        result.setPages(page.getPages());
+        result.setRecords(page.getRecords());
+        return result;
+    }
+}

+ 69 - 0
src/main/java/com/hz3r/domain/result/Result.java

@@ -0,0 +1,69 @@
+package com.hz3r.domain.result;
+
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.hz3r.infrastructure.park.domain.enums.meter.ResultCode;
+import com.hz3r.utils.SpringBeanUtils;
+import lombok.Data;
+
+@Data
+public class Result<T> {
+
+
+    private Integer code;
+
+    private String message;
+
+    private T data;
+
+    private Long timestamp;
+
+    public Result() {
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public static <T> Result<T> success(T data) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage("success");
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> error(String message) {
+        Result<T> result = new Result<>();
+        result.setCode(500);
+        result.setMessage(message);
+        return result;
+    }
+
+    public static <T> Result<T> error(Integer code, String message) {
+        Result<T> result = new Result<>();
+        result.setCode(code);
+        result.setMessage(message);
+        return result;
+    }
+
+    public static <T> Result<T> fail(ResultCode code, String message) {
+        Result<T> result = new Result<>();
+        result.setCode(code.getCode());
+        result.setMessage(message);
+        return result;
+    }
+
+    /**
+     * 返回JSON字符串。
+     *
+     */
+    public String toJsonStr() {
+        ObjectMapper objectMapper = SpringBeanUtils.getBean(ObjectMapper.class);
+        try {
+            if (objectMapper != null) {
+                return objectMapper.writeValueAsString(this);
+            }
+        } catch (Exception ex) {
+            System.out.println(ex.getLocalizedMessage());
+        }
+        return JSONUtil.toJsonStr(this);
+    }
+}

+ 17 - 0
src/main/java/com/hz3r/domain/sort/SortMetadata.java

@@ -0,0 +1,17 @@
+package com.hz3r.domain.sort;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SortMetadata {
+
+    private Map<String, String> metadata = new HashMap<>();
+
+    public void put(String key, String value) {
+        metadata.put(key, value);
+    }
+
+    public String get(String key) {
+        return metadata.get(key);
+    }
+}

+ 19 - 0
src/main/java/com/hz3r/domain/sort/SortMetadataBuilder.java

@@ -0,0 +1,19 @@
+package com.hz3r.domain.sort;
+
+public class SortMetadataBuilder {
+
+    private SortMetadata sortMetadata = new SortMetadata();
+
+    public static SortMetadataBuilder builder() {
+        return new SortMetadataBuilder();
+    }
+
+    public SortMetadataBuilder withMetadata(String key, String value) {
+        sortMetadata.put(key, value);
+        return this;
+    }
+
+    public SortMetadata build() {
+        return sortMetadata;
+    }
+}

+ 8 - 0
src/main/java/com/hz3r/domain/sort/Sortable.java

@@ -0,0 +1,8 @@
+package com.hz3r.domain.sort;
+
+public interface Sortable {
+
+    String getSort();
+
+    void setSort(String sort);
+}

+ 40 - 0
src/main/java/com/hz3r/domain/sort/Sorter.java

@@ -0,0 +1,40 @@
+package com.hz3r.domain.sort;
+
+import com.hz3r.exception.ClientRuntimeException;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public interface Sorter extends Sortable {
+
+    SortMetadata getSortMetadata();
+
+    default String getDefaultSort() {
+        return "id_asc";
+    }
+
+    default String getSortSql() {
+
+        List<String> sortSqls = new ArrayList<>();
+        SortMetadata sortMetadata = getSortMetadata();
+
+        if (sortMetadata != null) {
+            String sortStr = getSort();
+            if (StringUtils.hasText(sortStr)) {
+                String[] sorters = sortStr.split(",");
+                for (String sorter : sorters) {
+                    String sortSql = sortMetadata.get(sorter);
+                    if (sortSql != null) {
+                        sortSqls.add(sortSql);
+                    } else {
+                        throw new ClientRuntimeException("InvalidParameter", "排序参数不正确");
+                    }
+                }
+            }
+            return String.join(",", sortSqls);
+        } else {
+            return getSort();
+        }
+    }
+}

+ 14 - 0
src/main/java/com/hz3r/domain/tree/TreeNode.java

@@ -0,0 +1,14 @@
+package com.hz3r.domain.tree;
+
+import java.util.List;
+
+public interface TreeNode<T extends TreeNode<T>> {
+
+    Long getId();
+    Long getParentId();
+    void setChildren(List<T> children);
+//    Integer getIndex();
+//    List<T> getChildren();
+//    boolean addChild(T child);
+//    boolean removeChild(T child);
+}

+ 235 - 0
src/main/java/com/hz3r/domain/vo/BasePageQueryVo.java

@@ -0,0 +1,235 @@
+package com.hz3r.domain.vo;
+
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.bean.copier.CopyOptions;
+import cn.hutool.core.map.MapBuilder;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.hz3r.domain.entity.EntitySearchConfig;
+import com.hz3r.domain.page.Paginable;
+import com.hz3r.exception.ClientRuntimeException;
+import com.hz3r.infrastructure.park.domain.vo.SearchConfigExt;
+import com.hz3r.utils.MyStrUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class BasePageQueryVo extends BaseSortQueryVo implements Paginable {
+
+    /**
+     * 当前用户名条件
+     */
+    private String currentUsername = MyStrUtils.EMPTY; //AuthUtils.currentUsername();
+
+    @Schema(description = "【仅查询】分页-当前页码")
+    @JsonProperty("current")
+    private long current = 1;
+    @Schema(description = "【仅查询】分页-每页数量")
+    @JsonProperty("size")
+    private long size = 20;
+
+    @Schema(description = "【仅查询】字符串查询模式,0=左匹配(value%,默认),1=左右匹配(%value%),2=右匹配(%value),3=精确匹配")
+    @JsonProperty("strMatchMode")
+    private int strMatchMode = 0;
+
+    /**
+     *         {
+     *                 "B1zppecId": 0
+     *             },
+     *             "QD": {
+     *                 "StrMatchMode": 0,
+     *                 "SearchConfig": {
+     *                     "B1zppecId": [
+     *                         {
+     *                             "In": [1,2,3,4,5],
+     *                             "OrderByAsc": false,
+     *                             "OrderSort": 80
+     *                         }
+     *                     ]
+     *                 }
+     *             }
+     *         },
+     *         {
+     *             "SF": {
+     *                 "DepartmentId": 0
+     *             },
+     *             "QD": {
+     *                 "StrMatchMode": 0,
+     *                 "SearchConfig": {
+     *                     "DepartmentId": [
+     *                         {
+     *                             "In": [1,2,3,4,5],
+     *                             "OrderByAsc": false,
+     *                             "OrderSort": 80
+     *                         }
+     *                     ]
+     *                 }
+     *             }
+     *         }
+     *
+     */
+    @Schema(description = "【仅查询】查询配置(放在Body中),如时间段查询"
+            + "{\"字段名称\": [ "
+            + "    { \"Start\": \"1987-01-01\", \"End\": \"2005-01-01\" }, //在范围中"
+            + "    { \"In\": [1,2,3,\"A\", \"好\"] }, //在列表中"
+            + "    { \"Neq\": \"A\" },                // 不等于"
+            + "    { \"Neq\": 0 },                    // 不等于"
+            + "    { \"OrderByAsc\": true, \"OrderSort\": 0 },          // 数据排序(true=正序,false=倒序)"
+            + "  ] "
+            + "}")
+    @JsonProperty("searchConfig")
+    private Map<String, List<EntitySearchConfig>> searchConfig;
+
+    @Schema(description = "【仅查询】查询配置文本(作为查询参数,客户端要先用 UrlEncoder 进行预编码),配置参考 SearchConfig")
+    @JsonProperty("searchConfigTxt")
+    private String searchConfigTxt;
+
+    @Schema(description = "导出头列表,格式 [ { \"prop\": \"bName\", \"title\": \"名称\" } ]")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @JsonProperty("exportHeaders")
+    private String exportHeaders;
+
+
+
+    @Override
+    public long getCurrent() {
+        return current;
+    }
+
+    @Override
+    public void setCurrent(long current) {
+        this.current = current;
+    }
+
+    @Override
+    public long getSize() {
+        return size;
+    }
+
+    @Override
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    public String getCurrentUsername() {
+        return currentUsername;
+    }
+
+    public void setCurrentUsername(String currentUsername) {
+        this.currentUsername = currentUsername;
+    }
+
+    public int getStrMatchMode() {
+        return strMatchMode;
+    }
+
+    public void setStrMatchMode(int strMatchMode) {
+        this.strMatchMode = strMatchMode;
+    }
+
+    public Map<String, List<EntitySearchConfig>> getSearchConfig() {
+        return searchConfig;
+    }
+
+    public void setSearchConfig(Map<String, List<EntitySearchConfig>> searchConfig) {
+        this.searchConfig = searchConfig == null || searchConfig.isEmpty() ? this.searchConfig : searchConfig;
+    }
+
+    public void initSearchConfig() { this.searchConfig = this.searchConfig == null ? new HashMap<>(5) : this.searchConfig; }
+
+    public String getSearchConfigTxt() {
+        return searchConfigTxt;
+    }
+
+    public void setSearchConfigTxt(String searchConfigTxt) {
+        this.searchConfigTxt = searchConfigTxt;
+        final Map<String, List<EntitySearchConfig>> ECT;
+
+        /*
+        {
+            "CheckingTime": [
+                {
+                    "Start": "2024-03-06 00:00:00",
+                    "End": "2024-03-06 23:59:59"
+                },
+                { "OrderByAsc": false, "OrderSort": 1 }
+            ],
+            "CheckedOrg": [
+                {
+                    "In": ["A","A","A","A","A"]
+                },
+                { "OrderByAsc": true, "OrderSort": 2 }
+            ],
+            "Id": [
+                {
+                    "In": [1,1,1,1]
+                }, {
+                    "Neq": 0
+                },
+                { "OrderByAsc": false, "OrderSort": 3 }
+            ]
+        }
+        */
+        ECT = new HashMap<>();
+        try {
+            final Map root = JSONUtil.toBean(MyStrUtils.defaultIfBlank(this.searchConfigTxt, "{}"), Map.class);
+            root.keySet().forEach(e -> {
+                ECT.put(String.valueOf(e),
+                        JSONArray.class.cast(root.get(e))
+                                .stream()
+                                .map(EJO -> {
+                                    EntitySearchConfig ESC = new EntitySearchConfig();
+                                    BeanUtil.copyProperties(EJO, ESC,
+                                            CopyOptions.create().ignoreCase().ignoreNullValue());
+                                    // ESC = JSONUtil.toBean(JSONObject.class.cast(EJO), EntitySearchConfig.class);
+                                    return ESC;
+                                })
+                                .collect(Collectors.toList()));
+            });
+        } catch (Exception ex) {
+            throw new ClientRuntimeException(ClientRuntimeException.UNKNOWN, ex.getLocalizedMessage());
+        }
+
+        this.searchConfig = ECT;
+    }
+
+    public String getExportHeaders() {
+        return exportHeaders;
+    }
+
+    public void setExportHeaders(String exportHeaders) {
+        this.exportHeaders = exportHeaders;
+    }
+
+
+    public BasePageQueryVo autoGenConfig() {
+        SearchConfigExt.autoFillSearchConfig(this);
+        return this;
+    }
+
+    public List<Map<String, String>> getExportHeadersObject() {
+        List<JSONObject> JOL = JSONUtil.toList(MyStrUtils.isBlank(exportHeaders) ? "[]"
+                : JSONUtil.isTypeJSONArray(exportHeaders) ? exportHeaders
+                : StrFormatter.format("[{}]", exportHeaders), JSONObject.class);
+        return JOL.stream()
+                .map(e -> MapBuilder.create(new LinkedHashMap<String, String>(2))
+                        .put("prop", String.valueOf(e.getOrDefault("prop", e.getOrDefault("Prop", MyStrUtils.EMPTY))))
+                        .put("title", String.valueOf(e.getOrDefault("title",
+                                e.getOrDefault("Title",
+                                        e.getOrDefault("label",
+                                                e.getOrDefault("Label", MyStrUtils.EMPTY)
+                                        )))))
+                        .build())
+                .collect(Collectors.toList());
+    }
+}

+ 25 - 0
src/main/java/com/hz3r/domain/vo/BaseSortQueryVo.java

@@ -0,0 +1,25 @@
+package com.hz3r.domain.vo;
+
+
+import com.hz3r.domain.sort.SortMetadata;
+import com.hz3r.domain.sort.Sorter;
+
+public class BaseSortQueryVo implements Sorter {
+
+    private String sort;
+
+    @Override
+    public String getSort() {
+        return sort;
+    }
+
+    @Override
+    public void setSort(String sort) {
+        this.sort = sort;
+    }
+
+    @Override
+    public SortMetadata getSortMetadata() {
+        return null;
+    }
+}

+ 40 - 0
src/main/java/com/hz3r/domain/vo/FileContentExchangeVo.java

@@ -0,0 +1,40 @@
+package com.hz3r.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 文件内容交换 视图模型
+ *
+ */
+@Data
+@Schema(name = "★文件内容交换 - 数据代码 FileContentExchange", description = "★文件内容交换,Java 类型 FileContentExchangeVo 对象。")
+public class FileContentExchangeVo {
+
+    @Schema(description = "导入ID,分页查询导入数据")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @JsonProperty("importId")
+    private String importId;
+
+    @Schema(description = "每页数量,大数据量分页查询导入数据")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @JsonProperty("pageSize")
+    private Integer pageSize = 100;
+
+    @Schema(description = "当前页码,大数据量分页查询导入数据")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @JsonProperty("pageIndex")
+    private Integer pageIndex = 1;
+
+    @Schema(description = "数据标头和字段映射,用 UrlEncode 进行编码", example = "%7B%20%22%E5%A7%93%E5%90%8D%22%3A%20%22realName%22%2C%20%22%E6%89%8B%E6%9C%BA%E5%8F%B7%22%3A%20%22phone%22%20%7D")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @JsonProperty("headMap")
+    private String headMap = "{}";
+
+}

+ 11 - 0
src/main/java/com/hz3r/domain/vo/MapRequest.java

@@ -0,0 +1,11 @@
+package com.hz3r.domain.vo;
+
+import java.util.LinkedHashMap;
+
+/**
+ * @author liu
+ * @since 1.0.0
+ */
+
+public class MapRequest extends LinkedHashMap<String, Object> {
+}

+ 10 - 0
src/main/java/com/hz3r/domain/vo/MapResponse.java

@@ -0,0 +1,10 @@
+package com.hz3r.domain.vo;
+
+import java.util.LinkedHashMap;
+
+/**
+ * @author Jason Wu
+ * @since 1.0.0
+ */
+public class MapResponse extends LinkedHashMap<String, Object> {
+}

+ 85 - 0
src/main/java/com/hz3r/domain/vo/PageVo.java

@@ -0,0 +1,85 @@
+package com.hz3r.domain.vo;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.hz3r.domain.page.Paginator;
+
+import java.util.List;
+
+public class PageVo<T> implements Paginator<T> {
+
+    public static <T> PageVo<T> of(IPage<T> page) {
+        if (page == null) {
+            return new PageVo<>();
+        }
+
+        PageVo<T> pageVo = new PageVo<>(page.getCurrent(), page.getSize(), page.getTotal());
+        pageVo.setPages(page.getPages());
+        pageVo.setCurrent(page.getCurrent());
+        pageVo.setSize(page.getSize());
+        pageVo.setTotal(page.getTotal());
+        pageVo.setRecords(page.getRecords());
+        return pageVo;
+    }
+
+    /** 内容列表 */
+    private List<T> records;
+    /** 当前页 */
+    private long current = 1;
+    /** 每页的数量 */
+    private long size = 10;
+    /** 总记录数 */
+    private long total = 0;
+
+    private long pages;
+
+    public PageVo() {}
+
+    public PageVo(long current, long size, long total) {
+        if (current > 1) {
+            this.current = current;
+        }
+        this.size = size;
+        this.total = total;
+    }
+
+    @Override
+    public List<T> getRecords() {
+        return records;
+    }
+
+    @Override
+    public void setRecords(List<T> records) {
+        this.records = records;
+    }
+
+    @Override
+    public long getCurrent() {
+        return current;
+    }
+
+    @Override
+    public void setCurrent(long current) {
+        this.current = current;
+    }
+
+    @Override
+    public long getSize() {
+        return size;
+    }
+
+    @Override
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    @Override
+    public long getTotal() {
+        return total;
+    }
+
+    @Override
+    public void setTotal(long total) {
+        this.total = total;
+    }
+
+}

+ 27 - 0
src/main/java/com/hz3r/domain/vo/TreeVo.java

@@ -0,0 +1,27 @@
+package com.hz3r.domain.vo;
+
+
+import com.hz3r.domain.tree.TreeNode;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class TreeVo {
+
+    public static <T extends TreeNode<T>> List<T> of(List<T> treeNodes) {
+//        return treeNodes
+//                .stream()
+//                .filter(treeNode -> treeNode.getParentId() == 0L)
+//                .peek(treeNode -> treeNode.setChildren(getChildren(treeNode.getId(), treeNodes)))
+//                .collect(Collectors.toList());
+        return getChildren(0L, treeNodes);
+    }
+
+    private static <T extends TreeNode<T>> List<T> getChildren(Long parentId, List<T> treeNodes) {
+        return treeNodes
+                .stream()
+                .filter(treeNode -> treeNode.getParentId() == parentId)
+                .peek(treeNode -> treeNode.setChildren(getChildren(treeNode.getId(), treeNodes)))
+                .collect(Collectors.toList());
+    }
+}

+ 51 - 0
src/main/java/com/hz3r/exception/ClientRuntimeException.java

@@ -0,0 +1,51 @@
+package com.hz3r.exception;
+
+import cn.hutool.core.text.StrFormatter;
+
+/**
+ * 返回给客户的运行时异常。
+ *
+ */
+public class ClientRuntimeException extends RuntimeException {
+
+    static public final String UNKNOWN = "Invalid";
+    static public final String AUTH_TOKEN = "InvalidToken";
+    static public final String PARAM = "InvalidParam";
+    static public final String ID = "InvalidId";
+    static public final String NAME = "InvalidName";
+    static public final String HTTP_REQUEST = "InvalidHttpRequest";
+    static public final String UNIQUE = "DuplicateKey";
+    static public final String FUNCTION = "InvalidFunction";
+
+
+    private String code;
+
+    public ClientRuntimeException(String code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    public ClientRuntimeException(String code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+    }
+
+    public ClientRuntimeException(String code, String msgPattern, Object ...megs) {
+        super(StrFormatter.format(msgPattern, megs));
+        this.code = code;
+    }
+
+    public ClientRuntimeException(String code, Throwable cause, String msgPattern, Object ...megs) {
+        super(StrFormatter.format(msgPattern, megs), cause);
+        this.code = code;
+    }
+
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+}

+ 24 - 0
src/main/java/com/hz3r/exception/ServerRuntimeException.java

@@ -0,0 +1,24 @@
+package com.hz3r.exception;
+
+public class ServerRuntimeException extends RuntimeException {
+
+    private String code;
+
+    public ServerRuntimeException(String code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    public ServerRuntimeException(String code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+}

+ 60 - 0
src/main/java/com/hz3r/infrastructure/park/BusinessWebapiApplication.java

@@ -0,0 +1,60 @@
+package com.hz3r.infrastructure.park;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScans;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.math.BigDecimal;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SpringBootApplication
+@EnableScheduling
+@ComponentScans({@ComponentScan(basePackages = {"com.hz3r"})})
+@MapperScan(basePackages = "com.hz3r.infrastructure.park.**.mapper")
+@Slf4j
+public class BusinessWebapiApplication {
+
+	@Bean
+	public CommandLineRunner commandLineRunner() {
+
+		return new CommandLineRunner() {
+			@Autowired
+			private PasswordEncoder passwordEncoder;
+			@Override
+			public void run(String... args) {
+			}
+		};
+	}
+
+	public static void main(String[] args) throws Exception{
+		ConfigurableApplicationContext application = SpringApplication.run(BusinessWebapiApplication.class, args);
+
+		log.info("\n--------------------------启动成功--------------------------------\n\t" );
+
+		Environment env = application.getEnvironment();
+		log.info("\n----------------------------------------------------------\n\t" +
+						"Application '{}' is running! Access URLs:\n\t" +
+						"Login: \thttp://{}:{}/manager\n\t" +
+						"Doc: \thttp://{}:{}/manager/doc.html\n" +
+						"----------------------------------------------------------",
+				env.getProperty("spring.application.name"),
+				InetAddress.getLocalHost().getHostAddress(),
+				env.getProperty("server.port"),
+				InetAddress.getLocalHost().getHostAddress(),
+				env.getProperty("server.port"));
+	}
+}

+ 23 - 0
src/main/java/com/hz3r/infrastructure/park/client/ArtemisGAClient.java

@@ -0,0 +1,23 @@
+package com.hz3r.infrastructure.park.client;
+
+
+import com.hz3r.domain.vo.PageVo;
+import com.hz3r.infrastructure.park.domain.enums.ga.GeometryType;
+import com.hz3r.infrastructure.park.domain.enums.ga.StreamingProtocol;
+import com.hz3r.infrastructure.park.domain.vo.ga.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface ArtemisGAClient {
+
+    List<ArtemisRegionVo> getRegionsByParentIndexCode(String parentIndexCode);
+
+    List<ArtemisCameraVo> getCamerasByRegionIndexCode(String regionIndexCode);
+
+    PageVo<ArtemisCameraPageVo> describeCamerasByPage(ArtemisCameraPageQueryVo pageQueryVo);
+
+    String getCameraPreviewURL(String cameraIndexCode, StreamingProtocol streamingProtocol);
+
+    String getCameraPlaybackURL(String cameraIndexCode, StreamingProtocol streamingProtocol, LocalDateTime beginTime, LocalDateTime endTime);
+}

+ 236 - 0
src/main/java/com/hz3r/infrastructure/park/client/ArtemisGAClientImpl.java

@@ -0,0 +1,236 @@
+package com.hz3r.infrastructure.park.client;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.hikvision.artemis.sdk.ArtemisHttpUtil;
+import com.hikvision.artemis.sdk.config.ArtemisConfig;
+import com.hikvision.artemis.sdk.constant.Constants;
+import com.hz3r.domain.vo.PageVo;
+import com.hz3r.exception.ServerRuntimeException;
+import com.hz3r.infrastructure.park.domain.enums.ga.GeometryType;
+import com.hz3r.infrastructure.park.domain.enums.ga.StreamingProtocol;
+import com.hz3r.infrastructure.park.domain.vo.ga.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+@Slf4j
+@Component
+public class ArtemisGAClientImpl implements ArtemisGAClient {
+    
+    private static final int PAGE_SIZE = 1000;
+    private static final String HTTP_SCHEMA = "https://";
+    private static final String CONTENT_TYPE = "application/json";
+    private static final String ARTEMIS_PATH = "/artemis";
+    public static final String GET_REGIONS_BY_PARENT_INDEX_CODE_API = ARTEMIS_PATH + "/api/resource/v2/regions/subRegions";
+    public static final String GET_CAMERAS_BY_REGION_INDEX_CODE_API = ARTEMIS_PATH + "/api/resource/v2/camera/search";
+    public static final String GET_CAMERAS_BY_PAGE_API = ARTEMIS_PATH + "/api/resource/v1/cameras";
+    public static final String GET_CAMERA_PREVIEW_URLS_API = ARTEMIS_PATH + "/api/video/v2/cameras/previewURLs";
+    public static final String GET_CAMERA_PLAYBACK_URLS_API = ARTEMIS_PATH + "/api/video/v2/cameras/playbackURLs";
+
+    static {
+        Constants.DEFAULT_TIMEOUT = 10000;
+        Constants.SOCKET_TIMEOUT = 60000;
+    }
+    
+    @Value("${artemis.ga.host}")
+    private String host;
+    @Value("${artemis.ga.app-key}")
+    private String appKey;
+    @Value("${artemis.ga.app-secret}")
+    private String appSecret;
+    
+    @Override
+    public List<ArtemisRegionVo> getRegionsByParentIndexCode(String parentIndexCode) {
+
+        List<ArtemisRegionVo> artemisRegionVos = new ArrayList<>();
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("parentIndexCode", parentIndexCode);
+        body.put("resourceType", "camera");
+        body.put("treeCode", "0");
+        body.put("pageNo", "1");
+        body.put("pageSize", "100");
+
+        JSONObject data = executeAndReturnData(GET_REGIONS_BY_PARENT_INDEX_CODE_API, body);
+
+        JSONArray list = data.getJSONArray("list");
+        if (!CollectionUtils.isEmpty(list)) {
+            list.toList(JSONObject.class).forEach(jsonObject -> {
+                ArtemisRegionVo artemisRegionVo = jsonObject.to(ArtemisRegionVo.class);
+                if (StringUtils.hasText(artemisRegionVo.getName())
+                        && StringUtils.hasText(artemisRegionVo.getIndexCode())) {
+                    artemisRegionVos.add(artemisRegionVo);
+                }
+            });
+        }
+
+        return artemisRegionVos;
+    }
+
+    @Override
+    public List<ArtemisCameraVo> getCamerasByRegionIndexCode(String regionIndexCode) {
+
+        List<ArtemisCameraVo> artemisCameraVos = new ArrayList<>();
+
+        int totalPageNo;
+        int pageNo = 1;
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("pageSize", PAGE_SIZE);
+        body.put("regionIndexCodes", Arrays.asList(regionIndexCode));
+
+        do {
+            body.put("pageNo", pageNo);
+
+            JSONObject data = executeAndReturnData(GET_CAMERAS_BY_REGION_INDEX_CODE_API, body);
+
+            totalPageNo = data.getIntValue("total") / PAGE_SIZE + 1;
+
+            JSONArray list = data.getJSONArray("list");
+            if (!CollectionUtils.isEmpty(list)) {
+                list.toList(JSONObject.class).forEach(jsonObject -> {
+                    ArtemisCameraVo artemisCameraVo = jsonObject.to(ArtemisCameraVo.class);
+                    if (StringUtils.hasText(artemisCameraVo.getName())
+                            && StringUtils.hasText(artemisCameraVo.getIndexCode())) {
+                        artemisCameraVo.setAltitude(0.0);
+                        artemisCameraVos.add(artemisCameraVo);
+                    }
+                });
+            }
+        } while (pageNo++ < totalPageNo);
+
+        return artemisCameraVos;
+    }
+
+    @Override
+    public PageVo<ArtemisCameraPageVo> describeCamerasByPage(ArtemisCameraPageQueryVo pageQueryVo) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("pageNo", pageQueryVo.getCurrent());
+        body.put("pageSize", pageQueryVo.getSize());
+        Optional.ofNullable(pageQueryVo.getRegionIndexCode()).ifPresent(arg->body.put("regionIndexCodes", arg));
+
+        JSONObject data = executeAndReturnData(GET_CAMERAS_BY_PAGE_API, body);
+
+        PageVo<ArtemisCameraPageVo> pageVo = new PageVo<>();
+        pageVo.setTotal(data.getInteger("total"));
+        pageVo.setCurrent(data.getInteger("pageNo"));
+        pageVo.setSize(data.getInteger("pageSize"));
+        pageVo.setPages(data.getIntValue("total") / PAGE_SIZE + 1);
+
+        List<ArtemisCameraPageVo> artemisCameraVos = new ArrayList<>();
+
+        JSONArray list = data.getJSONArray("list");
+        if (!CollectionUtils.isEmpty(list)) {
+            list.toList(JSONObject.class).forEach(jsonObject -> {
+                ArtemisCameraPageVo artemisCameraVo = jsonObject.to(ArtemisCameraPageVo.class);
+                if (StringUtils.hasText(artemisCameraVo.getCameraName())
+                        && StringUtils.hasText(artemisCameraVo.getCameraIndexCode())) {
+                    artemisCameraVo.setAltitude(0.0);
+                    artemisCameraVos.add(artemisCameraVo);
+                }
+            });
+            pageVo.setRecords(artemisCameraVos);
+        }
+
+        return pageVo;
+    }
+
+    @Override
+    public String getCameraPreviewURL(String cameraIndexCode, StreamingProtocol streamingProtocol) {
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("cameraIndexCode", cameraIndexCode);
+        body.put("streamType", 0);
+        body.put("protocol", streamingProtocol.getProtocol());
+//        body.put("protocol", "hik");
+        body.put("transmode", 1);
+        body.put("expend", "transcode=0");
+
+        JSONObject data = executeAndReturnData(GET_CAMERA_PREVIEW_URLS_API, body);
+
+        return data.getString("url");
+    }
+
+    @Override
+    public String getCameraPlaybackURL(String cameraIndexCode, StreamingProtocol streamingProtocol, LocalDateTime beginTime, LocalDateTime endTime) {
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("cameraIndexCode", cameraIndexCode);
+        body.put("beginTime", format(beginTime));
+        body.put("endTime", format(endTime));
+        body.put("protocol", streamingProtocol.getProtocol());
+        body.put("needReturnClipInfo", false);
+
+        JSONObject data = executeAndReturnData(GET_CAMERA_PLAYBACK_URLS_API, body);
+
+        return data.getString("url");
+    }
+
+    private JSONObject executeAndReturnRoot(String apiUrl, Map<String, Object> body) {
+        
+        try {
+            ArtemisConfig config = new ArtemisConfig();
+            config.setHost(host);
+            config.setAppKey(appKey);
+            config.setAppSecret(appSecret);
+
+            Map<String, String> path = new HashMap<>(1);
+            path.put(HTTP_SCHEMA, apiUrl);
+
+            String resultText = ArtemisHttpUtil.doPostStringArtemis(config, path,
+                    JSON.toJSONString(body), null, null, CONTENT_TYPE);
+
+            JSONObject root = JSON.parseObject(resultText);
+
+            log.info("****************************");
+            log.info("****************************");
+            log.info("****************************");
+            log.info(apiUrl);
+            log.info("****************************");
+            log.info("****************************");
+            log.info(JSON.toJSONString(body));
+            log.info("****************************");
+            log.info("****************************");
+            log.info(resultText);
+            log.info("****************************");
+            log.info("****************************");
+            log.info("****************************");
+
+            if (ObjectUtils.isEmpty(root)
+                    || !"0".equals(root.getString("code"))) {
+                throw new ServerRuntimeException("ArtemisError", "海康接口执行失败");
+            }
+
+            return root;
+        } catch (Exception e) {
+            throw new ServerRuntimeException("ArtemisError", "请求海康接口失败", e);
+        }
+    }
+    
+    private JSONObject executeAndReturnData(String apiUrl, Map<String, Object> body) {
+
+        JSONObject root = executeAndReturnRoot(apiUrl, body);
+        
+        if (ObjectUtils.isEmpty(root.getJSONObject("data"))) {
+            throw new ServerRuntimeException("ArtemisError", "海康接口返回不包含data数据");
+        }
+
+        return root.getJSONObject("data");
+    }
+    
+    private String format(LocalDateTime localDateTime) {
+        return OffsetDateTime.of(localDateTime, ZoneOffset.ofHours(8))
+                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
+    }
+}

+ 11 - 0
src/main/java/com/hz3r/infrastructure/park/client/HikvisionClient.java

@@ -0,0 +1,11 @@
+//package com.hz3r.infrastructure.park.client;
+//
+//import com.alibaba.fastjson2.JSONArray;
+//import com.alibaba.fastjson2.JSONObject;
+//import com.hz3r.infrastructure.park.domain.vo.client.HikvisionCardFaceRecordPusherVo;
+//
+//public interface HikvisionClient {
+//
+//    JSONObject deployToMachine(HikvisionCardFaceRecordPusherVo request);
+//
+//}

+ 85 - 0
src/main/java/com/hz3r/infrastructure/park/client/HikvisionClientImpl.java

@@ -0,0 +1,85 @@
+//package com.hz3r.infrastructure.park.client;
+//
+//import com.alibaba.fastjson2.JSON;
+//import com.alibaba.fastjson2.JSONArray;
+//import com.alibaba.fastjson2.JSONObject;
+//import com.hz3r.exception.ClientRuntimeException;
+//import com.hz3r.infrastructure.park.domain.vo.client.HikvisionCardFaceRecordPusherVo;
+//import com.hz3r.utils.RestTemplateUtil;
+//import jakarta.annotation.Resource;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.beans.factory.annotation.Value;
+//import org.springframework.http.HttpMethod;
+//import org.springframework.stereotype.Component;
+//import org.springframework.util.ObjectUtils;
+//
+//import java.time.format.DateTimeFormatter;
+//import java.util.HashMap;
+//import java.util.Map;
+//
+//@Slf4j
+//@Component
+//public class HikvisionClientImpl implements HikvisionClient {
+//
+//    private static final String AI_PATH = "";
+//    public static final String ADD_DEVICE_API = AI_PATH + "/ba1hkcfrCardFaceRecord/DeployToMachine";
+//
+//    @Value("${ai.host}")
+//    private String host;
+//
+//
+//    @Resource
+//    private RestTemplateUtil restTemplateUtil;
+//
+//    @Override
+//    public JSONObject deployToMachine(HikvisionCardFaceRecordPusherVo request) {
+//        JSONObject jsonObject = executeAndReturnData(ADD_DEVICE_API, request, null, HttpMethod.POST);
+//        return jsonObject;
+//    }
+//
+//    private JSONObject executeAndReturnRoot(String apiUrl, Object body, Map<String, Object> params, HttpMethod method) {
+//
+//        try {
+//
+//            JSONObject root = restTemplateUtil.exchange(host+apiUrl, method, null, body, params, JSONObject.class);
+//
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info(apiUrl);
+//            log.info("****************************");
+//            log.info("************body************");
+//            log.info(JSON.toJSONString(body));
+//            log.info("****************************");
+//            log.info("************param************");
+//            log.info(JSON.toJSONString(params));
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info(root.toJSONString());
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info("****************************");
+//
+//            if (ObjectUtils.isEmpty(root)
+//                    || !"0".equals(root.getString("code"))) {
+//                throw new ClientRuntimeException("ArtemisError", "AI接口执行失败");
+//            }
+//
+//            return root;
+//        } catch (Exception e) {
+//            throw new ClientRuntimeException("ArtemisError", "请求AI接口失败", e);
+//        }
+//    }
+//
+//    private JSONObject executeAndReturnData(String apiUrl, Object body, Map<String, Object> params, HttpMethod method) {
+//
+//        JSONObject root = executeAndReturnRoot(apiUrl, body, params, method);
+//
+//        if (ObjectUtils.isEmpty(root.getJSONObject("data"))) {
+//            throw new ClientRuntimeException("ArtemisError", "AI接口返回不包含data数据");
+//        }
+//
+//        return root.getJSONObject("data");
+//    }
+//
+//}

+ 26 - 0
src/main/java/com/hz3r/infrastructure/park/client/OaClient.java

@@ -0,0 +1,26 @@
+package com.hz3r.infrastructure.park.client;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.hz3r.infrastructure.park.domain.vo.oa.CompanyAndDepartmentVo;
+import com.hz3r.infrastructure.park.domain.vo.oa.OaEmployeeVo;
+
+import java.util.List;
+
+public interface OaClient {
+//
+//    String getAccessToken();
+//
+//    String setAccessToken();
+//
+//    String oauth2AuthorizeCode();
+//
+//    JSONObject oauth2AccessToken();
+//
+//    JSONObject workflowCorePaServiceDoCreate(VisitorReservationRequest request);
+//
+//    List<CompanyAndDepartmentVo> hrmQuery(HrmQueryRequest request);
+//    List<CompanyAndDepartmentVo> hrmQuery1(HrmQueryRequest request);
+//
+//    List<OaEmployeeVo> employeeQuery(EmployeeQueryRequest request);
+
+}

+ 522 - 0
src/main/java/com/hz3r/infrastructure/park/client/OaClientImpl.java

@@ -0,0 +1,522 @@
+package com.hz3r.infrastructure.park.client;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class OaClientImpl implements OaClient {
+//
+//    private static final String OA_PATH = "/papi/openapi";
+//    public static final String OAUTH2_AUTHORIZE_CODE_API = OA_PATH + "/oauth2/authorize";
+//    public static final String OAUTH2_ACCESS_TOKEN_API = OA_PATH + "/oauth2/access_token";
+//    public static final String WORKFLOW_CORE_PA_SERVICE_DO_CREATE_REQUEST_API = OA_PATH + "/api/workflow/core/paService/v1/doCreateRequest";
+//    public static final String QUERY_HRM_API = OA_PATH + "/api/hrm/restful/queryOrg";
+//    public static final String QUERY_EMPLOYEE_API = OA_PATH + "/api/hrm/restful/queryEmployee";
+//
+//    @Value("${oa.host}")
+//    private String host;
+//    @Value("${oa.app_key}")
+//    private String appKey;
+//    @Value("${oa.app_secret}")
+//    private String appSecret;
+//
+//    // 工作流配置常量
+//    private static final String WORKFLOW_ID = "1219290225737072720";
+//    private static final String FORM_ID = "7833764773709572808";
+//    private static final String FORM_DATA_ID = "1219290225737072642";
+//    private static final String FORM_USER_ID = "1209986175391637546";
+//
+//    private static final Integer PAGE_SIZE = 100;
+//
+//    // 字段ID映射
+//    private static final Map<String, String> FIELD_MAPPING = new HashMap<>();
+//
+//    static {
+//        // 基本信息字段
+//        FIELD_MAPPING.put("reservationNo", "1221039694631534592");
+//        FIELD_MAPPING.put("visitorName", "1221039694631534593");
+//        FIELD_MAPPING.put("visitorPhone", "1221039694631534594");
+//        FIELD_MAPPING.put("visitorIdType", "1221039694631534595");
+//        FIELD_MAPPING.put("visitorIdNumber", "1221039694631534596");
+//        FIELD_MAPPING.put("visitorCompany", "1221039827683246082");
+//        FIELD_MAPPING.put("visitReason", "1221039827683246083");
+//        FIELD_MAPPING.put("plannedVisitTime", "1221039827683246084");
+//        FIELD_MAPPING.put("plannedLeaveTime", "1221039827683246085");
+//        FIELD_MAPPING.put("intervieweeName", "1221039827683246086");
+//        FIELD_MAPPING.put("intervieweePhone", "1221039930972176390");
+//        FIELD_MAPPING.put("intervieweeDepartment", "1221039930972176391");
+//        FIELD_MAPPING.put("visitLocation", "1221039930972176392");
+//        FIELD_MAPPING.put("visitorCount", "1221039930972176393");
+//
+//        // 车辆信息字段
+//        FIELD_MAPPING.put("licensePlateNumber", "1221040197159485445");
+//        FIELD_MAPPING.put("vehicleType", "1221040197159485446");
+//        FIELD_MAPPING.put("vehicleBrand", "1221040197159485447");
+//        FIELD_MAPPING.put("vehicleColor", "1221040197159485448");
+//        FIELD_MAPPING.put("driverName", "1221040252994060290");
+//        FIELD_MAPPING.put("driverPhone", "1221040252994060291");
+//    }
+//
+//    @Resource
+//    private RestTemplateUtil restTemplateUtil;
+//    @Autowired
+//    protected RedisUtil redisUtil;
+//
+//    public final static String accessTokenKey = "access_token";
+//    @Override
+//    public String setAccessToken() {
+//        JSONObject jsonObject = oauth2AccessToken();
+//        redisUtil.set(accessTokenKey, jsonObject.getString("accessToken"), jsonObject.getLong("expires_in"), TimeUnit.MILLISECONDS);
+//
+//        return jsonObject.getString("accessToken");
+//    }
+//
+//    @Override
+//    public String getAccessToken() {
+////        String access_token = (String) redisUtil.get(accessTokenKey);
+////        if(!StringUtils.hasText(access_token)){
+////            access_token = setAccessToken();
+////        }
+//        JSONObject jsonObject = oauth2AccessToken();
+//        return jsonObject.getString("accessToken");
+////        return access_token;
+//    }
+//
+//    @Override
+//    public String oauth2AuthorizeCode() {
+//        Map<String, Object> params = new HashMap<>();
+//
+//        params.put("corpid", "2bcaddf107fb6254d6cc6f29d0770883");
+//        params.put("response_type", "code");
+//        params.put("state", "");
+//        JSONObject jsonObject = executeAndReturnRoot(OAUTH2_AUTHORIZE_CODE_API, null, params, HttpMethod.GET, null);
+//
+//        return jsonObject.getString("code");
+//    }
+//
+//    @Override
+//    public JSONObject oauth2AccessToken() {
+//        Map<String, Object> params = new HashMap<>();
+//
+//        params.put("app_key", appKey);
+//        params.put("app_secret",appSecret);
+//        params.put("grant_type", "authorization_code");
+//        params.put("code", oauth2AuthorizeCode());
+//        JSONObject jsonObject = executeAndReturnRoot(OAUTH2_ACCESS_TOKEN_API, null, params, HttpMethod.POST, null);
+//
+//        return jsonObject;
+//    }
+//
+//    @Override
+//    public JSONObject workflowCorePaServiceDoCreate(VisitorReservationRequest request) {
+//        HttpHeaders headers = new HttpHeaders();
+//        headers.setContentType(MediaType.APPLICATION_JSON);
+//        Map<String, Object> params = buildWorkflowParams(request);
+//        JSONObject jsonObject = executeAndReturnRootWithMessage(WORKFLOW_CORE_PA_SERVICE_DO_CREATE_REQUEST_API, params, null, HttpMethod.POST, headers);
+//
+//        return jsonObject;
+//    }
+//
+//    @Override
+//    public List<CompanyAndDepartmentVo> hrmQuery(HrmQueryRequest request) {
+//
+//        HttpHeaders headers = new HttpHeaders();
+//        headers.setContentType(MediaType.APPLICATION_JSON);
+//
+//        Map<String, Object> queryParams = new HashMap<>();
+//
+//        // 基础参数
+//        queryParams.put("access_token", getAccessToken());
+//
+//        List<CompanyAndDepartmentVo> companyAndDepartmentVos = new ArrayList<>();
+//
+//        int totalPageNo;
+//        int current = 1;
+//
+//        Map<String, Object> body = new HashMap<>();
+//        body.put("pageSize", PAGE_SIZE);
+//        body.put("typeList", Arrays.asList(request.getType().getCode()));
+//
+//        Optional.ofNullable(request.getSubcompany()).ifPresent(arg->body.put("subcompanyList", Arrays.asList(arg)));
+//
+//        do {
+//            body.put("current", current);
+//
+//            JSONObject result = executeAndReturnRootWithMessage(QUERY_HRM_API, body, queryParams, HttpMethod.POST, headers);
+//            JSONObject data = result.getJSONObject("data");
+//            totalPageNo = data.getIntValue("total") / PAGE_SIZE + 1;
+//
+//            JSONArray list = data.getJSONArray("data");
+//            if (!CollectionUtils.isEmpty(list)) {
+//                list.toList(JSONObject.class).forEach(jsonObject -> {
+//                    CompanyAndDepartmentVo companyAndDepartmentVo = jsonObject.to(CompanyAndDepartmentVo.class);
+//                    companyAndDepartmentVos.add(companyAndDepartmentVo);
+//                });
+//            }
+//        } while (current++ < totalPageNo);
+//
+//        return companyAndDepartmentVos;
+//    }
+//
+//    @Override
+//    public List<CompanyAndDepartmentVo> hrmQuery1(HrmQueryRequest request) {
+//        HttpHeaders headers = new HttpHeaders();
+//        headers.setContentType(MediaType.APPLICATION_JSON);
+//
+//        Map<String, Object> queryParams = new HashMap<>();
+//
+//        // 基础参数
+//        queryParams.put("access_token", getAccessToken());
+//
+//        List<CompanyAndDepartmentVo> companyAndDepartmentVos = new ArrayList<>();
+//
+//        int totalPageNo;
+//        int current = 1;
+//
+//        Map<String, Object> body = new HashMap<>();
+//        body.put("pageSize", PAGE_SIZE);
+//        body.put("typeList", Arrays.asList(SyncTypeEnum.DEPARTMENT.getCode(), SyncTypeEnum.SUBCOMPANY.getCode()));
+//
+//        do {
+//            body.put("current", current);
+//
+//            JSONObject result = executeAndReturnRootWithMessage(QUERY_HRM_API, body, queryParams, HttpMethod.POST, headers);
+//            JSONObject data = result.getJSONObject("data");
+//            totalPageNo = data.getIntValue("total") / PAGE_SIZE + 1;
+//
+//            JSONArray list = data.getJSONArray("data");
+//            if (!CollectionUtils.isEmpty(list)) {
+//                list.toList(JSONObject.class).forEach(jsonObject -> {
+//                    CompanyAndDepartmentVo companyAndDepartmentVo = jsonObject.to(CompanyAndDepartmentVo.class);
+//                    companyAndDepartmentVos.add(companyAndDepartmentVo);
+//                });
+//            }
+//        } while (current++ < totalPageNo);
+//
+//        return companyAndDepartmentVos;
+//    }
+//
+//    @Override
+//    public List<OaEmployeeVo> employeeQuery(EmployeeQueryRequest request) {
+//        HttpHeaders headers = new HttpHeaders();
+//        headers.setContentType(MediaType.APPLICATION_JSON);
+//
+//        Map<String, Object> queryParams = new HashMap<>();
+//
+//        // 基础参数
+//        queryParams.put("access_token", getAccessToken());
+//
+//        List<OaEmployeeVo> oaEmployeeVos = new ArrayList<>();
+//
+//        int totalPageNo;
+//        int current = 1;
+//
+//        Map<String, Object> body = new HashMap<>();
+//        body.put("pageSize", PAGE_SIZE);
+//        body.put("subcompanyIds", Arrays.asList(request.getSubcompanyId()));
+//
+//        do {
+//            body.put("current", current);
+//
+//            JSONObject result = executeAndReturnRootWithMessage(QUERY_EMPLOYEE_API, body, queryParams, HttpMethod.POST, headers);
+//            JSONObject data = result.getJSONObject("data");
+//            totalPageNo = data.getIntValue("total") / PAGE_SIZE + 1;
+//
+//            JSONArray list = data.getJSONArray("data");
+//            if (!CollectionUtils.isEmpty(list)) {
+//                list.toList(JSONObject.class).forEach(jsonObject -> {
+//                    OaEmployeeVo oaEmployeeVo = jsonObject.to(OaEmployeeVo.class);
+//                    if (StringUtils.hasText(oaEmployeeVo.getJob_num())) {
+//                        oaEmployeeVos.add(oaEmployeeVo);
+//                    }
+//                });
+//            }
+//        } while (current++ < totalPageNo);
+//
+//        return oaEmployeeVos;
+//    }
+//
+//    private JSONObject executeAndReturnRoot(String apiUrl, Object body, Map<String, Object> params, HttpMethod method, HttpHeaders headers) {
+//
+//        try {
+//
+//            JSONObject root = restTemplateUtil.exchange(host+apiUrl, method, headers, body, params, JSONObject.class);
+//
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info(apiUrl);
+//            log.info("****************************");
+//            log.info("************body************");
+//            log.info(JSON.toJSONString(body));
+//            log.info("****************************");
+//            log.info("************param************");
+//            log.info(JSON.toJSONString(params));
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info(root.toJSONString());
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info("****************************");
+//
+//            if (ObjectUtils.isEmpty(root)
+//                    || !"0".equals(root.getString("errcode"))) {
+//                throw new ServerRuntimeException("OAError", "OA接口执行失败");
+//            }
+//
+//            return root;
+//        } catch (Exception e) {
+//            throw new ServerRuntimeException("OAError", "请求OA接口失败", e);
+//        }
+//    }
+//
+//    private JSONObject executeAndReturnRootWithMessage(String apiUrl, Object body, Map<String, Object> params, HttpMethod method, HttpHeaders headers) {
+//
+//        try {
+//
+//            JSONObject root = restTemplateUtil.exchange(host+apiUrl, method, headers, body, params, JSONObject.class);
+//
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info(apiUrl);
+//            log.info("****************************");
+//            log.info("************body************");
+//            log.info(JSON.toJSONString(body));
+//            log.info("****************************");
+//            log.info("************param************");
+//            log.info(JSON.toJSONString(params));
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info(root.toJSONString());
+//            log.info("****************************");
+//            log.info("****************************");
+//            log.info("****************************");
+//
+//            if (ObjectUtils.isEmpty(root)
+//                    || !"0".equals(root.getJSONObject("message").getString("errcode"))) {
+//                throw new ServerRuntimeException("OAError", "OA接口执行失败");
+//            }
+//
+//            return root;
+//        } catch (Exception e) {
+//            throw new ServerRuntimeException("OAError", "请求OA接口失败", e);
+//        }
+//    }
+//
+//    /**
+//     * 根据访客预约请求构建工作流提交参数
+//     */
+//    public Map<String, Object> buildWorkflowParams(VisitorReservationRequest request) {
+//        try {
+//            Map<String, Object> params = new HashMap<>();
+//
+//            // 基础参数
+//            params.put("access_token", getAccessToken());
+//            params.put("userid", FORM_USER_ID);
+//            params.put("requestname", generateRequestName(request));
+//            params.put("workflowId", WORKFLOW_ID);
+//            params.put("isnextflow", "1");
+//            params.put("isVerifyFormRequired", "true");
+//            params.put("isExecuteCreateAddAction", "true");
+//            params.put("isVerifyPer", "1");
+//            params.put("formId", FORM_ID);
+//            params.put("module", "workflow");
+//
+//            // 表单数据
+//            params.put("formData", buildFormData(request));
+//
+//            // 其他参数
+//            Map<String, Object> otherParams = new HashMap<>();
+//            otherParams.put("isnextflow", 1);
+//            params.put("otherParams", otherParams);
+//
+//            log.debug("构建工作流参数成功:{}", params);
+//            return params;
+//
+//        } catch (Exception e) {
+//            log.error("构建工作流参数失败", e);
+//            throw new RuntimeException("构建工作流参数失败", e);
+//        }
+//    }
+//
+//    /**
+//     * 构建表单数据
+//     */
+//    private Map<String, Object> buildFormData(VisitorReservationRequest request) {
+//        Map<String, Object> formData = new HashMap<>();
+//        formData.put("formId", FORM_DATA_ID);
+//        formData.put("module", "workflow");
+//
+//        List<Map<String, Object>> dataDetails = new ArrayList<>();
+//
+//        // 添加基本信息字段(dataIndex为0或不设置)
+//        addBasicInfoFields(dataDetails, request);
+//
+//        // 处理多条车辆信息
+//        addVehicleInfoFields(dataDetails, request);
+//
+//        formData.put("dataDetails", dataDetails.toArray());
+//        return formData;
+//    }
+//
+//    /**
+//     * 添加基本信息字段
+//     */
+//    private void addBasicInfoFields(List<Map<String, Object>> dataDetails, VisitorReservationRequest request) {
+//        addFieldData(dataDetails, FIELD_MAPPING.get("reservationNo"),
+//                request.getReservationNo(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitorName"),
+//                request.getVisitorName(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitorPhone"),
+//                request.getVisitorPhone(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitorIdType"),
+//                request.getVisitorIdType(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitorIdNumber"),
+//                request.getVisitorIdNumber(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitorCompany"),
+//                request.getVisitorCompany(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitReason"),
+//                request.getVisitReason(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("plannedVisitTime"),
+//                formatDateTime(request.getPlannedVisitTime()), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("plannedLeaveTime"),
+//                formatDateTime(request.getPlannedLeaveTime()), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("intervieweeName"),
+//                request.getIntervieweeName(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("intervieweePhone"),
+//                request.getIntervieweePhone(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("intervieweeDepartment"),
+//                request.getIntervieweeDepartment(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitLocation"),
+//                request.getVisitLocation(), null);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("visitorCount"),
+//                String.valueOf(request.getVisitorCount() != null ? request.getVisitorCount() : 1), null);
+//    }
+//
+//    /**
+//     * 添加多条车辆信息字段
+//     */
+//    private void addVehicleInfoFields(List<Map<String, Object>> dataDetails, VisitorReservationRequest request) {
+//        if (request.getVehicles() != null && !request.getVehicles().isEmpty()) {
+//            for (int i = 0; i < request.getVehicles().size(); i++) {
+//                VehicleRegistrationRequest vehicle = request.getVehicles().get(i);
+//                int dataIndex = i + 1; // dataIndex从1开始,与车辆序号对应
+//
+//                // 添加当前车辆的各个字段
+//                addVehicleField(dataDetails, vehicle, request, dataIndex, i);
+//            }
+//        } else {
+//            // 如果没有车辆信息,至少添加一组空值
+//            addVehicleField(dataDetails, null, request, 1, 0);
+//        }
+//    }
+//
+//    /**
+//     * 添加单个车辆的所有字段
+//     */
+//    private void addVehicleField(List<Map<String, Object>> dataDetails,
+//                                 VehicleRegistrationRequest vehicle,
+//                                 VisitorReservationRequest request,
+//                                 int dataIndex,
+//                                 int vehicleIndex) {
+//        // 车牌号码
+//        addFieldData(dataDetails, FIELD_MAPPING.get("licensePlateNumber"),
+//                vehicle != null ? vehicle.getPlateNumber() : "",
+//                dataIndex);
+//
+//        // 车辆类型
+//        addFieldData(dataDetails, FIELD_MAPPING.get("vehicleType"),
+//                vehicle != null ? vehicle.getVehicleType().getDescription() : "",
+//                dataIndex);
+//
+//        // 车辆品牌
+//        addFieldData(dataDetails, FIELD_MAPPING.get("vehicleBrand"),
+//                vehicle != null ? vehicle.getVehicleBrand() : "",
+//                dataIndex);
+//
+//        // 车辆颜色
+//        addFieldData(dataDetails, FIELD_MAPPING.get("vehicleColor"),
+//                vehicle != null ? vehicle.getVehicleColor() : "",
+//                dataIndex);
+//
+//        // 驾驶员姓名(如果有多个驾驶员信息,可以按需调整)
+//        String driverName = getDriverName(request, vehicle, vehicleIndex);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("driverName"),
+//                driverName, dataIndex);
+//
+//        // 驾驶员电话(如果有多个驾驶员信息,可以按需调整)
+//        String driverPhone = getDriverPhone(request, vehicle, vehicleIndex);
+//        addFieldData(dataDetails, FIELD_MAPPING.get("driverPhone"),
+//                driverPhone, dataIndex);
+//    }
+//
+//    /**
+//     * 获取驾驶员姓名
+//     * 可以根据实际情况调整逻辑
+//     */
+//    private String getDriverName(VisitorReservationRequest request,
+//                                 VehicleRegistrationRequest vehicle,
+//                                 int vehicleIndex) {
+//        if (vehicle != null && vehicle.getDriverName() != null) {
+//            return vehicle.getDriverName();
+//        }
+//        // 默认使用访客姓名
+//        return request.getVisitorName();
+//    }
+//
+//    /**
+//     * 获取驾驶员电话
+//     * 可以根据实际情况调整逻辑
+//     */
+//    private String getDriverPhone(VisitorReservationRequest request,
+//                                  VehicleRegistrationRequest vehicle,
+//                                  int vehicleIndex) {
+//        if (vehicle != null && vehicle.getDriverPhone() != null) {
+//            return vehicle.getDriverPhone();
+//        }
+//        // 默认使用访客电话
+//        return request.getVisitorPhone();
+//    }
+//
+//    /**
+//     * 添加字段数据
+//     */
+//    private void addFieldData(List<Map<String, Object>> dataDetails, String fieldId,
+//                              String content, Integer dataIndex) {
+//        Map<String, Object> field = new HashMap<>();
+//        field.put("content", content != null ? content : "");
+//        field.put("fieldId", fieldId);
+//
+//        if (dataIndex != null) {
+//            field.put("dataIndex", dataIndex);
+//        }
+//
+//        dataDetails.add(field);
+//    }
+//
+//    /**
+//     * 生成请求名称
+//     */
+//    private String generateRequestName(VisitorReservationRequest request) {
+//        String applicant = Optional.ofNullable(request.getVisitorName()).orElse("未知");
+//        String interviewee = Optional.ofNullable(request.getIntervieweeName()).orElse("未知");
+//        String date = Optional.ofNullable(request.getPlannedVisitTime())
+//                .map(time -> time.toLocalDate().toString())
+//                .orElse(LocalDateTime.now().toLocalDate().toString());
+//
+//        return String.format("园区访客申请-%s-%s-被访对象:%s", applicant, date, interviewee);
+//    }
+//
+//    /**
+//     * 格式化日期时间
+//     */
+//    private String formatDateTime(LocalDateTime dateTime) {
+//        if (dateTime == null) {
+//            return "";
+//        }
+//        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
+//    }
+
+}

+ 160 - 0
src/main/java/com/hz3r/infrastructure/park/config/DataOps.java

@@ -0,0 +1,160 @@
+package com.hz3r.infrastructure.park.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 数据操作配置
+ *
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "data")
+public class DataOps {
+
+    /**
+     * DACL 数据访问控制功能开关。
+     */
+    public boolean dacl = true;
+
+    /**
+     * JWT 安全密钥
+     */
+    private String jwtKey;
+    /**
+     * 本地 OSS 文件目录。
+     */
+    private String localOssDirectory;
+    /**
+     *  OSS 存储桶名称
+     */
+    private String ossBucketName;
+    /**
+     *  OSS 服务器。
+     */
+    private String ossServer;
+    /**
+     *  OSS 访问 KEY。
+     */
+    private String ossAccessKey;
+    /**
+     *  OSS 访问 密钥。
+     */
+    private String ossAccessSecret;
+    /**
+     *  OSS 公网访问入口。
+     */
+    private String ossPublicEntry;
+    /**
+     *  OSS 公网访问路径。
+     */
+    private String ossPublicPath;
+
+    private SmsCloud smsCloud;
+
+
+    /**
+     * 级联操作
+     */
+    @Data
+    @Configuration
+    @ConfigurationProperties(prefix = "data.casc")
+    static public class Casc {
+
+        /**
+         * 级联删除的数据代码列表
+         */
+        private List<String> dels = new ArrayList<>(10);
+
+        /**
+         * 级联查询上游的数据代码
+         */
+        private List<String> queryUps = new ArrayList<>(10);
+
+        /**
+         * 级联查询下游的数据代码
+         */
+        private List<String> queryDowns = new ArrayList<>(10);
+
+        /**
+         * 忽略的代码
+         */
+        private List<String> ignore = new ArrayList<>(0);
+
+        /**
+         * 级联查询深度
+         */
+        private int queryDepth = 1;
+
+    }
+
+    /**
+     * 数据管理代码
+     */
+    @Data
+    @Configuration
+    @ConfigurationProperties(prefix = "data.admin.district")
+    static public class AdminDistrict {
+
+        /**
+         * 行政区划属性
+         */
+        private List<String> columns = new ArrayList<>(0);
+
+    }
+
+    /**
+     * 数据访问类型
+     */
+    @Data
+    @Configuration
+    @ConfigurationProperties(prefix = "data.access.type")
+    static public class AccessType {
+
+        /**
+         * 行政区划属性
+         */
+        private List<String> columns = new ArrayList<>(0);
+
+    }
+
+    /**
+     * 云短信
+     */
+    @Data
+    @Configuration
+    @ConfigurationProperties(prefix = "data.sms-cloud")
+    static public class SmsCloud {
+
+        private String server;
+        private String regionId;
+
+        private String accessKey;
+
+        private String accessSecret;
+
+        private List<SmsTemplate> templates;
+
+
+        /**
+         * 云短信
+         */
+        @Data
+        static public class SmsTemplate {
+
+            private String name;
+
+            private String signName;
+
+            private String code;
+
+        }
+
+    }
+
+
+}

+ 32 - 0
src/main/java/com/hz3r/infrastructure/park/config/LoginLimitConfig.java

@@ -0,0 +1,32 @@
+package com.hz3r.infrastructure.park.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 登录失败次数限制配置
+ *
+ * 配置项前缀: data.login-limit
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "data.login-limit")
+public class LoginLimitConfig {
+
+    /**
+     * 登录限制功能开关,默认开启
+     */
+    private boolean enabled = true;
+
+    /**
+     * 最大失败次数,超过后锁定账户
+     */
+    private int maxFailCount = 5;
+
+    /**
+     * 锁定时间(分钟)
+     */
+    private int lockMinutes = 15;
+
+}

+ 11 - 0
src/main/java/com/hz3r/infrastructure/park/config/MybatisConfiguration.java

@@ -0,0 +1,11 @@
+package com.hz3r.infrastructure.park.config;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@MapperScan("com.hz3r.infrastructure.park.mapper")
+public class MybatisConfiguration {
+
+
+}

+ 76 - 0
src/main/java/com/hz3r/infrastructure/park/config/WebSecurityConfiguration.java

@@ -0,0 +1,76 @@
+package com.hz3r.infrastructure.park.config;
+
+import com.hz3r.infrastructure.park.security.TokenAuthenticationFilter;
+import com.hz3r.infrastructure.park.security.UserDetailsServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+public class WebSecurityConfiguration {
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+        httpSecurity
+                .addFilterBefore(new TokenAuthenticationFilter(userDetailsService()), UsernamePasswordAuthenticationFilter.class)
+                .sessionManagement(AbstractHttpConfigurer::disable)
+//                .securityContext(AbstractHttpConfigurer::disable)
+//                .headers(AbstractHttpConfigurer::disable)
+                .cors(Customizer.withDefaults())
+                .csrf(AbstractHttpConfigurer::disable)
+                .logout(AbstractHttpConfigurer::disable)
+                .anonymous(AbstractHttpConfigurer::disable)
+//                .exceptionHandling(AbstractHttpConfigurer::disable)
+                .authorizeHttpRequests(requestMatcherRegistry -> {
+                    requestMatcherRegistry
+                            .requestMatchers(
+                                    "/api/auth/login", "/api/user/logout"
+                                    , "/Sms/Login/**", "/Sms/ResetPass/**", "/api/app/captcha/**"
+                                    , "/v3/api-docs/**","/swagger-ui/**","/swagger-ui.html" // Open Api 文档。
+                                    , "/public/**/event" // 开放事件传入。
+                                    , "/ba1oOrderNotify/Order/Pay/Notify" // 支付回调
+                                    , "/ba1oOrderNotify/Refund/Pay/Notify" // 退款回调
+                                    , "/ba1oOrderNotify/Refund/Transfer/Notify" // 支转账回调
+                                    , "/api/front/**" // 微信小程序
+                                    , "/api/app/**" // 支付宝小程序
+                                    , "/api/openapi/**" // oa系统推送结果
+                                    , "/ba1alipayoOrderAd/**" // 支付宝支付
+                                    , "/fileContentExchange/**" // 文件内容转换
+                            )
+                            .permitAll()
+                            .requestMatchers("/api/**").authenticated()
+                            .anyRequest()
+                            .permitAll();
+                });
+
+        return httpSecurity.build();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager() {
+        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
+        authenticationProvider.setUserDetailsService(userDetailsService());
+        authenticationProvider.setPasswordEncoder(passwordEncoder());
+        return new ProviderManager(authenticationProvider);
+    }
+
+    @Bean
+    public UserDetailsService userDetailsService() {
+        return new UserDetailsServiceImpl();
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+}

+ 22 - 0
src/main/java/com/hz3r/infrastructure/park/domain/beb/agc/Ba1attATemplateTableBEB.java

@@ -0,0 +1,22 @@
+package com.hz3r.infrastructure.park.domain.beb.agc;
+
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+
+/**
+ * <p>
+ * 业务模板表 业务数据扩展
+ * </p>
+ *
+ * @author hzhs
+ * @since 2026年04月 第5周
+ */
+@JsonInclude(JsonInclude.Include.NON_DEFAULT)
+@Data
+public class Ba1attATemplateTableBEB {
+
+
+    /*BatchReplace*/
+}

+ 101 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/InvoiceTypeEnum.java

@@ -0,0 +1,101 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 账单类型枚举
+ *
+ */
+public enum InvoiceTypeEnum {
+
+    INCOME   ("收入",   "income", ""),
+    EXPENSE ("支出",  "expense", "")
+
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, InvoiceTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public InvoiceTypeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    InvoiceTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 111 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/JeTypeEnum.java

@@ -0,0 +1,111 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 明细类型枚举
+ *
+ */
+public enum JeTypeEnum {
+    /**
+     * 订单流水类型
+     */
+    CONSULTATION_FEE ("费用",  "Consultation_Fee", ""),
+    CONSULTATION_DEPOSIT ("商家费用",  "Consultation_Deposit", ""),
+    DEPOSIT_CHARGING ("储值充值",  "Deposit_charging", ""),
+    /**
+     * 退款流水类型
+     */
+    ORDER_REFUND   ("订单退款",   "Order_Refund", ""),
+    INDEMNITY_PAYMENT ("违约赔款",  "Indemnity_Payment", ""),
+    DEPOSIT_WITHDRAWAL ("储值提取",  "Deposit_Withdrawal", ""),
+    DEPOSIT_REFUND ("储值退还",  "Deposit_Refund", "")
+
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, JeTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public JeTypeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    JeTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 106 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/MchCDDRecordTypeEnum.java

@@ -0,0 +1,106 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 商户储值记录类型 枚举
+ *
+ */
+public enum MchCDDRecordTypeEnum {
+
+    CHARGING    ("充值",   "charging", "储值充值记录"),
+    WITHDRAWAL ("提取",  "withdrawal", ""),
+    ORDER    ("订单扣款",   "order", "订单扣除储值记录"),
+    REFUND    ("退单退还",   "refund", "订单退款归还储值记录"),
+    COMPENSATION      ("订单赔付",  "compensation", "订单对方过错赔付")
+
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, MchCDDRecordTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public MchCDDRecordTypeEnum of(String code) {
+        return CODE_MAP.get(code);
+    }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    MchCDDRecordTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 101 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/MchCarInventoryEeRecordEnum.java

@@ -0,0 +1,101 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 商户车辆库存明细出入类型枚举
+ *
+ */
+public enum MchCarInventoryEeRecordEnum {
+
+    INCREASE ("增加库存", "increase", "增加库存数量"),
+    REDUCE  ("扣减库存", "reduce", "扣减库存数量")
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, MchCarInventoryEeRecordEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public MchCarInventoryEeRecordEnum of(String code) { return  CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    MchCarInventoryEeRecordEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 108 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/OrderStatusEnum.java

@@ -0,0 +1,108 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 订单状态枚举
+ *
+ *
+ */
+public enum OrderStatusEnum {
+
+    SUBMITTING ("提交中", "submitting", "提交中,还在草稿中"),
+    WAIT2PAY ("待支付", "wait2pay", "已提交,客户不可修改订单"),
+    PAYING ("支付中", "paying", "支付中,需要定时确认结果"),
+    PAID ("已支付", "paid", "订单已支付"),
+    EXECUTING ("执行中", "executing", "订单内容执行中,如物流中等"),
+    CONFIRMING ("交易确认中", "confirming", "订单交易结果确认中"),
+    FINISHED ("完成", "finished", "已完成"),
+            ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, OrderStatusEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public OrderStatusEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    OrderStatusEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+
+
+}

+ 114 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/OrderTypeEnum.java

@@ -0,0 +1,114 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 订单类型枚举
+ *
+ */
+public enum OrderTypeEnum {
+
+    CARD_FEE_DEPOSIT ("IC卡押金和工本费", "card_fee_deposit", "IC卡押金和工本费"),
+    CARD_RECHARGE ("IC卡充值", "card_recharge", "IC卡充值"),
+    CASH_DEPOSIT ("储值", "cash_deposit", "储值订单"),
+    PARKING ("停车缴费", "parking", "园区停车支付订单"),
+    PARKING_PREPAY ("停车预付", "parking_prepay", "园区停车预付订单"),
+    ELECTRICITY  ("电费缴纳", "electricity", "园区电费支付订单"),
+    WATER        ("水费缴纳", "water", "园区水费支付订单"),
+    ELECTRICITY_PREPAY  ("电费预付", "electricity_prepay", "园区电费预付订单"),
+    WATER_PREPAY        ("水费预付", "water_prepay", "园区水费预付订单"),
+    METER_PREPAY       ("水电预付", "meter_prepay", "园区水电预付"),
+    METER_PREPAY_APP   ("移动端水电预付", "meter_prepay_app", "移动端园区水电预付"),
+
+    /* --------------------------------------------------------------------------------------------------------- */
+    IGNORED ("无需订单", "ignored", "无需订单的下单操作")
+    ;
+
+
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, OrderTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public OrderTypeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    OrderTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 103 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PaFreasonEnum.java

@@ -0,0 +1,103 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 支付失败原因枚举
+ *
+ */
+public enum PaFreasonEnum {
+
+    UNAVAILABLE ("支付不可用", "unavailable", "支付不可用,如 Redis 失效找不到支付二维码,支付服务失效。"),
+    TIMEOUT ("支付超时", "timeout", "支付操作超时"),
+    PAYER_CLOSE ("用户关闭", "payer_close", "订单发起方关闭支付"),
+    PT_CLOSE ("平台关闭", "pt_close", "平台运营方关闭支付")
+
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, PaFreasonEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public PaFreasonEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    PaFreasonEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 108 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/ParkingOrderingRecordTypeEnum.java

@@ -0,0 +1,108 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 停车下单记录类型枚举
+ *
+ */
+public enum ParkingOrderingRecordTypeEnum {
+
+    TVC_CASH_PAY_FEE ("临时停车缴费", "tvccpf", "园区临时停车支付订单"),
+    PARKING_CASH_PREPAY ("现金储值", "cdpp", "园区停车储值预付支付订单"),
+    PARKING_MCR_PREPAY ("月租充值", "mrcpp", "园区停车月租预付支付订单")
+    ;
+
+
+
+
+
+
+
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, ParkingOrderingRecordTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public ParkingOrderingRecordTypeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    ParkingOrderingRecordTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 104 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayIfCodeEnum.java

@@ -0,0 +1,104 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 支付接口 枚举
+ * 如 wxpay-微信官方接口 ; alipay-支付宝官方接口
+ *
+ */
+public enum PayIfCodeEnum {
+
+    NONE ("未选择", "", "未选择支付接口"),
+    WXPAY ("微信官方", "wxpay", "微信支付"),
+    ALIPAY ("支付宝官方", "alipay", "支付宝支付")
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, PayIfCodeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public PayIfCodeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+
+    PayIfCodeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 101 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayServiceEnum.java

@@ -0,0 +1,101 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 支付服务枚举
+ *
+ */
+public enum PayServiceEnum {
+
+    JEEPAY ("计全支付服务", "jeepay", "自行部署的计全支付服务"),
+    INTERNAL ("系统平台", "internal", "系统平台内部流转,如储值")
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, PayServiceEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public PayServiceEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    PayServiceEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+
+}

+ 102 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayStatusEnum.java

@@ -0,0 +1,102 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 支付状态枚举
+ *
+ */
+public enum PayStatusEnum {
+
+    NONE ("未知", "", "未设置状态"),
+    CONFIRMING ("确认中", "confirming", "确认支付结果中"),
+    OK ("支付成功", "ok", "支付完成并成功"),
+    FAILED ("支付失败", "failed", "支付失败")
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, PayStatusEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public PayStatusEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    PayStatusEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 118 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/PayWayEnum.java

@@ -0,0 +1,118 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 支付方式枚举
+ *
+ */
+public enum PayWayEnum {
+
+    NONE ("未选择", "none", "no_psp","未选择支付方式"),
+    WX_NATIVE ("微信官方扫码", "wx_native", PayIfCodeEnum.WXPAY.getCode(), "微信APP扫码"),
+    WX_CASH ("微信转账到零钱", "wx_cash", PayIfCodeEnum.WXPAY.getCode(),"微信钱包零钱入账,记转账到微信钱包"),
+    WX_REFUND ("微信订单退款", "wx_refund", PayIfCodeEnum.WXPAY.getCode(),"微信原路退款"),
+
+    ALI_PC ("支付宝PC网站支付", "ali_pc", PayIfCodeEnum.ALIPAY.getCode(), "支付宝PC网站支付"),
+    ALI_LITE ("支付宝小程序支付", "ali_lite", PayIfCodeEnum.ALIPAY.getCode(), "支付宝小程序支付"),
+    ALI_JSAPI ("支付宝 H5 支付", "ali_jsapi", PayIfCodeEnum.ALIPAY.getCode(), "支付宝 H5 JSAPI 支付"),
+    ALI_CASH ("支付宝转账", "ali_cash", PayIfCodeEnum.ALIPAY.getCode(),"支付宝转账"),
+    ALI_REFUND ("支付宝订单退款", "ali_refund", PayIfCodeEnum.ALIPAY.getCode(),"支付宝退款")
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, PayWayEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public PayWayEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 支付接口代码,如 wxpay-微信官方接口 ; alipay-支付宝官方接口
+     *
+     */
+    private final String ifCode;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+
+    PayWayEnum(String name, String code, String ifCode, String description) {
+        this.name = name;
+        this.code = code;
+        this.ifCode = ifCode;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    public String getIfCode() { return ifCode; }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 23 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RedisKeyConstants.java

@@ -0,0 +1,23 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+
+/**
+ * Redis Key 常量
+ *
+ */
+public class RedisKeyConstants {
+
+    /**
+     * 支付二维码
+     */
+    static public final String PAY_QR_CODE_URL = "pay-qr-code:";
+
+    /**
+     * 转账二维码
+     */
+    static public final String TRANSFER_QR_CODE_URL = "transfer-qr-code:";
+
+    static public final String MCH_CASH_DEPOSIT_BALANCE = "mch-cash-deposit-balance:";
+  
+
+}

+ 100 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RefundPaymentResultEnum.java

@@ -0,0 +1,100 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 退单支付结果 枚举
+ */
+public enum RefundPaymentResultEnum {
+
+    NONE("未知", "", "未设置状态"),
+    REFUNDED("已退款", "refunded", "退款支付完成"),
+    TRANSFERRED("已转账", "transferred", "退款转账完成"),
+    ;
+
+    /**
+     * 值映射
+     */
+    static private final Map<String, RefundPaymentResultEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public RefundPaymentResultEnum of(String code) {
+        return CODE_MAP.get(code);
+    }
+
+    /**
+     * 枚举名称
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     */
+    private final String code;
+
+    /**
+     * 描述
+     */
+    private final String description;
+
+    RefundPaymentResultEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() {
+        return this.getCode();
+    }
+
+}

+ 103 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RefundStatusEnum.java

@@ -0,0 +1,103 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 退单状态枚举
+ *
+ *
+ */
+public enum RefundStatusEnum {
+
+    APPROVING ("审核中", "approving", "审核中,平台审核,或商家审核"),
+    PAYING ("支付中", "paying", "支付中,需要定时确认结果"),
+    PAID ("已支付", "paid", "订单已支付"),
+    FINISHED ("完成", "finished", "已完成"),
+            ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, RefundStatusEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public RefundStatusEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    RefundStatusEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

+ 101 - 0
src/main/java/com/hz3r/infrastructure/park/domain/constant/adc/RefundTypeEnum.java

@@ -0,0 +1,101 @@
+package com.hz3r.infrastructure.park.domain.constant.adc;
+
+import cn.hutool.core.map.MapUtil;
+import com.hz3r.utils.MyStrUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 退款类型枚举
+ *
+ */
+public enum RefundTypeEnum {
+
+    CASH_DEPOSIT    ("储值",   "cash_deposit", "储值退款"),
+    COUNSELING_USER ("费用支付退款",  "counseling_user", "费用退款,退给用户")
+
+    ;
+
+    /**
+     * 值映射
+     *
+     */
+    static private final Map<String, RefundTypeEnum> CODE_MAP = MapUtil
+            .unmodifiable(Stream
+                    .of(values())
+                    .collect(Collectors.toMap(e -> e.getCode(), e -> e))
+            );
+
+    /**
+     * 代码转成枚举对象
+     *
+     * @param code
+     * @return
+     */
+    static public RefundTypeEnum of(String code) { return CODE_MAP.get(code); }
+
+    /**
+     * 枚举名称
+     *
+     */
+    private final String name;
+
+    /**
+     * 枚举值,不能有汉字,仅字母,数字,下划线,横线
+     *
+     */
+    private final String code;
+
+    /**
+     * 描述
+     *
+     */
+    private final String description;
+
+    RefundTypeEnum(String name, String code, String description) {
+        this.name = name;
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 枚举名称
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * 枚举代码
+     *
+     * @return
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 比较代码是否一致。忽略大小写。
+     *
+     * @param code
+     * @return
+     */
+    @NotNull
+    public Boolean equalsIgnoreCase(String code) {
+        return MyStrUtils.equalsIgnoreCase(code, this.code);
+    }
+
+    /**
+     * 覆盖枚举继承的 toString -> name,改成 toString -> code。
+     *
+     * @return
+     */
+    @Override
+    public String toString() { return this.getCode(); }
+
+}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä