[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.{groovy,java,kt,xml}]\nindent_style = tab\nindent_size = 4\ncontinuation_indent_size = 8\n"
  },
  {
    "path": ".gitee/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false # 不允许用户创建空白 Issue\ncontact_links:\n  - name: 遇到问题先去看文档！谢谢！ # 外部网站名称\n    url: https://wiki.pig4cloud.com/ # 跳转的外部网站目标地址\n    about: 文档可以解决你80%的疑惑 # 跳转外部网站的描述说明\n"
  },
  {
    "path": ".gitee/ISSUE_TEMPLATE/issue.yml",
    "content": "name: 问题咨询\ndescription: \"请尽可能详细的描述问题，提供足够的上下文，一分钟的描述不需要期望别人花半小时帮你排查\"\nbody:\n  - type: dropdown\n    id: version\n    attributes:\n      label: PIG版本（提问先右上角 Star ♥️）\n      options:\n        - \"不处理PIGX或其他魔改版本\"\n        - \"3.9\"\n        - \"3.8\"\n        - \"3.7\"\n    validations:\n      required: true\n  - type: checkboxes\n    validations:\n      required: true\n    attributes:\n      label: 架构\n      options:\n        - label: 微服务架构\n        - label: 单体架构\n  - type: textarea\n    id: desired-solution\n    attributes:\n      label: 问题描述，提供详细截图和报错\n      description: 详细问题，提供相应截图和日志，一分钟的描述不需要期望别人花半小时帮你排查\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"baseBranches\": [\n    \"jdk17-dev\"\n  ],\n  \"extends\": [\n    \"config:recommended\"\n  ],\n  \"rangeStrategy\": \"bump\",\n  \"packageRules\": [\n    {\n      \"matchPackagePatterns\": [\n        \"^com.amazonaws:aws-java-sdk-s3$\",\n        \"^tomcat$\",\n        \"^io.springboot:knife4j-openapi3-ui$\",\n        \"^com.alibaba:fastjson$\",\n        \"^org.anyline:anyline.*$\",\n        \"^org.apache.velocity:velocity-engine-core.*$\"\n      ],\n      \"enabled\": false\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/github-release.yml",
    "content": "name: publish github release\n\non:\n  workflow_dispatch:\n    inputs:\n      releaseversion:\n        description: 'Release version'\n        required: true\n        default: '3.8.0'\n\njobs:\n  publish-github-release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Generate changelog\n        id: changelog\n        uses: metcalfc/changelog-generator@v4.5.0\n        with:\n          myToken: ${{ secrets.GH_TOKEN }}\n\n      - name: Create GitHub Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}\n        with:\n          tag_name: ${{ github.event.inputs.releaseversion }}\n          release_name: ${{ github.event.inputs.releaseversion }}\n          body: |\n            ### Things that changed in this release\n            ${{ steps.changelog.outputs.changelog }}\n          draft: false\n          prerelease: ${{ contains(github.event.inputs.releaseversion, '-') }}\n"
  },
  {
    "path": ".github/workflows/image.yml",
    "content": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven\n\nname: Docker 镜像 构建\n\non:\n  push:\n    branches: [ master ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        java-version: [ 17 ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up JDK ${{ matrix.java-version }}\n        uses: actions/setup-java@v4\n        with:\n          java-version: ${{ matrix.java-version }}\n          distribution: 'zulu'\n\n      - name: mvn clean install\n        run: mvn clean install -Pcloud\n\n      - name: Login to Docker Registry\n        run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com\n\n      - name: Build and push Docker images\n        run: |\n          docker compose build\n          registry=\"registry.cn-hangzhou.aliyuncs.com/pigx/\"\n          for service in $(docker compose config --services); do\n            if [ \"$service\" != \"pig-redis\" ]; then\n              docker tag ${service}:latest ${registry}${service}:latest\n              docker push ${registry}${service}:latest\n            else\n              echo \"Skipping pig-redis service\"\n            fi\n          done\n"
  },
  {
    "path": ".github/workflows/maven.yml",
    "content": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven\n\nname: maven 编译检查\n\non:\n  push:\n    branches: [ master,dev ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        java-version: [ 17,21,25 ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up JDK ${{ matrix.java-version }}\n        uses: actions/setup-java@v4\n        with:\n          java-version: ${{ matrix.java-version }}\n          distribution: 'zulu'\n\n      - name: mvn spring-javaformat:validate\n        id: validate\n        run: mvn spring-javaformat:validate -Dmaven.compiler.release=${{ matrix.java-version }}\n        continue-on-error: true\n\n      - name: Auto format code if validation fails\n        if: steps.validate.outcome == 'failure'\n        run: mvn spring-javaformat:apply -Dmaven.compiler.release=${{ matrix.java-version }}\n\n      - name: Create Pull Request for formatting changes\n        if: steps.validate.outcome == 'failure'\n        uses: peter-evans/create-pull-request@v5\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          commit-message: 'Auto-format code with spring-javaformat'\n          title: 'Auto-format: Fix code formatting issues'\n          body: |\n            This PR was automatically created because the spring-javaformat validation failed.\n            \n            The following changes have been applied:\n            - Applied spring-javaformat:apply to fix formatting issues\n            \n            Please review and merge if the changes look correct.\n          branch: auto-format-${{ github.run_number }}\n          delete-branch: true\n\n      - name: mvn clean install\n        run: mvn clean install -Pboot -Dmaven.compiler.release=${{ matrix.java-version }}\n\n      - name: mvn clean install\n        run: mvn clean install -Dmaven.compiler.release=${{ matrix.java-version }}\n\n      - name:  failure\n        if: failure() && github.repository == 'pig-mesh/pig'\n        uses: chf007/action-wechat-work@master\n        env:\n          WECHAT_WORK_BOT_WEBHOOK: ${{secrets.WECHAT_WORK_BOT_WEBHOOK}}\n        with:\n          msgtype: markdown\n          content: |\n            # 💤🤷‍♀️ failure 🙅‍♂️💣 [pig-mesh/pig](https://github.com/pig-mesh/pig)\n            > Github Action: https://github.com/pig-mesh/pig failure\n            > (⋟﹏⋞)   from github action message\n"
  },
  {
    "path": ".github/workflows/mirror.yml",
    "content": "name: 同步代码\n\non:\n  push:\n    branches: [ master,dev ]\n  pull_request:\n    branches: [ master,dev ]\n\njobs:\n  gitee:\n    runs-on: ubuntu-latest\n    container:\n      image: \"centos:8\"\n    steps:\n      - uses: wearerequired/git-mirror-action@master #同步至 gitee\n        env:\n          SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}\n        with:\n          source-repo: \"git@github.com:pig-mesh/pig.git\"\n          destination-repo: \"git@gitee.com:log4j/pig.git\"\n"
  },
  {
    "path": ".gitignore",
    "content": "### gradle ###\n.gradle\n/build/\n!gradle/wrapper/gradle-wrapper.jar\n.mvn/wrapper/maven-wrapper.jar\n\n### STS ###\n.settings/\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\nbin/\n\n### IntelliJ IDEA ###\n!.idea/icon.png\n.idea\n*.iws\n*.iml\n*.ipr\nrebel.xml\n\n### NetBeans ###\nnbproject/private/\nbuild/\nnbbuild/\nnbdist/\n.nb-gradle/\n\n### maven ###\ntarget/\n*.war\n*.ear\n*.zip\n*.tar\n*.tar.gz\n*.versionsBackup\n\n### vscode ###\n.vscode\n\n### logs ###\n/logs/\n*.log\n\n### temp ignore ###\n*.cache\n*.diff\n*.patch\n*.tmp\n*.java~\n*.properties~\n*.xml~\n\n### system ignore ###\n.DS_Store\nThumbs.db\nServers\n.metadata\n.flattened-pom.xml\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "> **🚀 Spring Boot 4.0 版本来了**\n>\n> 分支 `boot4` 基于 Spring Boot 4.0 + Spring Cloud 2025.1 进行开发。\n\n<p align=\"center\">\n <img src=\"https://img.shields.io/badge/Pig-3.9-success.svg\" alt=\"Build Status\">\n <img src=\"https://img.shields.io/badge/Spring%20Cloud-2025-blue.svg\" alt=\"Coverage Status\">\n <img src=\"https://img.shields.io/badge/Spring%20Boot-3.5-blue.svg\" alt=\"Downloads\">\n <img src=\"https://img.shields.io/badge/Vue-3.5-blue.svg\" alt=\"Downloads\">\n <img src=\"https://img.shields.io/github/license/pig-mesh/pig\"/>\n <img src=\"https://gitcode.com/pig-mesh/pig/star/badge.svg\"/>\n</p>\n\n## 系统说明\n\n- 基于 Spring Cloud 、Spring Boot、 OAuth2 的 RBAC **企业级快速开发平台**， 同时支持微服务架构和单体架构\n- 提供 Spring Authorization Server 的生产级实践方案，支持多种安全授权模式\n- 提供对常见容器化方案支持 Kubernetes、Rancher2 、KubeSphere、EDAS、SAE 支持\n\n#### 使用文档\n\nPIG 提供了详尽的部署文档 👉 [wiki.pig4cloud.com](https://wiki.pig4cloud.com)，涵盖开发环境配置、服务端启动、前端运行等关键步骤。\n\n重要的事情说三遍：\n\n- 🔥 [ 配套文档 wiki.pig4cloud.com](https://wiki.pig4cloud.com)\n- 🔥 [ 配套文档 wiki.pig4cloud.com](https://wiki.pig4cloud.com)\n- 🔥 [ 配套文档 wiki.pig4cloud.com](https://wiki.pig4cloud.com)\n\n#### 其他产品\n\n- 👉🏻 [PIGX 在线体验](http://home.pig4cloud.com:38081)\n\n- 👉🏻 [自研BPMN工作流引擎](http://home.pig4cloud.com:38082)\n\n- 👉🏻 [大模型 RAG 知识库](http://home.pig4cloud.com:38083)\n\n## 微信群 [禁广告]\n\n<img src='https://minio.pigx.vip/oss/202412/1735262426.png' alt='1735262426'/>\n\n## 快速开始\n\n#### Docker 快速体验\n\n```shell\n# 可用内存大于4G\ncurl -o docker-compose.yaml https://try.pig4cloud.com\n# 等待5分钟\ndocker compose up\n```\n\n### 核心依赖\n\n| 依赖                         | 版本     |\n|-----------------------------|--------|\n| Spring Boot                 | 3.5.11 |\n| Spring Cloud                | 2025   |\n| Spring Cloud Alibaba        | 2025   |\n| Spring Authorization Server | 1.5.2  |\n| Mybatis Plus                | 3.5.15 |\n| Vue                         | 3.5    |\n| Element Plus                | 2.8    |\n\n### 模块说明\n\n```lua\npig-ui  -- https://gitee.com/log4j/pig-ui\n\npig\n├── pig-boot -- 单体模式启动器[9999]\n├── pig-auth -- 授权服务提供[3000]\n└── pig-common -- 系统公共模块\n     ├── pig-common-bom -- 全局依赖管理控制\n     ├── pig-common-core -- 公共工具类核心包\n     ├── pig-common-datasource -- 动态数据源包\n     ├── pig-common-log -- 日志服务\n     ├── pig-common-oss -- 文件上传工具类\n     ├── pig-common-mybatis -- mybatis 扩展封装\n     ├── pig-common-seata -- 分布式事务\n     ├── pig-common-websocket -- websocket 封装\n     ├── pig-common-security -- 安全工具类\n     ├── pig-common-swagger -- 接口文档\n     ├── pig-common-feign -- feign 扩展封装\n     └── pig-common-xss -- xss 安全封装\n├── pig-register -- Nacos Server[8848]\n├── pig-gateway -- Spring Cloud Gateway网关[9999]\n└── pig-upms -- 通用用户权限管理模块\n     └── pig-upms-api -- 通用用户权限管理系统公共api模块\n     └── pig-upms-biz -- 通用用户权限管理系统业务处理模块[4000]\n└── pig-visual\n     └── pig-monitor -- 服务监控 [5001]\n     ├── pig-codegen -- 图形化代码生成 [5002]\n     └── pig-quartz -- 定时任务管理台 [5007]\n```\n\n## 免费公开课\n\n<table>\n  <tr>\n    <td><a href=\"https://www.bilibili.com/video/av45084065\" target=\"_blank\"><img src=\"https://foruda.gitee.com/images/1731647304254897555/88a9c2fa_441246.jpeg\"></a></td>\n    <td><a href=\"https://www.bilibili.com/video/av77344954\" target=\"_blank\"><img src=\"https://foruda.gitee.com/images/1731647324953921510/39689640_441246.jpeg\"></a></td>\n  </tr>\n    <tr>\n    <td><a href=\"https://www.bilibili.com/video/BV1J5411476V\" target=\"_blank\"><img src=\"https://foruda.gitee.com/images/1731647357502030768/7f31f392_441246.jpeg\"></a></td>\n    <td><a href=\"https://www.bilibili.com/video/BV14p4y197K5\" target=\"_blank\"><img src=\"https://foruda.gitee.com/images/1731647375444479120/2b8fd494_441246.jpeg\"></a></td>\n  </tr>\n</table>\n\n## 开源共建\n\n### 开源协议\n\npig 开源软件遵循 [Apache 2.0 协议](https://www.apache.org/licenses/LICENSE-2.0.html)。\n允许商业使用，但务必保留类作者、Copyright 信息。\n\n![](https://foruda.gitee.com/images/1731647419204307063/91217172_441246.jpeg)\n\n### 其他说明\n\n1. 欢迎提交 [PR](https://dwz.cn/2KURd5Vf)，注意对应提交对应 `dev` 分支\n   代码规范 [spring-javaformat](https://github.com/spring-io/spring-javaformat)\n\n   <details>\n    <summary>代码规范说明</summary>\n\n    1. 由于 <a href=\"https://github.com/spring-io/spring-javaformat\" target=\"_blank\">spring-javaformat</a>\n       强制所有代码按照指定格式排版，未按此要求提交的代码将不能通过合并（打包）\n    2. 如果使用 IntelliJ IDEA\n       开发，请安装自动格式化软件 <a href=\"https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin/\" target=\"_blank\">\n       spring-javaformat-intellij-idea-plugin</a>\n    3. 其他开发工具，请参考 <a href=\"https://github.com/spring-io/spring-javaformat\" target=\"_blank\">\n       spring-javaformat</a>\n       说明，或`提交代码前`在项目根目录运行下列命令（需要开发者电脑支持`mvn`命令）进行代码格式化\n       ```\n       mvn spring-javaformat:apply\n       ```\n   </details>\n\n2. 欢迎提交 [issue](https://gitee.com/log4j/pig/issues)，请写清楚遇到问题的原因、开发环境、复显步骤。\n"
  },
  {
    "path": "db/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/mysql-server:8.0.32\n\nMAINTAINER lengleng(wangiegie@gmail.com)\n\nENV TZ=Asia/Shanghai\n\nRUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\n\nCOPY ./pig.sql /docker-entrypoint-initdb.d\n\nCOPY ./pig_config.sql /docker-entrypoint-initdb.d\n\nEXPOSE 3306\n"
  },
  {
    "path": "db/pig.sql",
    "content": "DROP DATABASE IF EXISTS `pig`;\n\nCREATE DATABASE  `pig` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\nUSE `pig`;\n\n\n-- ----------------------------\n-- Table structure for sys_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dept`;\nCREATE TABLE `sys_dept` (\n  `dept_id` bigint NOT NULL COMMENT '部门ID',\n  `name` varchar(50)  DEFAULT NULL COMMENT '部门名称',\n  `sort_order` int NOT NULL DEFAULT '0' COMMENT '排序',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '修改时间',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',\n  `parent_id` bigint DEFAULT NULL COMMENT '父级部门ID',\n  PRIMARY KEY (`dept_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='部门管理';\n\n-- ----------------------------\n-- Records of sys_dept\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dept` VALUES (1, '总裁办', 1, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:07:49', '0', 0);\nINSERT INTO `sys_dept` VALUES (2, '技术部', 2, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);\nINSERT INTO `sys_dept` VALUES (3, '市场部', 3, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);\nINSERT INTO `sys_dept` VALUES (4, '销售部', 4, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);\nINSERT INTO `sys_dept` VALUES (5, '财务部', 5, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);\nINSERT INTO `sys_dept` VALUES (6, '人事行政部', 6, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:53:36', '1', 1);\nINSERT INTO `sys_dept` VALUES (7, '研发部', 7, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 2);\nINSERT INTO `sys_dept` VALUES (8, 'UI设计部', 11, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 7);\nINSERT INTO `sys_dept` VALUES (9, '产品部', 12, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 2);\nINSERT INTO `sys_dept` VALUES (10, '渠道部', 13, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 3);\nINSERT INTO `sys_dept` VALUES (11, '推广部', 14, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 3);\nINSERT INTO `sys_dept` VALUES (12, '客服部', 15, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 4);\nINSERT INTO `sys_dept` VALUES (13, '财务会计部', 16, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 5);\nINSERT INTO `sys_dept` VALUES (14, '审计风控部', 17, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 14:06:57', '0', 5);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dict\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dict`;\nCREATE TABLE `sys_dict` (\n  `id` bigint NOT NULL COMMENT '编号',\n  `dict_type` varchar(100)  DEFAULT NULL COMMENT '字典类型',\n  `description` varchar(100)  DEFAULT NULL COMMENT '描述',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  `remarks` varchar(255)  DEFAULT NULL COMMENT '备注信息',\n  `system_flag` char(1)  DEFAULT '0' COMMENT '系统标志',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',\n  PRIMARY KEY (`id`) USING BTREE,\n  KEY `sys_dict_del_flag` (`del_flag`) USING BTREE\n) ENGINE=InnoDB  COMMENT='字典表';\n\n-- ----------------------------\n-- Records of sys_dict\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dict` VALUES (1, 'log_type', '日志类型', ' ', ' ', '2019-03-19 11:06:44', '2019-03-19 11:06:44', '异常、正常', '1', '0');\nINSERT INTO `sys_dict` VALUES (2, 'social_type', '社交登录', ' ', ' ', '2019-03-19 11:09:44', '2019-03-19 11:09:44', '微信、QQ', '1', '0');\nINSERT INTO `sys_dict` VALUES (3, 'job_type', '定时任务类型', ' ', ' ', '2019-03-19 11:22:21', '2019-03-19 11:22:21', 'quartz', '1', '0');\nINSERT INTO `sys_dict` VALUES (4, 'job_status', '定时任务状态', ' ', ' ', '2019-03-19 11:24:57', '2019-03-19 11:24:57', '发布状态、运行状态', '1', '0');\nINSERT INTO `sys_dict` VALUES (5, 'job_execute_status', '定时任务执行状态', ' ', ' ', '2019-03-19 11:26:15', '2019-03-19 11:26:15', '正常、异常', '1', '0');\nINSERT INTO `sys_dict` VALUES (6, 'misfire_policy', '定时任务错失执行策略', ' ', ' ', '2019-03-19 11:27:19', '2019-03-19 11:27:19', '周期', '1', '0');\nINSERT INTO `sys_dict` VALUES (7, 'gender', '性别', ' ', ' ', '2019-03-27 13:44:06', '2019-03-27 13:44:06', '微信用户性别', '1', '0');\nINSERT INTO `sys_dict` VALUES (8, 'subscribe', '订阅状态', ' ', ' ', '2019-03-27 13:48:33', '2019-03-27 13:48:33', '公众号订阅状态', '1', '0');\nINSERT INTO `sys_dict` VALUES (9, 'response_type', '回复', ' ', ' ', '2019-03-28 21:29:21', '2019-03-28 21:29:21', '微信消息是否已回复', '1', '0');\nINSERT INTO `sys_dict` VALUES (10, 'param_type', '参数配置', ' ', ' ', '2019-04-29 18:20:47', '2019-04-29 18:20:47', '检索、原文、报表、安全、文档、消息、其他', '1', '0');\nINSERT INTO `sys_dict` VALUES (11, 'status_type', '租户状态', ' ', ' ', '2019-05-15 16:31:08', '2019-05-15 16:31:08', '租户状态', '1', '0');\nINSERT INTO `sys_dict` VALUES (12, 'dict_type', '字典类型', ' ', ' ', '2019-05-16 14:16:20', '2019-05-16 14:20:16', '系统类不能修改', '1', '0');\nINSERT INTO `sys_dict` VALUES (13, 'channel_type', '支付类型', ' ', ' ', '2019-05-16 14:16:20', '2019-05-16 14:20:16', '系统类不能修改', '1', '0');\nINSERT INTO `sys_dict` VALUES (14, 'grant_types', '授权类型', ' ', ' ', '2019-08-13 07:34:10', '2019-08-13 07:34:10', NULL, '1', '0');\nINSERT INTO `sys_dict` VALUES (15, 'style_type', '前端风格', ' ', ' ', '2020-02-07 03:49:28', '2020-02-07 03:50:40', '0-Avue 1-element', '1', '0');\nINSERT INTO `sys_dict` VALUES (16, 'captcha_flag_types', '验证码开关', ' ', ' ', '2020-11-18 06:53:25', '2020-11-18 06:53:25', '是否校验验证码', '1', '0');\nINSERT INTO `sys_dict` VALUES (17, 'enc_flag_types', '前端密码加密', ' ', ' ', '2020-11-18 06:54:44', '2020-11-18 06:54:44', '前端密码是否加密传输', '1', '0');\nINSERT INTO `sys_dict` VALUES (18, 'lock_flag', '用户状态', 'admin', ' ', '2023-02-01 16:55:31', NULL, NULL, '1', '0');\nINSERT INTO `sys_dict` VALUES (19, 'ds_config_type', '数据连接类型', 'admin', ' ', '2023-02-06 18:36:59', NULL, NULL, '1', '0');\nINSERT INTO `sys_dict` VALUES (20, 'common_status', '通用状态', 'admin', ' ', '2023-02-09 11:02:08', NULL, NULL, '1', '0');\nINSERT INTO `sys_dict` VALUES (21, 'app_social_type', 'app社交登录', 'admin', ' ', '2023-02-10 11:11:06', NULL, 'app社交登录', '1', '0');\nINSERT INTO `sys_dict` VALUES (22, 'yes_no_type', '是否', 'admin', ' ', '2023-02-20 23:25:04', NULL, NULL, '1', '0');\nINSERT INTO `sys_dict` VALUES (23, 'repType', '微信消息类型', 'admin', ' ', '2023-02-24 15:08:25', NULL, NULL, '0', '0');\nINSERT INTO `sys_dict` VALUES (24, 'leave_status', '请假状态', 'admin', ' ', '2023-03-02 22:50:15', NULL, NULL, '0', '0');\nINSERT INTO `sys_dict` VALUES (25, 'schedule_type', '日程类型', 'admin', ' ', '2023-03-06 14:49:18', NULL, NULL, '0', '0');\nINSERT INTO `sys_dict` VALUES (26, 'schedule_status', '日程状态', 'admin', ' ', '2023-03-06 14:52:57', NULL, NULL, '0', '0');\nINSERT INTO `sys_dict` VALUES (27, 'ds_type', '代码生成器支持的数据库类型', 'admin', ' ', '2023-03-12 09:57:59', NULL, NULL, '1', '0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dict_item\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dict_item`;\nCREATE TABLE `sys_dict_item` (\n  `id` bigint NOT NULL COMMENT '编号',\n  `dict_id` bigint NOT NULL COMMENT '字典ID',\n  `item_value` varchar(100)  DEFAULT NULL COMMENT '字典项值',\n  `label` varchar(100)  DEFAULT NULL COMMENT '字典项名称',\n  `dict_type` varchar(100)  DEFAULT NULL COMMENT '字典类型',\n  `description` varchar(100)  DEFAULT NULL COMMENT '字典项描述',\n  `sort_order` int NOT NULL DEFAULT '0' COMMENT '排序（升序）',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  `remarks` varchar(255)  DEFAULT NULL COMMENT '备注信息',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',\n  PRIMARY KEY (`id`) USING BTREE,\n  KEY `sys_dict_value` (`item_value`) USING BTREE,\n  KEY `sys_dict_label` (`label`) USING BTREE,\n  KEY `sys_dict_item_del_flag` (`del_flag`) USING BTREE\n) ENGINE=InnoDB  COMMENT='字典项';\n\n-- ----------------------------\n-- Records of sys_dict_item\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dict_item` VALUES (1, 1, '9', '异常', 'log_type', '日志异常', 1, ' ', ' ', '2019-03-19 11:08:59', '2019-03-25 12:49:13', '', '0');\nINSERT INTO `sys_dict_item` VALUES (2, 1, '0', '正常', 'log_type', '日志正常', 0, ' ', ' ', '2019-03-19 11:09:17', '2019-03-25 12:49:18', '', '0');\nINSERT INTO `sys_dict_item` VALUES (3, 2, 'WX', '微信', 'social_type', '微信登录', 0, ' ', ' ', '2019-03-19 11:10:02', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (4, 2, 'QQ', 'QQ', 'social_type', 'QQ登录', 1, ' ', ' ', '2019-03-19 11:10:14', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (5, 3, '1', 'java类', 'job_type', 'java类', 1, ' ', ' ', '2019-03-19 11:22:37', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (6, 3, '2', 'spring bean', 'job_type', 'spring bean容器实例', 2, ' ', ' ', '2019-03-19 11:23:05', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (7, 3, '9', '其他', 'job_type', '其他类型', 9, ' ', ' ', '2019-03-19 11:23:31', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (8, 3, '3', 'Rest 调用', 'job_type', 'Rest 调用', 3, ' ', ' ', '2019-03-19 11:23:57', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (9, 3, '4', 'jar', 'job_type', 'jar类型', 4, ' ', ' ', '2019-03-19 11:24:20', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (10, 4, '1', '未发布', 'job_status', '未发布', 1, ' ', ' ', '2019-03-19 11:25:18', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (11, 4, '2', '运行中', 'job_status', '运行中', 2, ' ', ' ', '2019-03-19 11:25:31', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (12, 4, '3', '暂停', 'job_status', '暂停', 3, ' ', ' ', '2019-03-19 11:25:42', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (13, 5, '0', '正常', 'job_execute_status', '正常', 0, ' ', ' ', '2019-03-19 11:26:27', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (14, 5, '1', '异常', 'job_execute_status', '异常', 1, ' ', ' ', '2019-03-19 11:26:41', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (15, 6, '1', '错失周期立即执行', 'misfire_policy', '错失周期立即执行', 1, ' ', ' ', '2019-03-19 11:27:45', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (16, 6, '2', '错失周期执行一次', 'misfire_policy', '错失周期执行一次', 2, ' ', ' ', '2019-03-19 11:27:57', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (17, 6, '3', '下周期执行', 'misfire_policy', '下周期执行', 3, ' ', ' ', '2019-03-19 11:28:08', '2019-03-25 12:49:36', '', '0');\nINSERT INTO `sys_dict_item` VALUES (18, 7, '1', '男', 'gender', '微信-男', 0, ' ', ' ', '2019-03-27 13:45:13', '2019-03-27 13:45:13', '微信-男', '0');\nINSERT INTO `sys_dict_item` VALUES (19, 7, '2', '女', 'gender', '女-微信', 1, ' ', ' ', '2019-03-27 13:45:34', '2019-03-27 13:45:34', '女-微信', '0');\nINSERT INTO `sys_dict_item` VALUES (20, 7, '0', '未知', 'gender', 'x性别未知', 3, ' ', ' ', '2019-03-27 13:45:57', '2019-03-27 13:45:57', 'x性别未知', '0');\nINSERT INTO `sys_dict_item` VALUES (21, 8, '0', '未关注', 'subscribe', '公众号-未关注', 0, ' ', ' ', '2019-03-27 13:49:07', '2019-03-27 13:49:07', '公众号-未关注', '0');\nINSERT INTO `sys_dict_item` VALUES (22, 8, '1', '已关注', 'subscribe', '公众号-已关注', 1, ' ', ' ', '2019-03-27 13:49:26', '2019-03-27 13:49:26', '公众号-已关注', '0');\nINSERT INTO `sys_dict_item` VALUES (23, 9, '0', '未回复', 'response_type', '微信消息-未回复', 0, ' ', ' ', '2019-03-28 21:29:47', '2019-03-28 21:29:47', '微信消息-未回复', '0');\nINSERT INTO `sys_dict_item` VALUES (24, 9, '1', '已回复', 'response_type', '微信消息-已回复', 1, ' ', ' ', '2019-03-28 21:30:08', '2019-03-28 21:30:08', '微信消息-已回复', '0');\nINSERT INTO `sys_dict_item` VALUES (25, 10, '1', '检索', 'param_type', '检索', 0, ' ', ' ', '2019-04-29 18:22:17', '2019-04-29 18:22:17', '检索', '0');\nINSERT INTO `sys_dict_item` VALUES (26, 10, '2', '原文', 'param_type', '原文', 0, ' ', ' ', '2019-04-29 18:22:27', '2019-04-29 18:22:27', '原文', '0');\nINSERT INTO `sys_dict_item` VALUES (27, 10, '3', '报表', 'param_type', '报表', 0, ' ', ' ', '2019-04-29 18:22:36', '2019-04-29 18:22:36', '报表', '0');\nINSERT INTO `sys_dict_item` VALUES (28, 10, '4', '安全', 'param_type', '安全', 0, ' ', ' ', '2019-04-29 18:22:46', '2019-04-29 18:22:46', '安全', '0');\nINSERT INTO `sys_dict_item` VALUES (29, 10, '5', '文档', 'param_type', '文档', 0, ' ', ' ', '2019-04-29 18:22:56', '2019-04-29 18:22:56', '文档', '0');\nINSERT INTO `sys_dict_item` VALUES (30, 10, '6', '消息', 'param_type', '消息', 0, ' ', ' ', '2019-04-29 18:23:05', '2019-04-29 18:23:05', '消息', '0');\nINSERT INTO `sys_dict_item` VALUES (31, 10, '9', '其他', 'param_type', '其他', 0, ' ', ' ', '2019-04-29 18:23:16', '2019-04-29 18:23:16', '其他', '0');\nINSERT INTO `sys_dict_item` VALUES (32, 10, '0', '默认', 'param_type', '默认', 0, ' ', ' ', '2019-04-29 18:23:30', '2019-04-29 18:23:30', '默认', '0');\nINSERT INTO `sys_dict_item` VALUES (33, 11, '0', '正常', 'status_type', '状态正常', 0, ' ', ' ', '2019-05-15 16:31:34', '2019-05-16 22:30:46', '状态正常', '0');\nINSERT INTO `sys_dict_item` VALUES (34, 11, '9', '冻结', 'status_type', '状态冻结', 1, ' ', ' ', '2019-05-15 16:31:56', '2019-05-16 22:30:50', '状态冻结', '0');\nINSERT INTO `sys_dict_item` VALUES (35, 12, '1', '系统类', 'dict_type', '系统类字典', 0, ' ', ' ', '2019-05-16 14:20:40', '2019-05-16 14:20:40', '不能修改删除', '0');\nINSERT INTO `sys_dict_item` VALUES (36, 12, '0', '业务类', 'dict_type', '业务类字典', 0, ' ', ' ', '2019-05-16 14:20:59', '2019-05-16 14:20:59', '可以修改', '0');\nINSERT INTO `sys_dict_item` VALUES (37, 2, 'GITEE', '码云', 'social_type', '码云', 2, ' ', ' ', '2019-06-28 09:59:12', '2019-06-28 09:59:12', '码云', '0');\nINSERT INTO `sys_dict_item` VALUES (38, 2, 'OSC', '开源中国', 'social_type', '开源中国登录', 2, ' ', ' ', '2019-06-28 10:04:32', '2019-06-28 10:04:32', '', '0');\nINSERT INTO `sys_dict_item` VALUES (39, 14, 'password', '密码模式', 'grant_types', '支持oauth密码模式', 0, ' ', ' ', '2019-08-13 07:35:28', '2019-08-13 07:35:28', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (40, 14, 'authorization_code', '授权码模式', 'grant_types', 'oauth2 授权码模式', 1, ' ', ' ', '2019-08-13 07:36:07', '2019-08-13 07:36:07', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (41, 14, 'client_credentials', '客户端模式', 'grant_types', 'oauth2 客户端模式', 2, ' ', ' ', '2019-08-13 07:36:30', '2019-08-13 07:36:30', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (42, 14, 'refresh_token', '刷新模式', 'grant_types', 'oauth2 刷新token', 3, ' ', ' ', '2019-08-13 07:36:54', '2019-08-13 07:36:54', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (43, 14, 'implicit', '简化模式', 'grant_types', 'oauth2 简化模式', 4, ' ', ' ', '2019-08-13 07:39:32', '2019-08-13 07:39:32', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (44, 15, '0', 'Avue', 'style_type', 'Avue风格', 0, ' ', ' ', '2020-02-07 03:52:52', '2020-02-07 03:52:52', '', '0');\nINSERT INTO `sys_dict_item` VALUES (45, 15, '1', 'element', 'style_type', 'element-ui', 1, ' ', ' ', '2020-02-07 03:53:12', '2020-02-07 03:53:12', '', '0');\nINSERT INTO `sys_dict_item` VALUES (46, 16, '0', '关', 'captcha_flag_types', '不校验验证码', 0, ' ', ' ', '2020-11-18 06:53:58', '2020-11-18 06:53:58', '不校验验证码 -0', '0');\nINSERT INTO `sys_dict_item` VALUES (47, 16, '1', '开', 'captcha_flag_types', '校验验证码', 1, ' ', ' ', '2020-11-18 06:54:15', '2020-11-18 06:54:15', '不校验验证码-1', '0');\nINSERT INTO `sys_dict_item` VALUES (48, 17, '0', '否', 'enc_flag_types', '不加密', 0, ' ', ' ', '2020-11-18 06:55:31', '2020-11-18 06:55:31', '不加密-0', '0');\nINSERT INTO `sys_dict_item` VALUES (49, 17, '1', '是', 'enc_flag_types', '加密', 1, ' ', ' ', '2020-11-18 06:55:51', '2020-11-18 06:55:51', '加密-1', '0');\nINSERT INTO `sys_dict_item` VALUES (50, 13, 'MERGE_PAY', '聚合支付', 'channel_type', '聚合支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0');\nINSERT INTO `sys_dict_item` VALUES (51, 2, 'CAS', 'CAS登录', 'social_type', 'CAS 单点登录系统', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (52, 2, 'DINGTALK', '钉钉', 'social_type', '钉钉', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (53, 2, 'WEIXIN_CP', '企业微信', 'social_type', '企业微信', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (54, 15, '2', 'APP', 'style_type', 'uview风格', 1, ' ', ' ', '2020-02-07 03:53:12', '2020-02-07 03:53:12', '', '0');\nINSERT INTO `sys_dict_item` VALUES (55, 13, 'ALIPAY_WAP', '支付宝支付', 'channel_type', '支付宝支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0');\nINSERT INTO `sys_dict_item` VALUES (56, 13, 'WEIXIN_MP', '微信支付', 'channel_type', '微信支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0');\nINSERT INTO `sys_dict_item` VALUES (57, 14, 'mobile', 'mobile', 'grant_types', '移动端登录', 5, 'admin', ' ', '2023-01-29 17:21:42', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (58, 18, '0', '有效', 'lock_flag', '有效', 0, 'admin', ' ', '2023-02-01 16:56:00', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (59, 18, '9', '禁用', 'lock_flag', '禁用', 1, 'admin', ' ', '2023-02-01 16:56:09', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (60, 15, '4', 'vue3', 'style_type', 'element-plus', 4, 'admin', ' ', '2023-02-06 13:52:43', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (61, 19, '0', '主机', 'ds_config_type', '主机', 0, 'admin', ' ', '2023-02-06 18:37:23', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (62, 19, '1', 'JDBC', 'ds_config_type', 'jdbc', 2, 'admin', ' ', '2023-02-06 18:37:34', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (63, 20, 'false', '否', 'common_status', '否', 1, 'admin', ' ', '2023-02-09 11:02:39', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (64, 20, 'true', '是', 'common_status', '是', 2, 'admin', ' ', '2023-02-09 11:02:52', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (65, 21, 'MINI', '小程序', 'app_social_type', '小程序登录', 0, 'admin', ' ', '2023-02-10 11:11:41', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (66, 22, '0', '否', 'yes_no_type', '0', 0, 'admin', ' ', '2023-02-20 23:35:23', NULL, '0', '0');\nINSERT INTO `sys_dict_item` VALUES (67, 22, '1', '是', 'yes_no_type', '1', 0, 'admin', ' ', '2023-02-20 23:35:37', NULL, '1', '0');\nINSERT INTO `sys_dict_item` VALUES (69, 23, 'text', '文本', 'repType', '文本', 0, 'admin', ' ', '2023-02-24 15:08:45', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (70, 23, 'image', '图片', 'repType', '图片', 0, 'admin', ' ', '2023-02-24 15:08:56', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (71, 23, 'voice', '语音', 'repType', '语音', 0, 'admin', ' ', '2023-02-24 15:09:08', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (72, 23, 'video', '视频', 'repType', '视频', 0, 'admin', ' ', '2023-02-24 15:09:18', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (73, 23, 'shortvideo', '小视频', 'repType', '小视频', 0, 'admin', ' ', '2023-02-24 15:09:29', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (74, 23, 'location', '地理位置', 'repType', '地理位置', 0, 'admin', ' ', '2023-02-24 15:09:41', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (75, 23, 'link', '链接消息', 'repType', '链接消息', 0, 'admin', ' ', '2023-02-24 15:09:49', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (76, 23, 'event', '事件推送', 'repType', '事件推送', 0, 'admin', ' ', '2023-02-24 15:09:57', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (77, 24, '0', '未提交', 'leave_status', '未提交', 0, 'admin', ' ', '2023-03-02 22:50:45', NULL, '未提交', '0');\nINSERT INTO `sys_dict_item` VALUES (78, 24, '1', '审批中', 'leave_status', '审批中', 0, 'admin', ' ', '2023-03-02 22:50:57', NULL, '审批中', '0');\nINSERT INTO `sys_dict_item` VALUES (79, 24, '2', '完成', 'leave_status', '完成', 0, 'admin', ' ', '2023-03-02 22:51:06', NULL, '完成', '0');\nINSERT INTO `sys_dict_item` VALUES (80, 24, '9', '驳回', 'leave_status', '驳回', 0, 'admin', ' ', '2023-03-02 22:51:20', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (81, 25, 'record', '日程记录', 'schedule_type', '日程记录', 0, 'admin', ' ', '2023-03-06 14:50:01', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (82, 25, 'plan', '计划', 'schedule_type', '计划类型', 0, 'admin', ' ', '2023-03-06 14:50:29', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (83, 26, '0', '计划中', 'schedule_status', '日程状态', 0, 'admin', ' ', '2023-03-06 14:53:18', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (84, 26, '1', '已开始', 'schedule_status', '已开始', 0, 'admin', ' ', '2023-03-06 14:53:33', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (85, 26, '3', '已结束', 'schedule_status', '已结束', 0, 'admin', ' ', '2023-03-06 14:53:41', NULL, NULL, '0');\nINSERT INTO `sys_dict_item` VALUES (86, 27, 'mysql', 'mysql', 'ds_type', 'mysql', 0, 'admin', ' ', '2023-03-12 09:58:11', NULL, NULL, '0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_file\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_file`;\nCREATE TABLE `sys_file` (\n  `id` bigint NOT NULL COMMENT '编号',\n  `file_name` varchar(100)  DEFAULT NULL COMMENT '文件名',\n  `bucket_name` varchar(200)  DEFAULT NULL COMMENT '文件存储桶名称',\n  `original` varchar(100)  DEFAULT NULL COMMENT '原始文件名',\n  `type` varchar(50)  DEFAULT NULL COMMENT '文件类型',\n  `file_size` bigint DEFAULT NULL COMMENT '文件大小',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '上传时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='文件管理表';\n\n-- ----------------------------\n-- Records of sys_file\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_log`;\nCREATE TABLE `sys_log` (\n  `id` bigint NOT NULL COMMENT '编号',\n  `log_type` char(1)  DEFAULT '0' COMMENT '日志类型',\n  `title` varchar(255)  DEFAULT NULL COMMENT '日志标题',\n  `service_id` varchar(32)  DEFAULT NULL COMMENT '服务ID',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  `remote_addr` varchar(255)  DEFAULT NULL COMMENT '远程地址',\n  `user_agent` varchar(1000)  DEFAULT NULL COMMENT '用户代理',\n  `request_uri` varchar(255)  DEFAULT NULL COMMENT '请求URI',\n  `method` varchar(10)  DEFAULT NULL COMMENT '请求方法',\n  `params` text  COMMENT '请求参数',\n  `time` bigint DEFAULT NULL COMMENT '执行时间',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',\n  `exception` text  COMMENT '异常信息',\n  PRIMARY KEY (`id`) USING BTREE,\n  KEY `sys_log_request_uri` (`request_uri`) USING BTREE,\n  KEY `sys_log_type` (`log_type`) USING BTREE,\n  KEY `sys_log_create_date` (`create_time`) USING BTREE\n) ENGINE=InnoDB  COMMENT='日志表';\n\n\n-- ----------------------------\n-- Table structure for sys_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_menu`;\nCREATE TABLE `sys_menu` (\n  `menu_id` bigint NOT NULL COMMENT '菜单ID',\n  `name` varchar(32)  DEFAULT NULL COMMENT '菜单名称',\n  `en_name` varchar(128)  DEFAULT NULL COMMENT '英文名称',\n  `permission` varchar(32)  DEFAULT NULL COMMENT '权限标识',\n  `path` varchar(128)  DEFAULT NULL COMMENT '路由路径',\n  `parent_id` bigint DEFAULT NULL COMMENT '父菜单ID',\n  `icon` varchar(64)  DEFAULT NULL COMMENT '菜单图标',\n  `visible` char(1)  DEFAULT '1' COMMENT '是否可见，0隐藏，1显示',\n  `sort_order` int DEFAULT '1' COMMENT '排序值，越小越靠前',\n  `keep_alive` char(1)  DEFAULT '0' COMMENT '是否缓存，0否，1是',\n  `embedded` char(1)  DEFAULT NULL COMMENT '是否内嵌，0否，1是',\n  `menu_type` char(1)  DEFAULT '0' COMMENT '菜单类型，0目录，1菜单，2按钮',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志，0未删除，1已删除',\n  PRIMARY KEY (`menu_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='菜单权限表';\n\n-- ----------------------------\n-- Records of sys_menu\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_menu` VALUES (1000, '权限管理', 'authorization', NULL, '/admin', -1, 'iconfont icon-icon-', '1', 0, '0', '0', '0', '', '2018-09-28 08:29:53', 'admin', '2023-03-12 22:32:52', '0');\nINSERT INTO `sys_menu` VALUES (1100, '用户管理', 'user', NULL, '/admin/user/index', 1000, 'ele-User', '1', 1, '0', '0', '0', '', '2017-11-02 22:24:37', 'admin', '2023-07-05 10:28:22', '0');\nINSERT INTO `sys_menu` VALUES (1101, '用户新增', NULL, 'sys_user_add', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:52:09', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1102, '用户修改', NULL, 'sys_user_edit', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:52:48', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1103, '用户删除', NULL, 'sys_user_del', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1104, '导入导出', NULL, 'sys_user_export', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1200, '菜单管理', 'menu', NULL, '/admin/menu/index', 1000, 'iconfont icon-caidan', '1', 2, '0', '0', '0', '', '2017-11-08 09:57:27', 'admin', '2023-07-05 10:28:17', '0');\nINSERT INTO `sys_menu` VALUES (1201, '菜单新增', NULL, 'sys_menu_add', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:15:53', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1202, '菜单修改', NULL, 'sys_menu_edit', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:16:23', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1203, '菜单删除', NULL, 'sys_menu_del', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:16:43', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1300, '角色管理', 'role', NULL, '/admin/role/index', 1000, 'iconfont icon-gerenzhongxin', '1', 3, '0', NULL, '0', '', '2017-11-08 10:13:37', 'admin', '2023-07-05 10:28:13', '0');\nINSERT INTO `sys_menu` VALUES (1301, '角色新增', NULL, 'sys_role_add', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:18', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1302, '角色修改', NULL, 'sys_role_edit', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:41', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1303, '角色删除', NULL, 'sys_role_del', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:59', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1304, '分配权限', NULL, 'sys_role_perm', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2018-04-20 07:22:55', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1305, '角色导入导出', NULL, 'sys_role_export', NULL, 1300, NULL, '1', 4, '0', NULL, '1', ' ', '2022-03-26 15:54:34', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (1400, '部门管理', 'dept', NULL, '/admin/dept/index', 1000, 'iconfont icon-zidingyibuju', '1', 4, '0', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-07-05 10:28:07', '0');\nINSERT INTO `sys_menu` VALUES (1401, '部门新增', NULL, 'sys_dept_add', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:56:16', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1402, '部门修改', NULL, 'sys_dept_edit', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:56:59', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1403, '部门删除', NULL, 'sys_dept_del', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:57:28', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (1600, '岗位管理', 'post', NULL, '/admin/post/index', 1000, 'iconfont icon--chaifenhang', '1', 5, '1', '0', '0', '', '2022-03-26 13:04:14', 'admin', '2023-07-05 10:28:03', '0');\nINSERT INTO `sys_menu` VALUES (1601, '岗位信息查看', NULL, 'sys_post_view', NULL, 1600, NULL, '1', 0, '0', NULL, '1', ' ', '2022-03-26 13:05:34', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (1602, '岗位信息新增', NULL, 'sys_post_add', NULL, 1600, NULL, '1', 1, '0', NULL, '1', ' ', '2022-03-26 13:06:00', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (1603, '岗位信息修改', NULL, 'sys_post_edit', NULL, 1600, NULL, '1', 2, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', '2022-03-26 13:06:38', '0');\nINSERT INTO `sys_menu` VALUES (1604, '岗位信息删除', NULL, 'sys_post_del', NULL, 1600, NULL, '1', 3, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (1605, '岗位导入导出', NULL, 'sys_post_export', NULL, 1600, NULL, '1', 4, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', '2022-03-26 06:32:02', '0');\nINSERT INTO `sys_menu` VALUES (2000, '系统管理', 'system', NULL, '/system', -1, 'iconfont icon-quanjushezhi_o', '1', 1, '0', NULL, '0', '', '2017-11-07 20:56:00', 'admin', '2023-07-05 10:27:58', '0');\nINSERT INTO `sys_menu` VALUES (2001, '日志管理', 'log', NULL, '/admin/logs', 2000, 'ele-Cloudy', '1', 0, '0', '0', '0', 'admin', '2023-03-02 12:26:42', 'admin', '2023-07-05 10:27:53', '0');\nINSERT INTO `sys_menu` VALUES (2100, '操作日志', 'operation', NULL, '/admin/log/index', 2001, 'iconfont icon-jinridaiban', '1', 2, '0', '0', '0', '', '2017-11-20 14:06:22', 'admin', '2023-07-05 10:27:49', '0');\nINSERT INTO `sys_menu` VALUES (2101, '日志删除', NULL, 'sys_log_del', NULL, 2100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-20 20:37:37', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2102, '导入导出', NULL, 'sys_log_export', NULL, 2100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2200, '字典管理', 'dict', NULL, '/admin/dict/index', 2000, 'iconfont icon-zhongduancanshuchaxun', '1', 6, '0', NULL, '0', '', '2017-11-29 11:30:52', 'admin', '2023-07-05 10:27:37', '0');\nINSERT INTO `sys_menu` VALUES (2201, '字典删除', NULL, 'sys_dict_del', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-29 11:30:11', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2202, '字典新增', NULL, 'sys_dict_add', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:34:55', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2203, '字典修改', NULL, 'sys_dict_edit', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:36:03', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2210, '参数管理', 'parameter', NULL, '/admin/param/index', 2000, 'iconfont icon-wenducanshu-05', '1', 7, '1', NULL, '0', '', '2019-04-29 22:16:50', 'admin', '2023-02-16 15:24:51', '0');\nINSERT INTO `sys_menu` VALUES (2211, '参数新增', NULL, 'sys_syspublicparam_add', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:17:36', ' ', '2020-03-24 08:57:11', '0');\nINSERT INTO `sys_menu` VALUES (2212, '参数删除', NULL, 'sys_syspublicparam_del', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:17:55', ' ', '2020-03-24 08:57:12', '0');\nINSERT INTO `sys_menu` VALUES (2213, '参数编辑', NULL, 'sys_syspublicparam_edit', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:18:14', ' ', '2020-03-24 08:57:13', '0');\nINSERT INTO `sys_menu` VALUES (2300, '代码生成', 'code', NULL, '/gen/table/index', 9000, 'iconfont icon-zhongduancanshu', '1', 1, '0', '0', '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-20 13:54:35', '0');\nINSERT INTO `sys_menu` VALUES (2400, '终端管理', 'client', NULL, '/admin/client/index', 2000, 'iconfont icon-gongju', '1', 9, '1', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-16 15:25:28', '0');\nINSERT INTO `sys_menu` VALUES (2401, '客户端新增', NULL, 'sys_client_add', NULL, 2400, '1', '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2402, '客户端修改', NULL, 'sys_client_edit', NULL, 2400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:37:06', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2403, '客户端删除', NULL, 'sys_client_del', NULL, 2400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:39:16', ' ', '2021-05-25 03:12:55', '0');\nINSERT INTO `sys_menu` VALUES (2600, '令牌管理', 'token', NULL, '/admin/token/index', 2000, 'ele-Key', '1', 11, '0', NULL, '0', '', '2018-09-04 05:58:41', 'admin', '2023-02-16 15:28:28', '0');\nINSERT INTO `sys_menu` VALUES (2601, '令牌删除', NULL, 'sys_token_del', NULL, 2600, NULL, '1', 1, '0', NULL, '1', ' ', '2018-09-04 05:59:50', ' ', '2020-03-24 08:57:24', '0');\nINSERT INTO `sys_menu` VALUES (2800, 'Quartz管理', 'quartz', NULL, '/daemon/job-manage/index', 2000, 'ele-AlarmClock', '1', 8, '0', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-16 15:25:06', '0');\nINSERT INTO `sys_menu` VALUES (2810, '任务新增', NULL, 'job_sys_job_add', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:26', '0');\nINSERT INTO `sys_menu` VALUES (2820, '任务修改', NULL, 'job_sys_job_edit', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:27', '0');\nINSERT INTO `sys_menu` VALUES (2830, '任务删除', NULL, 'job_sys_job_del', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:28', '0');\nINSERT INTO `sys_menu` VALUES (2840, '任务暂停', NULL, 'job_sys_job_shutdown_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:28', '0');\nINSERT INTO `sys_menu` VALUES (2850, '任务开始', NULL, 'job_sys_job_start_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:29', '0');\nINSERT INTO `sys_menu` VALUES (2860, '任务刷新', NULL, 'job_sys_job_refresh_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:30', '0');\nINSERT INTO `sys_menu` VALUES (2870, '执行任务', NULL, 'job_sys_job_run_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2019-08-08 15:35:18', ' ', '2020-03-24 08:57:31', '0');\nINSERT INTO `sys_menu` VALUES (2871, '导出', NULL, 'job_sys_job_export', NULL, 2800, NULL, '1', 0, '0', '0', '1', 'admin', '2023-03-06 15:26:13', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (2906, '文件管理', 'file', NULL, '/admin/file/index', 2000, 'ele-Files', '1', 6, '0', NULL, '0', '', '2019-06-25 12:44:46', 'admin', '2023-02-16 15:24:42', '0');\nINSERT INTO `sys_menu` VALUES (2907, '删除文件', NULL, 'sys_file_del', NULL, 2906, NULL, '1', 1, '0', NULL, '1', ' ', '2019-06-25 13:41:41', ' ', '2020-03-24 08:58:42', '0');\nINSERT INTO `sys_menu` VALUES (4000, '系统监控', 'monitor', NULL, '/daemon', -1, 'iconfont icon-shuju', '1', 3, '0', '0', '0', 'admin', '2023-02-06 20:20:47', 'admin', '2023-02-23 20:01:07', '0');\nINSERT INTO `sys_menu` VALUES (4001, '文档扩展', 'doc', NULL, 'http://pig-gateway:9999/swagger-ui.html', 4000, 'iconfont icon-biaodan', '1', 2, '0', '1', '0', '', '2018-06-26 10:50:32', 'admin', '2023-02-23 20:01:29', '0');\nINSERT INTO `sys_menu` VALUES (4002, '缓存监控', 'cache', NULL, '/ext/cache', 4000, 'iconfont icon-shuju', '1', 1, '0', '0', '0', 'admin', '2023-05-29 15:12:59', 'admin', '2023-06-06 11:58:41', '0');\nINSERT INTO `sys_menu` VALUES (9000, '开发平台', 'develop', NULL, '/gen', -1, 'iconfont icon-shuxingtu', '1', 9, '0', '0', '0', '', '2019-08-12 09:35:16', 'admin', '2023-07-05 10:25:27', '0');\nINSERT INTO `sys_menu` VALUES (9005, '数据源管理', 'datasource', NULL, '/gen/datasource/index', 9000, 'ele-Coin', '1', 0, '0', NULL, '0', '', '2019-08-12 09:42:11', 'admin', '2023-07-05 10:26:56', '0');\nINSERT INTO `sys_menu` VALUES (9006, '表单设计', 'Form Design', NULL, '/gen/design/index', 9000, 'iconfont icon-AIshiyanshi', '0', 2, '0', '0', '0', '', '2019-08-16 10:08:56', 'admin', '2023-02-23 14:06:50', '0');\nINSERT INTO `sys_menu` VALUES (9007, '生成页面', 'generation', NULL, '/gen/gener/index', 9000, 'iconfont icon-tongzhi4', '0', 0, '0', '0', '0', 'admin', '2023-02-20 09:58:23', 'admin', '2023-07-05 10:27:06', '0');\nINSERT INTO `sys_menu` VALUES (9050, '元数据管理', 'metadata', NULL, '/gen/metadata', 9000, 'iconfont icon--chaifenhang', '1', 9, '0', '0', '0', '', '2018-07-27 01:13:21', 'admin', '2023-07-05 10:27:13', '0');\nINSERT INTO `sys_menu` VALUES (9051, '模板管理', 'template', NULL, '/gen/template/index', 9050, 'iconfont icon--chaifenhang', '1', 5, '0', '0', '0', 'admin', '2023-02-21 11:22:54', 'admin', '2023-07-05 10:27:18', '0');\nINSERT INTO `sys_menu` VALUES (9052, '查询', NULL, 'codegen_template_view', NULL, 9051, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 12:33:03', 'admin', '2023-02-21 13:50:54', '0');\nINSERT INTO `sys_menu` VALUES (9053, '增加', NULL, 'codegen_template_add', NULL, 9051, NULL, '1', 0, '0', '0', '1', 'admin', '2023-02-21 13:34:10', 'admin', '2023-02-21 13:39:49', '0');\nINSERT INTO `sys_menu` VALUES (9054, '新增', NULL, 'codegen_template_add', NULL, 9051, NULL, '0', 1, '0', '0', '1', 'admin', '2023-02-21 13:51:32', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9055, '导出', NULL, 'codegen_template_export', NULL, 9051, NULL, '0', 2, '0', '0', '1', 'admin', '2023-02-21 13:51:58', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9056, '删除', NULL, 'codegen_template_del', NULL, 9051, NULL, '0', 3, '0', '0', '1', 'admin', '2023-02-21 13:52:16', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9057, '编辑', NULL, 'codegen_template_edit', NULL, 9051, NULL, '0', 4, '0', '0', '1', 'admin', '2023-02-21 13:52:58', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9059, '模板分组', 'group', NULL, '/gen/group/index', 9050, 'iconfont icon-shuxingtu', '1', 6, '0', '0', '0', 'admin', '2023-02-21 15:06:50', 'admin', '2023-07-05 10:27:22', '0');\nINSERT INTO `sys_menu` VALUES (9060, '查询', NULL, 'codegen_group_view', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:07', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9061, '新增', NULL, 'codegen_group_add', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:28', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9062, '修改', NULL, 'codegen_group_edit', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:43', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9063, '删除', NULL, 'codegen_group_del', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:09:02', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9064, '导出', NULL, 'codegen_group_export', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:09:22', ' ', NULL, '0');\nINSERT INTO `sys_menu` VALUES (9065, '字段管理', 'field', NULL, '/gen/field-type/index', 9050, 'iconfont icon-fuwenben', '1', 0, '0', '0', '0', 'admin', '2023-02-23 20:05:09', 'admin', '2023-07-05 10:27:31', '0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_oauth_client_details\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_oauth_client_details`;\nCREATE TABLE `sys_oauth_client_details` (\n  `id` bigint NOT NULL COMMENT 'ID',\n  `client_id` varchar(32)  NOT NULL COMMENT '客户端ID',\n  `resource_ids` varchar(256)  DEFAULT NULL COMMENT '资源ID集合',\n  `client_secret` varchar(256)  DEFAULT NULL COMMENT '客户端秘钥',\n  `scope` varchar(256)  DEFAULT NULL COMMENT '授权范围',\n  `authorized_grant_types` varchar(256)  DEFAULT NULL COMMENT '授权类型',\n  `web_server_redirect_uri` varchar(256)  DEFAULT NULL COMMENT '回调地址',\n  `authorities` varchar(256)  DEFAULT NULL COMMENT '权限集合',\n  `access_token_validity` int DEFAULT NULL COMMENT '访问令牌有效期（秒）',\n  `refresh_token_validity` int DEFAULT NULL COMMENT '刷新令牌有效期（秒）',\n  `additional_information` varchar(4096)  DEFAULT NULL COMMENT '附加信息',\n  `autoapprove` varchar(256)  DEFAULT NULL COMMENT '自动授权',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记，0未删除，1已删除',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='终端信息表';\n\n-- ----------------------------\n-- Records of sys_oauth_client_details\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_oauth_client_details` VALUES (1, 'app', NULL, 'app', 'server', 'password,refresh_token,authorization_code,client_credentials,mobile', 'http://localhost:4040/sso1/login,http://localhost:4041/sso1/login,http://localhost:8080/renren-admin/sys/oauth2-sso,http://localhost:8090/sys/oauth2-sso', NULL, 43200, 2592001, '{\\\"enc_flag\\\":\\\"1\\\",\\\"captcha_flag\\\":\\\"1\\\",\\\"online_quantity\\\":\\\"1\\\"}', 'true', '0', '', 'admin', NULL, '2023-02-09 13:54:54');\nINSERT INTO `sys_oauth_client_details` VALUES (2, 'daemon', NULL, 'daemon', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\\\"enc_flag\\\":\\\"1\\\",\\\"captcha_flag\\\":\\\"1\\\"}', 'true', '0', ' ', ' ', NULL, NULL);\nINSERT INTO `sys_oauth_client_details` VALUES (3, 'gen', NULL, 'gen', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\\\"enc_flag\\\":\\\"1\\\",\\\"captcha_flag\\\":\\\"1\\\"}', 'true', '0', ' ', ' ', NULL, NULL);\nINSERT INTO `sys_oauth_client_details` VALUES (4, 'mp', NULL, 'mp', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\\\"enc_flag\\\":\\\"1\\\",\\\"captcha_flag\\\":\\\"1\\\"}', 'true', '0', ' ', ' ', NULL, NULL);\nINSERT INTO `sys_oauth_client_details` VALUES (5, 'pig', NULL, 'pig', 'server', 'password,refresh_token,authorization_code,client_credentials,mobile', 'http://localhost:4040/sso1/login,http://localhost:4041/sso1/login,http://localhost:8080/renren-admin/sys/oauth2-sso,http://localhost:8090/sys/oauth2-sso', NULL, 43200, 2592001, '{\\\"enc_flag\\\":\\\"1\\\",\\\"captcha_flag\\\":\\\"1\\\",\\\"online_quantity\\\":\\\"1\\\"}', 'false', '0', '', 'admin', NULL, '2023-03-08 11:32:41');\nINSERT INTO `sys_oauth_client_details` VALUES (6, 'test', NULL, 'test', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{ \\\"enc_flag\\\":\\\"1\\\",\\\"captcha_flag\\\":\\\"0\\\"}', 'true', '0', ' ', ' ', NULL, NULL);\nINSERT INTO `sys_oauth_client_details` VALUES (7, 'social', NULL, 'social', 'server', 'password,refresh_token,mobile', NULL, NULL, 43200, 2592001, '{ \\\"enc_flag\\\":\\\"0\\\",\\\"captcha_flag\\\":\\\"0\\\"}', 'true', '0', ' ', ' ', NULL, NULL);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_post\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_post`;\nCREATE TABLE `sys_post` (\n  `post_id` bigint NOT NULL COMMENT '岗位ID',\n  `post_code` varchar(64)  NOT NULL COMMENT '岗位编码',\n  `post_name` varchar(50)  NOT NULL COMMENT '岗位名称',\n  `post_sort` int NOT NULL COMMENT '岗位排序',\n  `remark` varchar(500)  DEFAULT NULL COMMENT '岗位描述',\n  `del_flag` char(1)  NOT NULL DEFAULT '0' COMMENT '是否删除  -1：已删除  0：正常',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '更新人',\n  PRIMARY KEY (`post_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='岗位信息表';\n\n-- ----------------------------\n-- Records of sys_post\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_post` VALUES (1, 'CTO', 'CTO', 0, 'CTOOO', '0', '2022-03-26 13:48:17', '', '2023-03-08 16:03:35', 'admin');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_public_param\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_public_param`;\nCREATE TABLE `sys_public_param` (\n  `public_id` bigint NOT NULL COMMENT '编号',\n  `public_name` varchar(128)  DEFAULT NULL COMMENT '名称',\n  `public_key` varchar(128)  DEFAULT NULL COMMENT '键',\n  `public_value` varchar(128)  DEFAULT NULL COMMENT '值',\n  `status` char(1)  DEFAULT '0' COMMENT '状态，0禁用，1启用',\n  `validate_code` varchar(64)  DEFAULT NULL COMMENT '校验码',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n  `public_type` char(1)  DEFAULT '0' COMMENT '类型，0未知，1系统，2业务',\n  `system_flag` char(1)  DEFAULT '0' COMMENT '系统标识，0非系统，1系统',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记，0未删除，1已删除',\n  PRIMARY KEY (`public_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='公共参数配置表';\n\n-- ----------------------------\n-- Records of sys_public_param\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_public_param` VALUES (1, '租户默认来源', 'TENANT_DEFAULT_ID', '1', '0', '', ' ', ' ', '2020-05-12 04:03:46', '2020-06-20 08:56:30', '2', '0', '1');\nINSERT INTO `sys_public_param` VALUES (2, '租户默认部门名称', 'TENANT_DEFAULT_DEPTNAME', '租户默认部门', '0', '', ' ', ' ', '2020-05-12 03:36:32', NULL, '2', '1', '0');\nINSERT INTO `sys_public_param` VALUES (3, '租户默认账户', 'TENANT_DEFAULT_USERNAME', 'admin', '0', '', ' ', ' ', '2020-05-12 04:05:04', NULL, '2', '1', '0');\nINSERT INTO `sys_public_param` VALUES (4, '租户默认密码', 'TENANT_DEFAULT_PASSWORD', '123456', '0', '', ' ', ' ', '2020-05-12 04:05:24', NULL, '2', '1', '0');\nINSERT INTO `sys_public_param` VALUES (5, '租户默认角色编码', 'TENANT_DEFAULT_ROLECODE', 'ROLE_ADMIN', '0', '', ' ', ' ', '2020-05-12 04:05:57', NULL, '2', '1', '0');\nINSERT INTO `sys_public_param` VALUES (6, '租户默认角色名称', 'TENANT_DEFAULT_ROLENAME', '租户默认角色', '0', '', ' ', ' ', '2020-05-12 04:06:19', NULL, '2', '1', '0');\nINSERT INTO `sys_public_param` VALUES (7, '表前缀', 'GEN_TABLE_PREFIX', 'tb_', '0', '', ' ', ' ', '2020-05-12 04:23:04', NULL, '9', '1', '0');\nINSERT INTO `sys_public_param` VALUES (8, '接口文档不显示的字段', 'GEN_HIDDEN_COLUMNS', 'tenant_id', '0', '', ' ', ' ', '2020-05-12 04:25:19', NULL, '9', '1', '0');\nINSERT INTO `sys_public_param` VALUES (9, '注册用户默认角色', 'USER_DEFAULT_ROLE', 'GENERAL_USER', '0', NULL, ' ', ' ', '2022-03-31 16:52:24', NULL, '2', '1', '0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_role`;\nCREATE TABLE `sys_role` (\n  `role_id` bigint NOT NULL COMMENT '角色ID',\n  `role_name` varchar(64)  DEFAULT NULL COMMENT '角色名称',\n  `role_code` varchar(64)  DEFAULT NULL COMMENT '角色编码',\n  `role_desc` varchar(255)  DEFAULT NULL COMMENT '角色描述',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记，0未删除，1已删除',\n  PRIMARY KEY (`role_id`) USING BTREE,\n  KEY `role_idx1_role_code` (`role_code`) USING BTREE\n) ENGINE=InnoDB  COMMENT='系统角色表';\n\n-- ----------------------------\n-- Records of sys_role\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_role` VALUES (1, '管理员', 'ROLE_ADMIN', '管理员', '', 'admin', '2017-10-29 15:45:51', '2023-07-07 14:55:07', '0');\nINSERT INTO `sys_role` VALUES (2, '普通用户', 'GENERAL_USER', '普通用户', '', 'admin', '2022-03-31 17:03:15', '2023-04-03 02:28:51', '0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_role_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_role_menu`;\nCREATE TABLE `sys_role_menu` (\n  `role_id` bigint NOT NULL COMMENT '角色ID',\n  `menu_id` bigint NOT NULL COMMENT '菜单ID',\n  PRIMARY KEY (`role_id`,`menu_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='角色菜单表';\n\n-- ----------------------------\n-- Records of sys_role_menu\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_role_menu` VALUES (1, 1000);\nINSERT INTO `sys_role_menu` VALUES (1, 1100);\nINSERT INTO `sys_role_menu` VALUES (1, 1101);\nINSERT INTO `sys_role_menu` VALUES (1, 1102);\nINSERT INTO `sys_role_menu` VALUES (1, 1103);\nINSERT INTO `sys_role_menu` VALUES (1, 1104);\nINSERT INTO `sys_role_menu` VALUES (1, 1200);\nINSERT INTO `sys_role_menu` VALUES (1, 1201);\nINSERT INTO `sys_role_menu` VALUES (1, 1202);\nINSERT INTO `sys_role_menu` VALUES (1, 1203);\nINSERT INTO `sys_role_menu` VALUES (1, 1300);\nINSERT INTO `sys_role_menu` VALUES (1, 1301);\nINSERT INTO `sys_role_menu` VALUES (1, 1302);\nINSERT INTO `sys_role_menu` VALUES (1, 1303);\nINSERT INTO `sys_role_menu` VALUES (1, 1304);\nINSERT INTO `sys_role_menu` VALUES (1, 1305);\nINSERT INTO `sys_role_menu` VALUES (1, 1400);\nINSERT INTO `sys_role_menu` VALUES (1, 1401);\nINSERT INTO `sys_role_menu` VALUES (1, 1402);\nINSERT INTO `sys_role_menu` VALUES (1, 1403);\nINSERT INTO `sys_role_menu` VALUES (1, 1600);\nINSERT INTO `sys_role_menu` VALUES (1, 1601);\nINSERT INTO `sys_role_menu` VALUES (1, 1602);\nINSERT INTO `sys_role_menu` VALUES (1, 1603);\nINSERT INTO `sys_role_menu` VALUES (1, 1604);\nINSERT INTO `sys_role_menu` VALUES (1, 1605);\nINSERT INTO `sys_role_menu` VALUES (1, 2000);\nINSERT INTO `sys_role_menu` VALUES (1, 2001);\nINSERT INTO `sys_role_menu` VALUES (1, 2100);\nINSERT INTO `sys_role_menu` VALUES (1, 2101);\nINSERT INTO `sys_role_menu` VALUES (1, 2102);\nINSERT INTO `sys_role_menu` VALUES (1, 2200);\nINSERT INTO `sys_role_menu` VALUES (1, 2201);\nINSERT INTO `sys_role_menu` VALUES (1, 2202);\nINSERT INTO `sys_role_menu` VALUES (1, 2203);\nINSERT INTO `sys_role_menu` VALUES (1, 2210);\nINSERT INTO `sys_role_menu` VALUES (1, 2211);\nINSERT INTO `sys_role_menu` VALUES (1, 2212);\nINSERT INTO `sys_role_menu` VALUES (1, 2213);\nINSERT INTO `sys_role_menu` VALUES (1, 2300);\nINSERT INTO `sys_role_menu` VALUES (1, 2400);\nINSERT INTO `sys_role_menu` VALUES (1, 2401);\nINSERT INTO `sys_role_menu` VALUES (1, 2402);\nINSERT INTO `sys_role_menu` VALUES (1, 2403);\nINSERT INTO `sys_role_menu` VALUES (1, 2600);\nINSERT INTO `sys_role_menu` VALUES (1, 2601);\nINSERT INTO `sys_role_menu` VALUES (1, 2800);\nINSERT INTO `sys_role_menu` VALUES (1, 2810);\nINSERT INTO `sys_role_menu` VALUES (1, 2820);\nINSERT INTO `sys_role_menu` VALUES (1, 2830);\nINSERT INTO `sys_role_menu` VALUES (1, 2840);\nINSERT INTO `sys_role_menu` VALUES (1, 2850);\nINSERT INTO `sys_role_menu` VALUES (1, 2860);\nINSERT INTO `sys_role_menu` VALUES (1, 2870);\nINSERT INTO `sys_role_menu` VALUES (1, 2871);\nINSERT INTO `sys_role_menu` VALUES (1, 2906);\nINSERT INTO `sys_role_menu` VALUES (1, 2907);\nINSERT INTO `sys_role_menu` VALUES (1, 4000);\nINSERT INTO `sys_role_menu` VALUES (1, 4001);\nINSERT INTO `sys_role_menu` VALUES (1, 4002);\nINSERT INTO `sys_role_menu` VALUES (1, 9000);\nINSERT INTO `sys_role_menu` VALUES (1, 9005);\nINSERT INTO `sys_role_menu` VALUES (1, 9006);\nINSERT INTO `sys_role_menu` VALUES (1, 9007);\nINSERT INTO `sys_role_menu` VALUES (1, 9050);\nINSERT INTO `sys_role_menu` VALUES (1, 9051);\nINSERT INTO `sys_role_menu` VALUES (1, 9052);\nINSERT INTO `sys_role_menu` VALUES (1, 9053);\nINSERT INTO `sys_role_menu` VALUES (1, 9054);\nINSERT INTO `sys_role_menu` VALUES (1, 9055);\nINSERT INTO `sys_role_menu` VALUES (1, 9056);\nINSERT INTO `sys_role_menu` VALUES (1, 9057);\nINSERT INTO `sys_role_menu` VALUES (1, 9059);\nINSERT INTO `sys_role_menu` VALUES (1, 9060);\nINSERT INTO `sys_role_menu` VALUES (1, 9061);\nINSERT INTO `sys_role_menu` VALUES (1, 9062);\nINSERT INTO `sys_role_menu` VALUES (1, 9063);\nINSERT INTO `sys_role_menu` VALUES (1, 9064);\nINSERT INTO `sys_role_menu` VALUES (1, 9065);\nINSERT INTO `sys_role_menu` VALUES (2, 4000);\nINSERT INTO `sys_role_menu` VALUES (2, 4001);\nINSERT INTO `sys_role_menu` VALUES (2, 4002);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_user\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user`;\nCREATE TABLE `sys_user` (\n  `user_id` bigint NOT NULL COMMENT '用户ID',\n  `username` varchar(64)  DEFAULT NULL COMMENT '用户名',\n  `password` varchar(255)  DEFAULT NULL COMMENT '密码',\n  `salt` varchar(255)  DEFAULT NULL COMMENT '盐值',\n  `phone` varchar(20)  DEFAULT NULL COMMENT '电话号码',\n  `avatar` varchar(255)  DEFAULT NULL COMMENT '头像',\n  `nickname` varchar(64)  DEFAULT NULL COMMENT '昵称',\n  `name` varchar(64)  DEFAULT NULL COMMENT '姓名',\n  `email` varchar(128)  DEFAULT NULL COMMENT '邮箱地址',\n  `dept_id` bigint DEFAULT NULL COMMENT '所属部门ID',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',\n  `lock_flag` char(1)  DEFAULT '0' COMMENT '锁定标记，0未锁定，9已锁定',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记，0未删除，1已删除',\n  `wx_openid` varchar(32)  DEFAULT NULL COMMENT '微信登录openId',\n  `mini_openid` varchar(32)  DEFAULT NULL COMMENT '小程序openId',\n  `qq_openid` varchar(32)  DEFAULT NULL COMMENT 'QQ openId',\n  `gitee_login` varchar(100)  DEFAULT NULL COMMENT '码云标识',\n  `osc_id` varchar(100)  DEFAULT NULL COMMENT '开源中国标识',\n  PRIMARY KEY (`user_id`) USING BTREE,\n  KEY `user_wx_openid` (`wx_openid`) USING BTREE,\n  KEY `user_qq_openid` (`qq_openid`) USING BTREE,\n  KEY `user_idx1_username` (`username`) USING BTREE\n) ENGINE=InnoDB  COMMENT='用户表';\n\n-- ----------------------------\n-- Records of sys_user\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$c/Ae0pRjJtMZg3BnvVpO.eIK6WYWVbKTzqgdy3afR7w.vd.xi3Mgy', '', '17034642999', '/admin/sys-file/s3demo/7ff4ca6b7bf446f3a5a13ac016dc21af.png', '管理员', '管理员', 'pig4cloud@qq.com', 4, ' ', 'admin', '2018-04-20 07:15:18', '2023-07-07 14:55:40', '0', '0', NULL, 'oBxPy5E-v82xWGsfzZVzkD3wEX64', NULL, 'log4j', NULL);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_user_post\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user_post`;\nCREATE TABLE `sys_user_post` (\n  `user_id` bigint NOT NULL COMMENT '用户ID',\n  `post_id` bigint NOT NULL COMMENT '岗位ID',\n  PRIMARY KEY (`user_id`,`post_id`) USING BTREE\n) ENGINE=InnoDB  ROW_FORMAT=DYNAMIC COMMENT='用户与岗位关联表';\n\n-- ----------------------------\n-- Records of sys_user_post\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_user_post` VALUES (1, 1);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_user_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user_role`;\nCREATE TABLE `sys_user_role` (\n  `user_id` bigint NOT NULL COMMENT '用户ID',\n  `role_id` bigint NOT NULL COMMENT '角色ID',\n  PRIMARY KEY (`user_id`,`role_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='用户角色表';\n\n-- ----------------------------\n-- Records of sys_user_role\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_user_role` VALUES (1, 1);\nINSERT INTO `sys_user_role` VALUES (1676492190299299842, 2);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_job\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_job`;\nCREATE TABLE `sys_job` (\n                           `job_id` bigint NOT NULL COMMENT '任务id',\n                           `job_name` varchar(64) NOT NULL COMMENT '任务名称',\n                           `job_group` varchar(64) NOT NULL COMMENT '任务组名',\n                           `job_order` char(1) DEFAULT '1' COMMENT '组内执行顺利，值越大执行优先级越高，最大值9，最小值1',\n                           `job_type` char(1) NOT NULL DEFAULT '1' COMMENT '1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他',\n                           `execute_path` varchar(500) DEFAULT NULL COMMENT 'job_type=3时，rest调用地址，仅支持rest get协议,需要增加String返回值，0成功，1失败;job_type=4时，jar路径;其它值为空',\n                           `class_name` varchar(500) DEFAULT NULL COMMENT 'job_type=1时，类完整路径;job_type=2时，spring bean名称;其它值为空',\n                           `method_name` varchar(500) DEFAULT NULL COMMENT '任务方法',\n                           `method_params_value` varchar(2000) DEFAULT NULL COMMENT '参数值',\n                           `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron执行表达式',\n                           `misfire_policy` varchar(20) DEFAULT '3' COMMENT '错失执行策略（1错失周期立即执行 2错失周期执行一次 3下周期执行）',\n                           `job_tenant_type` char(1) DEFAULT '1' COMMENT '1、多租户任务;2、非多租户任务',\n                           `job_status` char(1) DEFAULT '0' COMMENT '状态（1、未发布;2、运行中;3、暂停;4、删除;）',\n                           `job_execute_status` char(1) DEFAULT '0' COMMENT '状态（0正常 1异常）',\n                           `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',\n                           `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n                           `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',\n                           `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n                           `start_time` timestamp NULL DEFAULT NULL COMMENT '初次执行时间',\n                           `previous_time` timestamp NULL DEFAULT NULL COMMENT '上次执行时间',\n                           `next_time` timestamp NULL DEFAULT NULL COMMENT '下次执行时间',\n                           `remark` varchar(500) DEFAULT '' COMMENT '备注信息',\n                           PRIMARY KEY (`job_id`) USING BTREE,\n                           UNIQUE KEY `job_name_group_idx` (`job_name`,`job_group`) USING BTREE\n) ENGINE=InnoDB  COMMENT='定时任务调度表';\n\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_job_log`;\nCREATE TABLE `sys_job_log` (\n                               `job_log_id` bigint NOT NULL COMMENT '任务日志ID',\n                               `job_id` bigint NOT NULL COMMENT '任务id',\n                               `job_name` varchar(64)  DEFAULT NULL COMMENT '任务名称',\n                               `job_group` varchar(64)  DEFAULT NULL COMMENT '任务组名',\n                               `job_order` char(1)  DEFAULT NULL COMMENT '组内执行顺利，值越大执行优先级越高，最大值9，最小值1',\n                               `job_type` char(1)  NOT NULL DEFAULT '1' COMMENT '1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他',\n                               `execute_path` varchar(500)  DEFAULT NULL COMMENT 'job_type=3时，rest调用地址，仅支持post协议;job_type=4时，jar路径;其它值为空',\n                               `class_name` varchar(500)  DEFAULT NULL COMMENT 'job_type=1时，类完整路径;job_type=2时，spring bean名称;其它值为空',\n                               `method_name` varchar(500)  DEFAULT NULL COMMENT '任务方法',\n                               `method_params_value` varchar(2000)  DEFAULT NULL COMMENT '参数值',\n                               `cron_expression` varchar(255)  DEFAULT NULL COMMENT 'cron执行表达式',\n                               `job_message` varchar(500)  DEFAULT NULL COMMENT '日志信息',\n                               `job_log_status` char(1)  DEFAULT '0' COMMENT '执行状态（0正常 1失败）',\n                               `execute_time` varchar(30)  DEFAULT NULL COMMENT '执行时间',\n                               `exception_info` varchar(2000)  DEFAULT '' COMMENT '异常信息',\n                               `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n                               PRIMARY KEY (`job_log_id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='定时任务执行日志表';\n\n\n#\n# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar\n#\n# PLEASE consider using mysql with innodb tables to avoid locking issues\n#\n# In your Quartz properties file, you'll need to set\n# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate\n#\n\nDROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;\nDROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;\nDROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;\nDROP TABLE IF EXISTS QRTZ_LOCKS;\nDROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;\nDROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;\nDROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;\nDROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;\nDROP TABLE IF EXISTS QRTZ_TRIGGERS;\nDROP TABLE IF EXISTS QRTZ_JOB_DETAILS;\nDROP TABLE IF EXISTS QRTZ_CALENDARS;\n\n\nCREATE TABLE QRTZ_JOB_DETAILS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    JOB_NAME  VARCHAR(200) NOT NULL,\n    JOB_GROUP VARCHAR(200) NOT NULL,\n    DESCRIPTION VARCHAR(250) NULL,\n    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,\n    IS_DURABLE VARCHAR(1) NOT NULL,\n    IS_NONCONCURRENT VARCHAR(1) NOT NULL,\n    IS_UPDATE_DATA VARCHAR(1) NOT NULL,\n    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,\n    JOB_DATA BLOB NULL,\n    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)\n);\n\nCREATE TABLE QRTZ_TRIGGERS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    TRIGGER_NAME VARCHAR(200) NOT NULL,\n    TRIGGER_GROUP VARCHAR(200) NOT NULL,\n    JOB_NAME  VARCHAR(200) NOT NULL,\n    JOB_GROUP VARCHAR(200) NOT NULL,\n    DESCRIPTION VARCHAR(250) NULL,\n    NEXT_FIRE_TIME BIGINT(13) NULL,\n    PREV_FIRE_TIME BIGINT(13) NULL,\n    PRIORITY INTEGER NULL,\n    TRIGGER_STATE VARCHAR(16) NOT NULL,\n    TRIGGER_TYPE VARCHAR(8) NOT NULL,\n    START_TIME BIGINT(13) NOT NULL,\n    END_TIME BIGINT(13) NULL,\n    CALENDAR_NAME VARCHAR(200) NULL,\n    MISFIRE_INSTR SMALLINT(2) NULL,\n    JOB_DATA BLOB NULL,\n    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),\n    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)\n        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)\n);\n\nCREATE TABLE QRTZ_SIMPLE_TRIGGERS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    TRIGGER_NAME VARCHAR(200) NOT NULL,\n    TRIGGER_GROUP VARCHAR(200) NOT NULL,\n    REPEAT_COUNT BIGINT(7) NOT NULL,\n    REPEAT_INTERVAL BIGINT(12) NOT NULL,\n    TIMES_TRIGGERED BIGINT(10) NOT NULL,\n    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),\n    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n);\n\nCREATE TABLE QRTZ_CRON_TRIGGERS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    TRIGGER_NAME VARCHAR(200) NOT NULL,\n    TRIGGER_GROUP VARCHAR(200) NOT NULL,\n    CRON_EXPRESSION VARCHAR(200) NOT NULL,\n    TIME_ZONE_ID VARCHAR(80),\n    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),\n    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n);\n\nCREATE TABLE QRTZ_SIMPROP_TRIGGERS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    TRIGGER_NAME VARCHAR(200) NOT NULL,\n    TRIGGER_GROUP VARCHAR(200) NOT NULL,\n    STR_PROP_1 VARCHAR(512) NULL,\n    STR_PROP_2 VARCHAR(512) NULL,\n    STR_PROP_3 VARCHAR(512) NULL,\n    INT_PROP_1 INT NULL,\n    INT_PROP_2 INT NULL,\n    LONG_PROP_1 BIGINT NULL,\n    LONG_PROP_2 BIGINT NULL,\n    DEC_PROP_1 NUMERIC(13,4) NULL,\n    DEC_PROP_2 NUMERIC(13,4) NULL,\n    BOOL_PROP_1 VARCHAR(1) NULL,\n    BOOL_PROP_2 VARCHAR(1) NULL,\n    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),\n    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n);\n\nCREATE TABLE QRTZ_BLOB_TRIGGERS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    TRIGGER_NAME VARCHAR(200) NOT NULL,\n    TRIGGER_GROUP VARCHAR(200) NOT NULL,\n    BLOB_DATA BLOB NULL,\n    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),\n    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)\n);\n\nCREATE TABLE QRTZ_CALENDARS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    CALENDAR_NAME  VARCHAR(200) NOT NULL,\n    CALENDAR BLOB NOT NULL,\n    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)\n);\n\nCREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    TRIGGER_GROUP  VARCHAR(200) NOT NULL,\n    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)\n);\n\nCREATE TABLE QRTZ_FIRED_TRIGGERS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    ENTRY_ID VARCHAR(95) NOT NULL,\n    TRIGGER_NAME VARCHAR(200) NOT NULL,\n    TRIGGER_GROUP VARCHAR(200) NOT NULL,\n    INSTANCE_NAME VARCHAR(200) NOT NULL,\n    FIRED_TIME BIGINT(13) NOT NULL,\n    SCHED_TIME BIGINT(13) NOT NULL,\n    PRIORITY INTEGER NOT NULL,\n    STATE VARCHAR(16) NOT NULL,\n    JOB_NAME VARCHAR(200) NULL,\n    JOB_GROUP VARCHAR(200) NULL,\n    IS_NONCONCURRENT VARCHAR(1) NULL,\n    REQUESTS_RECOVERY VARCHAR(1) NULL,\n    PRIMARY KEY (SCHED_NAME,ENTRY_ID)\n);\n\nCREATE TABLE QRTZ_SCHEDULER_STATE\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    INSTANCE_NAME VARCHAR(200) NOT NULL,\n    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,\n    CHECKIN_INTERVAL BIGINT(13) NOT NULL,\n    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)\n);\n\nCREATE TABLE QRTZ_LOCKS\n  (\n    SCHED_NAME VARCHAR(120) NOT NULL,\n    LOCK_NAME  VARCHAR(40) NOT NULL,\n    PRIMARY KEY (SCHED_NAME,LOCK_NAME)\n);\n\n-- ----------------------------\n-- Table structure for gen_datasource_conf\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_datasource_conf`;\nCREATE TABLE `gen_datasource_conf` (\n  `id` bigint NOT NULL COMMENT '主键',\n  `name` varchar(64)  DEFAULT NULL COMMENT '别名',\n  `url` varchar(255)  DEFAULT NULL COMMENT 'jdbcurl',\n  `username` varchar(64)  DEFAULT NULL COMMENT '用户名',\n  `password` varchar(64)  DEFAULT NULL COMMENT '密码',\n  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记',\n  `ds_type` varchar(64)  DEFAULT NULL COMMENT '数据库类型',\n  `conf_type` char(1)  DEFAULT NULL COMMENT '配置类型',\n  `ds_name` varchar(64)  DEFAULT NULL COMMENT '数据库名称',\n  `instance` varchar(64)  DEFAULT NULL COMMENT '实例',\n  `port` int DEFAULT NULL COMMENT '端口',\n  `host` varchar(128)  DEFAULT NULL COMMENT '主机',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE=InnoDB  COMMENT='数据源表';\n\n-- ----------------------------\n-- Records of gen_datasource_conf\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for gen_field_type\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_field_type`;\nCREATE TABLE `gen_field_type` (\n  `id` bigint NOT NULL COMMENT '主键',\n  `column_type` varchar(200)  DEFAULT NULL COMMENT '字段类型',\n  `attr_type` varchar(200)  DEFAULT NULL COMMENT '属性类型',\n  `package_name` varchar(200)  DEFAULT NULL COMMENT '属性包名',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `create_by` varchar(64)  DEFAULT NULL COMMENT '创建人',\n  `update_time` datetime DEFAULT NULL COMMENT '修改时间',\n  `update_by` varchar(64)  DEFAULT NULL COMMENT '修改人',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `column_type` (`column_type`)\n) ENGINE=InnoDB AUTO_INCREMENT=1634915190321451010  COMMENT='字段类型管理';\n\n-- ----------------------------\n-- Records of gen_field_type\n-- ----------------------------\nBEGIN;\nINSERT INTO `gen_field_type` VALUES (1, 'datetime', 'LocalDateTime', 'java.time.LocalDateTime', '2023-02-06 08:45:10', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (2, 'date', 'LocalDate', 'java.time.LocalDate', '2023-02-06 08:45:10', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (3, 'tinyint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (4, 'smallint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (5, 'mediumint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (6, 'int', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (7, 'integer', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (8, 'bigint', 'Long', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (9, 'float', 'Float', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (10, 'double', 'Double', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (11, 'decimal', 'BigDecimal', 'java.math.BigDecimal', '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (12, 'bit', 'Boolean', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (13, 'char', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (14, 'varchar', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (15, 'tinytext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (16, 'text', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (17, 'mediumtext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (18, 'longtext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (19, 'timestamp', 'LocalDateTime', 'java.time.LocalDateTime', '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (20, 'NUMBER', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (21, 'BINARY_INTEGER', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (22, 'BINARY_FLOAT', 'Float', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (23, 'BINARY_DOUBLE', 'Double', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (24, 'VARCHAR2', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (25, 'NVARCHAR', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (26, 'NVARCHAR2', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (27, 'CLOB', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (28, 'int8', 'Long', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (29, 'int4', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (30, 'int2', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (31, 'numeric', 'BigDecimal', 'java.math.BigDecimal', '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nINSERT INTO `gen_field_type` VALUES (32, 'json', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for gen_group\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_group`;\nCREATE TABLE `gen_group` (\n  `id` bigint NOT NULL,\n  `group_name` varchar(255)  DEFAULT NULL COMMENT '分组名称',\n  `group_desc` varchar(255)  DEFAULT NULL COMMENT '分组描述',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  `create_time` datetime DEFAULT NULL COMMENT '创建人',\n  `update_time` datetime DEFAULT NULL COMMENT '修改人',\n  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB  COMMENT='模板分组';\n\n\n-- ----------------------------\n-- Table structure for gen_table\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_table`;\nCREATE TABLE `gen_table` (\n  `id` bigint NOT NULL,\n  `table_name` varchar(200)  DEFAULT NULL COMMENT '表名',\n  `class_name` varchar(200)  DEFAULT NULL COMMENT '类名',\n  `db_type` varchar(200)  DEFAULT NULL COMMENT '数据库类型',\n  `table_comment` varchar(200)  DEFAULT NULL COMMENT '说明',\n  `author` varchar(200)  DEFAULT NULL COMMENT '作者',\n  `email` varchar(200)  DEFAULT NULL COMMENT '邮箱',\n  `package_name` varchar(200)  DEFAULT NULL COMMENT '项目包名',\n  `version` varchar(200)  DEFAULT NULL COMMENT '项目版本号',\n  `i18n` char(1)  DEFAULT '0' COMMENT '是否生成带有i18n 0 不带有 1带有',\n  `style`  bigint DEFAULT NULL COMMENT '代码风格',\n  `child_table_name` varchar(200)  DEFAULT NULL COMMENT '子表名称',\n  `main_field` varchar(200)  DEFAULT NULL COMMENT '主表关联键',\n  `child_field` varchar(200)  DEFAULT NULL COMMENT '子表关联键',\n  `generator_type` char(1)  DEFAULT '0' COMMENT '生成方式  0：zip压缩包   1：自定义目录',\n  `backend_path` varchar(500)  DEFAULT NULL COMMENT '后端生成路径',\n  `frontend_path` varchar(500)  DEFAULT NULL COMMENT '前端生成路径',\n  `module_name` varchar(200)  DEFAULT NULL COMMENT '模块名',\n  `function_name` varchar(200)  DEFAULT NULL COMMENT '功能名',\n  `form_layout` tinyint DEFAULT NULL COMMENT '表单布局  1：一列   2：两列',\n  `ds_name` varchar(200)  DEFAULT NULL COMMENT '数据源ID',\n  `baseclass_id` bigint DEFAULT NULL COMMENT '基类ID',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `table_name` (`table_name`,`ds_name`) USING BTREE\n) ENGINE=InnoDB  COMMENT='代码生成表';\n\n-- ----------------------------\n-- Records of gen_table\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for gen_table_column\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_table_column`;\nCREATE TABLE `gen_table_column` (\n  `id` bigint NOT NULL,\n  `ds_name` varchar(200)  DEFAULT NULL COMMENT '数据源名称',\n  `table_name` varchar(200)  DEFAULT NULL COMMENT '表名称',\n  `field_name` varchar(200)  DEFAULT NULL COMMENT '字段名称',\n  `field_type` varchar(200)  DEFAULT NULL COMMENT '字段类型',\n  `field_comment` varchar(200)  DEFAULT NULL COMMENT '字段说明',\n  `attr_name` varchar(200)  DEFAULT NULL COMMENT '属性名',\n  `attr_type` varchar(200)  DEFAULT NULL COMMENT '属性类型',\n  `package_name` varchar(200)  DEFAULT NULL COMMENT '属性包名',\n  `sort` int DEFAULT NULL COMMENT '排序',\n  `auto_fill` varchar(20)  DEFAULT NULL COMMENT '自动填充  DEFAULT、INSERT、UPDATE、INSERT_UPDATE',\n  `primary_pk` char(1)  DEFAULT '0' COMMENT '主键 0：否  1：是',\n  `base_field` char(1)  DEFAULT '0' COMMENT '基类字段 0：否  1：是',\n  `form_item` char(1)  DEFAULT '0' COMMENT '表单项 0：否  1：是',\n  `form_required` char(1)  DEFAULT '0' COMMENT '表单必填 0：否  1：是',\n  `form_type` varchar(200)  DEFAULT NULL COMMENT '表单类型',\n  `form_validator` varchar(200)  DEFAULT NULL COMMENT '表单效验',\n  `grid_item` char(1)  DEFAULT '0' COMMENT '列表项 0：否  1：是',\n  `grid_sort` char(1)  DEFAULT '0' COMMENT '列表排序 0：否  1：是',\n  `query_item` char(1)  DEFAULT '0' COMMENT '查询项 0：否  1：是',\n  `query_type` varchar(200)  DEFAULT NULL COMMENT '查询方式',\n  `query_form_type` varchar(200)  DEFAULT NULL COMMENT '查询表单类型',\n  `field_dict` varchar(200)  DEFAULT NULL COMMENT '字典类型',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB  COMMENT='代码生成表字段';\n\n-- ----------------------------\n-- Records of gen_table_column\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for gen_template\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_template`;\nCREATE TABLE `gen_template` (\n  `id` bigint NOT NULL COMMENT '主键',\n  `template_name` varchar(255)  NOT NULL COMMENT '模板名称',\n  `generator_path` varchar(255)  NOT NULL COMMENT '模板路径',\n  `template_desc` varchar(255)  NOT NULL COMMENT '模板描述',\n  `template_code` text  NOT NULL COMMENT '模板代码',\n  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新',\n  `del_flag` char(1)  NOT NULL DEFAULT '0' COMMENT '删除标记',\n  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB  COMMENT='模板';\n\n\n-- ----------------------------\n-- Table structure for gen_template_group\n-- ----------------------------\nDROP TABLE IF EXISTS `gen_template_group`;\nCREATE TABLE `gen_template_group` (\n  `group_id` bigint NOT NULL COMMENT '分组id',\n  `template_id` bigint NOT NULL COMMENT '模板id',\n  PRIMARY KEY (`group_id`,`template_id`)\n) ENGINE=InnoDB  COMMENT='模板分组关联表';\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "db/pig_config.sql",
    "content": "DROP DATABASE IF EXISTS `pig_config`;\n\nCREATE DATABASE  `pig_config` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;\n\nUSE pig_config;\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for config_info\n-- ----------------------------\nDROP TABLE IF EXISTS `config_info`;\nCREATE TABLE `config_info` (\n  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'group_id',\n  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',\n  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COLLATE utf8_bin COMMENT 'source user',\n  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',\n  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',\n  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',\n  `c_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'configuration description',\n  `c_use` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'configuration usage',\n  `effect` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '配置生效的描述',\n  `type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '配置的类型',\n  `c_schema` text COLLATE utf8_bin COMMENT '配置的模式',\n  `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';\n-- ----------------------------\n-- Records of his_config_info\n-- ----------------------------\nBEGIN;\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 配置文件加密根密码\\njasypt:\\n  encryptor:\\n    password: pig\\n    algorithm: PBEWithMD5AndDES\\n    iv-generator-classname: org.jasypt.iv.NoIvGenerator\\n    \\n# Spring 相关\\nspring:\\n  cache:\\n    type: redis\\n  data:\\n    redis:\\n      host: ${REDIS_HOST:127.0.0.1}\\n      password: ${REDIS_PASSWORD:}\\n      port: ${REDIS_PORT:6379}\\n      database: ${REDIS_DATABASE:0}\\n  cloud:\\n    sentinel:\\n      eager: true\\n      transport:\\n        dashboard: pig-sentinel:5003\\n    openfeign:\\n      sentinel:\\n        enabled: true\\n      okhttp:\\n        enabled: true\\n      httpclient:\\n        enabled: false\\n      compression:\\n        request:\\n          enabled: true\\n        response:\\n          enabled: true\\n\\n# 暴露监控端点\\nmanagement:\\n  endpoints:\\n    web:\\n      exposure:\\n        include: \\\"*\\\"  \\n  endpoint:\\n    health:\\n      show-details: ALWAYS\\n\\n# mybaits-plus配置\\nmybatis-plus:\\n  mapper-locations: classpath:/mapper/*Mapper.xml\\n  global-config:\\n    banner: false\\n    db-config:\\n      id-type: auto\\n      table-underline: true\\n      logic-delete-value: 1\\n      logic-not-delete-value: 0\\n  type-handlers-package: com.pig4cloud.pig.common.mybatis.handler\\n  configuration:\\n    map-underscore-to-camel-case: true\\n    shrink-whitespaces-in-sql: true\\n\\n# 短信插件配置：https://www.yuque.com/vxixfq/pig/zw8udk\\nsms:\\n  is-print: false # 是否打印日志\\n  config-type: yaml # 配置类型，yaml', '670b60f71ed234ee2c2d363721a1e2c9', '2025-05-16 12:48:39', '2025-10-29 09:01:23', 'nacos', '10.25.25.1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\\nspring:\\n  freemarker:\\n    allow-request-override: false\\n    allow-session-override: false\\n    cache: true\\n    charset: UTF-8\\n    check-template-location: true\\n    content-type: text/html\\n    enabled: true\\n    request-context-attribute: request\\n    expose-request-attributes: false\\n    expose-session-attributes: false\\n    expose-spring-macro-helpers: true\\n    prefer-file-system-access: true\\n    suffix: .ftl\\n    template-loader-path: classpath:/templates/\\n\\n\\nsecurity:\\n  encode-key: \\'thanks,pig4cloud\\'\\n  ignore-clients:\\n    - test\\n    - client\\n    - open\\n    - app', 'b4a660ece61e8180b4940a0770eddfee', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '# 数据源配置\\nspring:\\n  datasource:\\n    type: com.zaxxer.hikari.HikariDataSource\\n    driver-class-name: com.mysql.cj.jdbc.Driver\\n    username: ${MYSQL_USERNAME:root}\\n    password: ${MYSQL_PASSWORD:root}\\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\\n  resources:\\n    static-locations: classpath:/static/,classpath:/views/\\n', 'a1e1ae7127517eae96a2df8b15d94fe3', '2025-10-29 11:39:40', '2025-10-29 11:39:40', 'nacos', '10.25.25.2', '', 'public', '', NULL, NULL, 'yaml', NULL, '');\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (4, 'pig-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\\n  cloud:\\n    gateway:\\n      server:\\n        webflux:\\n          routes:\\n            # 认证中心\\n            - id: pig-auth\\n              uri: lb://pig-auth\\n              predicates:\\n                - Path=/auth/**\\n            #UPMS 模块\\n            - id: pig-upms-biz\\n              uri: lb://pig-upms-biz\\n              predicates:\\n                - Path=/admin/**\\n              filters:\\n                # 限流配置\\n                - name: RequestRateLimiter\\n                  args:\\n                    key-resolver: \\'#{@remoteAddrKeyResolver}\\'\\n                    redis-rate-limiter.replenishRate: 100\\n                    redis-rate-limiter.burstCapacity: 200\\n            # 代码生成模块\\n            - id: pig-codegen\\n              uri: lb://pig-codegen\\n              predicates:\\n                - Path=/gen/**\\n            # 代码生成模块\\n            - id: pig-quartz\\n              uri: lb://pig-quartz\\n              predicates:\\n                - Path=/job/**\\n            # 固定路由转发配置 无修改\\n            - id: openapi\\n              uri: lb://pig-gateway\\n              predicates:\\n                - Path=/v3/api-docs/**\\n              filters:\\n                - RewritePath=/v3/api-docs/(?<path>.*), /$\\\\{path}/$\\\\{path}/v3/api-docs', '53ace4035d810f07e3767d94e1e68379', '2025-01-30 16:50:04', '2025-05-30 08:36:27', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (5, 'pig-monitor-dev.yml', 'DEFAULT_GROUP', 'spring:\\n  autoconfigure:\\n    exclude: com.pig4cloud.pig.common.core.config.JacksonConfiguration\\n  # 安全配置\\n  security:\\n    user:\\n      name: ENC(8Hk2ILNJM8UTOuW/Xi75qg==)     # pig\\n      password: ENC(o6cuPFfUevmTbkmBnE67Ow====) # pig\\n', '650bdfa15f60f3faa84dfe6e6878b8cf', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (6, 'pig-upms-biz-dev.yml', 'DEFAULT_GROUP', '# 数据源\\nspring:\\n  datasource:\\n    type: com.zaxxer.hikari.HikariDataSource\\n    driver-class-name: com.mysql.cj.jdbc.Driver\\n    username: ${MYSQL_USERNAME:root}\\n    password: ${MYSQL_PASSWORD:root}\\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\\n\\n# 文件上传相关 支持阿里云、华为云、腾讯、minio\\nfile:\\n  bucketName: s3demo \\n  local:\\n    enable: true\\n    base-path: /Users/lengleng/Downloads/img', '80cf3a9b7b490e32b03550c429dea33e', '2025-10-29 11:39:40', '2025-10-29 11:39:40', 'nacos', '10.25.25.2', '', 'public', '', NULL, NULL, 'yaml', NULL, '');\nINSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (7, 'pig-quartz-dev.yml', 'DEFAULT_GROUP', 'spring:\\n  datasource:\\n    type: com.zaxxer.hikari.HikariDataSource\\n    driver-class-name: com.mysql.cj.jdbc.Driver\\n    username: ${MYSQL_USERNAME:root}\\n    password: ${MYSQL_PASSWORD:root}\\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\\n', 'e0c9ce980fd14fd28f955852061970ca', '2025-10-29 11:39:40', '2025-10-29 11:39:40', 'nacos', '10.25.25.2', '', 'public', '', NULL, NULL, 'yaml', NULL, '');\nCOMMIT;\n\n\n-- ----------------------------\n-- Table structure for config_info_beta\n-- ----------------------------\nDROP TABLE IF EXISTS `config_info_beta`;\nCREATE TABLE `config_info_beta` (\n  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',\n  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',\n  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',\n  `beta_ips` varchar(1024) COLLATE utf8_bin DEFAULT NULL COMMENT 'betaIps',\n  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COLLATE utf8_bin COMMENT 'source user',\n  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',\n  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',\n  `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';\n\n-- ----------------------------\n-- Records of config_info_beta\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for config_info_gray\n-- ----------------------------\nDROP TABLE IF EXISTS `config_info_gray`;\nCREATE TABLE `config_info_gray` (\n  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `content` longtext NOT NULL COMMENT 'content',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `src_user` text COMMENT 'src_user',\n  `src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip',\n  `gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',\n  `gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',\n  `gray_name` varchar(128) NOT NULL COMMENT 'gray_name',\n  `gray_rule` text NOT NULL COMMENT 'gray_rule',\n  `encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),\n  KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),\n  KEY `idx_gmt_modified` (`gmt_modified`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='config_info_gray';\n\n-- ----------------------------\n-- Records of config_info_gray\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for config_info_tag\n-- ----------------------------\nDROP TABLE IF EXISTS `config_info_tag`;\nCREATE TABLE `config_info_tag` (\n  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',\n  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',\n  `tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id',\n  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',\n  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',\n  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COLLATE utf8_bin COMMENT 'source user',\n  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';\n\n-- ----------------------------\n-- Records of config_info_tag\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for config_tags_relation\n-- ----------------------------\nDROP TABLE IF EXISTS `config_tags_relation`;\nCREATE TABLE `config_tags_relation` (\n  `id` bigint NOT NULL COMMENT 'id',\n  `tag_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_name',\n  `tag_type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'tag_type',\n  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',\n  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',\n  `nid` bigint NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',\n  PRIMARY KEY (`nid`),\n  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),\n  KEY `idx_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';\n\n-- ----------------------------\n-- Records of config_tags_relation\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for group_capacity\n-- ----------------------------\nDROP TABLE IF EXISTS `group_capacity`;\nCREATE TABLE `group_capacity` (\n  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',\n  `group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID，空字符表示整个集群',\n  `quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值',\n  `usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',\n  `max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值',\n  `max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数，，0表示使用默认值',\n  `max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值',\n  `max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_group_id` (`group_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';\n\n-- ----------------------------\n-- Records of group_capacity\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for his_config_info\n-- ----------------------------\nDROP TABLE IF EXISTS `his_config_info`;\nCREATE TABLE `his_config_info` (\n  `id` bigint unsigned NOT NULL COMMENT 'id',\n  `nid` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',\n  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',\n  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',\n  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',\n  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COLLATE utf8_bin COMMENT 'source user',\n  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',\n  `op_type` char(10) COLLATE utf8_bin DEFAULT NULL COMMENT 'operation type',\n  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',\n  `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥',\n  `publish_type` varchar(50) COLLATE utf8_bin DEFAULT 'formal' COMMENT 'publish type gray or formal',\n  `gray_name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'gray name',\n  `ext_info` longtext COLLATE utf8_bin COMMENT 'ext info',\n  PRIMARY KEY (`nid`),\n  KEY `idx_gmt_create` (`gmt_create`),\n  KEY `idx_gmt_modified` (`gmt_modified`),\n  KEY `idx_did` (`data_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';\n\n\n-- ----------------------------\n-- Table structure for permissions\n-- ----------------------------\nDROP TABLE IF EXISTS `permissions`;\nCREATE TABLE `permissions` (\n  `role` varchar(50) NOT NULL COMMENT 'role',\n  `resource` varchar(128) NOT NULL COMMENT 'resource',\n  `action` varchar(8) NOT NULL COMMENT 'action',\n  UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- ----------------------------\n-- Records of permissions\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for roles\n-- ----------------------------\nDROP TABLE IF EXISTS `roles`;\nCREATE TABLE `roles` (\n  `username` varchar(50) NOT NULL COMMENT 'username',\n  `role` varchar(50) NOT NULL COMMENT 'role',\n  UNIQUE KEY `idx_user_role` (`username`,`role`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- ----------------------------\n-- Records of roles\n-- ----------------------------\nBEGIN;\nINSERT INTO `roles` (`username`, `role`) VALUES ('nacos', 'ROLE_ADMIN');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for tenant_capacity\n-- ----------------------------\nDROP TABLE IF EXISTS `tenant_capacity`;\nCREATE TABLE `tenant_capacity` (\n  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',\n  `tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',\n  `quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值',\n  `usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',\n  `max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值',\n  `max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',\n  `max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值',\n  `max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';\n\n-- ----------------------------\n-- Records of tenant_capacity\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for tenant_info\n-- ----------------------------\nDROP TABLE IF EXISTS `tenant_info`;\nCREATE TABLE `tenant_info` (\n  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `kp` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'kp',\n  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',\n  `tenant_name` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_name',\n  `tenant_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'tenant_desc',\n  `create_source` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'create_source',\n  `gmt_create` bigint NOT NULL COMMENT '创建时间',\n  `gmt_modified` bigint NOT NULL COMMENT '修改时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),\n  KEY `idx_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';\n\n-- ----------------------------\n-- Records of tenant_info\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for users\n-- ----------------------------\nDROP TABLE IF EXISTS `users`;\nCREATE TABLE `users` (\n  `username` varchar(50) NOT NULL COMMENT 'username',\n  `password` varchar(500) NOT NULL COMMENT 'password',\n  `enabled` tinyint(1) NOT NULL COMMENT 'enabled',\n  PRIMARY KEY (`username`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- ----------------------------\n-- Records of users\n-- ----------------------------\nBEGIN;\nINSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('nacos', '$2a$10$W6PKgRTzXUp6R/NY853Kn.nRaIcX3whIMTZ/WWkNqo2MTOeSBjKJq', 1);\nCOMMIT;\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  pig-mysql:\n    build:\n      context: ./db\n    environment:\n      MYSQL_ROOT_HOST: \"%\"\n      MYSQL_ROOT_PASSWORD: root\n    restart: always\n    container_name: pig-mysql\n    image: pig-mysql\n    ports:\n      - 33306:3306\n    networks:\n      - spring_cloud_default\n\n  pig-redis:\n    image: registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/redis\n    ports:\n      - 36379:6379\n    restart: always\n    container_name: pig-redis\n    hostname: pig-redis\n    networks:\n      - spring_cloud_default\n\n  pig-register:\n    build:\n      context: ./pig-register\n    restart: always\n    ports:\n      - 8848:8848\n      - 9848:9848\n      - 8080:8080\n    environment:\n      MYSQL_HOST: pig-mysql\n      REDIS_HOST: pig-redis\n    container_name: pig-register\n    hostname: pig-register\n    image: pig-register\n    networks:\n      - spring_cloud_default\n\n  pig-gateway:\n    build:\n      context: ./pig-gateway\n    restart: always\n    ports:\n      - 9999:9999\n    container_name: pig-gateway\n    hostname: pig-gateway\n    image: pig-gateway\n    environment:\n      REDIS_HOST: pig-redis\n      NACOS_HOST: pig-register\n    networks:\n      - spring_cloud_default\n\n  pig-auth:\n    build:\n      context: ./pig-auth\n    restart: always\n    container_name: pig-auth\n    hostname: pig-auth\n    image: pig-auth\n    environment:\n      REDIS_HOST: pig-redis\n      NACOS_HOST: pig-register\n    networks:\n      - spring_cloud_default\n\n  pig-upms:\n    build:\n      context: ./pig-upms/pig-upms-biz\n    restart: always\n    container_name: pig-upms\n    hostname: pig-upms\n    image: pig-upms\n    environment:\n      MYSQL_HOST: pig-mysql\n      REDIS_HOST: pig-redis\n      NACOS_HOST: pig-register\n    networks:\n      - spring_cloud_default\n\n  pig-monitor:\n    build:\n      context: ./pig-visual/pig-monitor\n    restart: always\n    ports:\n      - 5001:5001\n    container_name: pig-monitor\n    hostname: pig-monitor\n    image: pig-monitor\n    environment:\n      NACOS_HOST: pig-register\n    networks:\n      - spring_cloud_default\n\n  pig-codegen:\n    build:\n      context: ./pig-visual/pig-codegen\n    restart: always\n    container_name: pig-codegen\n    hostname: pig-codegen\n    image: pig-codegen\n    environment:\n      MYSQL_HOST: pig-mysql\n      REDIS_HOST: pig-redis\n      NACOS_HOST: pig-register\n    networks:\n      - spring_cloud_default\n\n  pig-quartz:\n    build:\n      context: ./pig-visual/pig-quartz\n    restart: always\n    image: pig-quartz\n    container_name: pig-quartz\n    environment:\n      MYSQL_HOST: pig-mysql\n      REDIS_HOST: pig-redis\n      NACOS_HOST: pig-register\n    networks:\n      - spring_cloud_default\n\nnetworks:\n  spring_cloud_default:\n    name:  spring_cloud_default\n    driver: bridge\n"
  },
  {
    "path": "pig-auth/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-auth\n\nARG JAR_FILE=target/pig-auth.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 3000\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-auth/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-auth</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 认证授权中心，基于 spring security oAuth2</description>\n\n    <dependencies>\n        <!--注册中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n        </dependency>\n        <!--配置中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n        </dependency>\n        <!--断路器依赖-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-feign</artifactId>\n        </dependency>\n        <!--upms api、model 模块-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-upms-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <!--freemarker 授权码模式渲染-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-freemarker</artifactId>\n        </dependency>\n        <!--undertow容器-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-undertow</artifactId>\n        </dependency>\n        <!-- log -->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-log</artifactId>\n        </dependency>\n        <!-- 调用验证码核心模块 -->\n        <dependency>\n            <groupId>com.pig4cloud.plugin</groupId>\n            <artifactId>captcha-core</artifactId>\n        </dependency>\n        <!-- 加解密依赖 -->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-crypto</artifactId>\n        </dependency>\n    </dependencies>\n\n    <profiles>\n        <profile>\n            <id>boot</id>\n        </profile>\n        <profile>\n            <id>cloud</id>\n            <activation>\n                <!-- 默认环境 -->\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-maven-plugin</artifactId>\n                    </plugin>\n                    <plugin>\n                        <groupId>io.fabric8</groupId>\n                        <artifactId>docker-maven-plugin</artifactId>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n</project>\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/PigAuthApplication.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth;\n\nimport com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n/**\n * 认证授权中心应用启动类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@EnablePigFeignClients\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class PigAuthApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigAuthApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/config/AuthorizationServerConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.config;\n\nimport com.pig4cloud.pig.auth.support.CustomeOAuth2AccessTokenGenerator;\nimport com.pig4cloud.pig.auth.support.core.CustomeOAuth2TokenCustomizer;\nimport com.pig4cloud.pig.auth.support.core.FormIdentityLoginConfigurer;\nimport com.pig4cloud.pig.auth.support.core.PigDaoAuthenticationProvider;\nimport com.pig4cloud.pig.auth.support.filter.PasswordDecoderFilter;\nimport com.pig4cloud.pig.auth.support.filter.ValidateCodeFilter;\nimport com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;\nimport com.pig4cloud.pig.auth.support.handler.PigAuthenticationSuccessEventHandler;\nimport com.pig4cloud.pig.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter;\nimport com.pig4cloud.pig.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider;\nimport com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter;\nimport com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.security.component.PigBootCorsProperties;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;\nimport org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;\nimport org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;\nimport org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;\nimport org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;\nimport org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;\nimport org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;\nimport org.springframework.security.web.DefaultSecurityFilterChain;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.AuthenticationConverter;\nimport org.springframework.security.web.authentication.DelegatingAuthenticationConverter;\nimport org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;\nimport org.springframework.web.cors.CorsConfiguration;\nimport org.springframework.web.cors.UrlBasedCorsConfigurationSource;\n\nimport java.util.Arrays;\n\n/**\n * 认证服务器配置类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Configuration\n@RequiredArgsConstructor\npublic class AuthorizationServerConfiguration {\n\n\tprivate final OAuth2AuthorizationService authorizationService;\n\n\tprivate final PasswordDecoderFilter passwordDecoderFilter;\n\n\tprivate final ValidateCodeFilter validateCodeFilter;\n\n\tprivate final PigBootCorsProperties pigBootCorsProperties;\n\n\t/**\n\t * Authorization Server 配置，仅对 /oauth2/** 的请求有效\n\t * @param http http\n\t * @return {@link SecurityFilterChain }\n\t * @throws Exception 异常\n\t */\n\t@Bean\n\t@Order(Ordered.HIGHEST_PRECEDENCE)\n\tpublic SecurityFilterChain authorizationServer(HttpSecurity http) throws Exception {\n\t\t// 配置授权服务器的安全策略，只有/oauth2/**的请求才会走如下的配置\n\t\thttp.securityMatcher(\"/oauth2/**\");\n\t\tOAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();\n\n\t\t// 增加验证码过滤器\n\t\thttp.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);\n\t\t// 增加密码解密过滤器\n\t\thttp.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class);\n\n\t\thttp.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点\n\t\t\ttokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter\n\t\t\t\t.accessTokenResponseHandler(new PigAuthenticationSuccessEventHandler()) // 登录成功处理器\n\t\t\t\t.errorResponseHandler(new PigAuthenticationFailureEventHandler());// 登录失败处理器\n\t\t}).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证\n\t\toAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))// 处理客户端认证异常\n\t\t\t.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面\n\t\t\t\t.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults())\n\t\t\t.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated());\n\n\t\t// 设置 Token 存储的策略\n\t\thttp.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现\n\t\t\t.authorizationServerSettings(\n\t\t\t\t\tAuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()),\n\t\t\t\tCustomizer.withDefaults());\n\n\t\t// 设置授权码模式登录页面\n\t\thttp.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults());\n\n\t\t// 配置 CORS 跨域资源共享\n\t\tif (Boolean.TRUE.equals(pigBootCorsProperties.getEnabled())) {\n\t\t\thttp.cors(cors -> cors.configurationSource(corsConfigurationSource()));\n\t\t}\n\n\t\tDefaultSecurityFilterChain securityFilterChain = http.build();\n\n\t\t// 注入自定义授权模式实现\n\t\taddCustomOAuth2GrantAuthenticationProvider(http);\n\n\t\treturn securityFilterChain;\n\t}\n\n\t/**\n\t * 令牌生成规则实现 </br>\n\t * client:username:uuid\n\t * @return OAuth2TokenGenerator\n\t */\n\t@Bean\n\tpublic OAuth2TokenGenerator oAuth2TokenGenerator() {\n\t\tCustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator();\n\t\t// 注入Token 增加关联用户信息\n\t\taccessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer());\n\t\treturn new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());\n\t}\n\n\t/**\n\t * request -> xToken 注入请求转换器\n\t * @return DelegatingAuthenticationConverter\n\t */\n\t@Bean\n\tpublic AuthenticationConverter accessTokenRequestConverter() {\n\t\treturn new DelegatingAuthenticationConverter(Arrays.asList(\n\t\t\t\tnew OAuth2ResourceOwnerPasswordAuthenticationConverter(),\n\t\t\t\tnew OAuth2ResourceOwnerSmsAuthenticationConverter(), new OAuth2RefreshTokenAuthenticationConverter(),\n\t\t\t\tnew OAuth2ClientCredentialsAuthenticationConverter(),\n\t\t\t\tnew OAuth2AuthorizationCodeAuthenticationConverter(),\n\t\t\t\tnew OAuth2AuthorizationCodeRequestAuthenticationConverter()));\n\t}\n\n\t/**\n\t * 注入授权模式实现提供方\n\t * <p>\n\t * 1. 密码模式 </br>\n\t * 2. 短信登录 </br>\n\t */\n\tprivate void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {\n\t\tAuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);\n\t\tOAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);\n\n\t\tOAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(\n\t\t\t\tauthenticationManager, authorizationService, oAuth2TokenGenerator());\n\n\t\tOAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(\n\t\t\t\tauthenticationManager, authorizationService, oAuth2TokenGenerator());\n\n\t\t// 处理 UsernamePasswordAuthenticationToken\n\t\thttp.authenticationProvider(new PigDaoAuthenticationProvider());\n\t\t// 处理 OAuth2ResourceOwnerPasswordAuthenticationToken\n\t\thttp.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);\n\t\t// 处理 OAuth2ResourceOwnerSmsAuthenticationToken\n\t\thttp.authenticationProvider(resourceOwnerSmsAuthenticationProvider);\n\t}\n\n\t/**\n\t * 配置 CORS 跨域资源共享\n\t * @return UrlBasedCorsConfigurationSource CORS配置源\n\t */\n\tprivate UrlBasedCorsConfigurationSource corsConfigurationSource() {\n\t\tUrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n\t\tCorsConfiguration corsConfiguration = new CorsConfiguration();\n\n\t\t// 从配置文件读取允许的源模式\n\t\tpigBootCorsProperties.getAllowedOriginPatterns().forEach(corsConfiguration::addAllowedOriginPattern);\n\t\t// 从配置文件读取允许的请求头\n\t\tpigBootCorsProperties.getAllowedHeaders().forEach(corsConfiguration::addAllowedHeader);\n\t\t// 从配置文件读取允许的HTTP方法\n\t\tpigBootCorsProperties.getAllowedMethods().forEach(corsConfiguration::addAllowedMethod);\n\t\t// 从配置文件读取是否允许携带凭证\n\t\tcorsConfiguration.setAllowCredentials(pigBootCorsProperties.getAllowCredentials());\n\n\t\t// 注册CORS配置到指定路径\n\t\tsource.registerCorsConfiguration(pigBootCorsProperties.getPathPattern(), corsConfiguration);\n\n\t\treturn source;\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/ImageCodeEndpoint.java",
    "content": "package com.pig4cloud.pig.auth.endpoint;\n\nimport cn.hutool.core.lang.Validator;\nimport com.pig4cloud.captcha.ArithmeticCaptcha;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 验证码相关的接口\n *\n * @author lengleng\n * @date 2022/6/27\n */\n@RestController\n@RequestMapping(\"/code\")\n@RequiredArgsConstructor\n@Tag(description = \"code\", name = \"验证码控制器管理模块\")\npublic class ImageCodeEndpoint {\n\n\tprivate static final Integer DEFAULT_IMAGE_WIDTH = 100;\n\n\tprivate static final Integer DEFAULT_IMAGE_HEIGHT = 40;\n\n\t/**\n\t * 创建图形验证码并输出到响应流\n\t * @param randomStr 随机字符串，用于缓存验证码\n\t * @param response HTTP响应对象，用于输出验证码图片\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/image\")\n\t@Operation(summary = \"创建图形验证码并输出到响应流\", description = \"创建图形验证码并输出到响应流\")\n\tpublic void image(String randomStr, HttpServletResponse response) {\n\t\tArithmeticCaptcha captcha = new ArithmeticCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);\n\n\t\tif (Validator.isMobile(randomStr)) {\n\t\t\treturn;\n\t\t}\n\n\t\tString result = captcha.text();\n\t\tRedisUtils.set(CacheConstants.DEFAULT_CODE_KEY + randomStr, result, SecurityConstants.CODE_TIME,\n\t\t\t\tTimeUnit.SECONDS);\n\t\t// 转换流信息写出\n\t\tcaptcha.out(response.getOutputStream());\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/PigTokenEndpoint.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.endpoint;\n\nimport java.security.Principal;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.converter.HttpMessageConverter;\nimport org.springframework.http.server.ServletServerHttpResponse;\nimport org.springframework.security.authentication.event.LogoutSuccessEvent;\nimport org.springframework.security.oauth2.core.OAuth2AccessToken;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;\nimport org.springframework.security.oauth2.server.authorization.OAuth2Authorization;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.OAuth2TokenType;\nimport org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;\nimport org.springframework.security.web.authentication.AuthenticationFailureHandler;\nimport org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestHeader;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.servlet.ModelAndView;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;\nimport com.pig4cloud.pig.admin.api.vo.TokenVo;\nimport com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport com.pig4cloud.pig.common.core.util.RetOps;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;\nimport com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand;\nimport com.pig4cloud.pig.common.security.util.OAuthClientException;\n\nimport cn.hutool.core.date.DatePattern;\nimport cn.hutool.core.date.TemporalAccessorUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\n/**\n * OAuth2 令牌端点控制器，提供令牌相关操作\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@RequestMapping\n@RequiredArgsConstructor\n@Tag(description = \"oauth\", name = \"OAuth2 令牌端点控制器管理模块\")\npublic class PigTokenEndpoint {\n\n\tprivate final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();\n\n\tprivate final AuthenticationFailureHandler authenticationFailureHandler = new PigAuthenticationFailureEventHandler();\n\n\tprivate final OAuth2AuthorizationService authorizationService;\n\n\tprivate final RemoteClientDetailsService clientDetailsService;\n\n\tprivate final CacheManager cacheManager;\n\n\t/**\n\t * 授权码模式：认证页面\n\t * @param modelAndView 视图模型对象\n\t * @param error 表单登录失败处理回调的错误信息\n\t * @return 包含登录页面视图和错误信息的ModelAndView对象\n\t */\n\t@GetMapping(\"/token/login\")\n\t@Operation(summary = \"授权码模式：认证页面\", description = \"授权码模式：认证页面\")\n\tpublic ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {\n\t\tmodelAndView.setViewName(\"ftl/login\");\n\t\tmodelAndView.addObject(\"error\", error);\n\t\treturn modelAndView;\n\t}\n\n\t/**\n\t * 授权码模式：确认页面\n\t * @param principal 用户主体信息\n\t * @param modelAndView 模型和视图对象\n\t * @param clientId 客户端ID\n\t * @param scope 请求的权限范围\n\t * @param state 状态参数\n\t * @return 包含确认页面信息的ModelAndView对象\n\t */\n\t@GetMapping(\"/oauth2/confirm_access\")\n\t@Operation(summary = \"授权码模式：确认页面\", description = \"授权码模式：确认页面\")\n\tpublic ModelAndView confirm(Principal principal, ModelAndView modelAndView,\n\t\t\t@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,\n\t\t\t@RequestParam(OAuth2ParameterNames.SCOPE) String scope,\n\t\t\t@RequestParam(OAuth2ParameterNames.STATE) String state) {\n\t\tSysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId))\n\t\t\t.getData()\n\t\t\t.orElseThrow(() -> new OAuthClientException(\"clientId 不合法\"));\n\n\t\tSet<String> authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope());\n\t\tmodelAndView.addObject(\"clientId\", clientId);\n\t\tmodelAndView.addObject(\"state\", state);\n\t\tmodelAndView.addObject(\"scopeList\", authorizedScopes);\n\t\tmodelAndView.addObject(\"principalName\", principal.getName());\n\t\tmodelAndView.setViewName(\"ftl/confirm\");\n\t\treturn modelAndView;\n\t}\n\n\t/**\n\t * 注销并删除令牌\n\t * @param authHeader 认证头信息，包含Bearer token\n\t * @return 返回操作结果，包含布尔值表示是否成功\n\t */\n\t@DeleteMapping(\"/token/logout\")\n\t@Operation(summary = \"注销并删除令牌\", description = \"注销并删除令牌\")\n\tpublic R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {\n\t\tif (StrUtil.isBlank(authHeader)) {\n\t\t\treturn R.ok();\n\t\t}\n\n\t\tString tokenValue = authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim();\n\t\treturn removeToken(tokenValue);\n\t}\n\n\t/**\n\t * 检查令牌有效性\n\t * @param token 待验证的令牌\n\t * @param response HTTP响应对象\n\t * @param request HTTP请求对象\n\t * @throws InvalidBearerTokenException 令牌无效或缺失时抛出异常\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/token/check_token\")\n\t@Operation(summary = \"检查令牌有效性\", description = \"检查令牌有效性\")\n\tpublic void checkToken(String token, HttpServletResponse response, HttpServletRequest request) {\n\t\tServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);\n\n\t\tif (StrUtil.isBlank(token)) {\n\t\t\thttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);\n\t\t\tthis.authenticationFailureHandler.onAuthenticationFailure(request, response,\n\t\t\t\t\tnew InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING));\n\t\t\treturn;\n\t\t}\n\t\tOAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);\n\n\t\t// 如果令牌不存在 返回401\n\t\tif (authorization == null || authorization.getAccessToken() == null) {\n\t\t\tthis.authenticationFailureHandler.onAuthenticationFailure(request, response,\n\t\t\t\t\tnew InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN));\n\t\t\treturn;\n\t\t}\n\n\t\tMap<String, Object> claims = authorization.getAccessToken().getClaims();\n\t\tOAuth2AccessTokenResponse sendAccessTokenResponse = OAuth2EndpointUtils.sendAccessTokenResponse(authorization,\n\t\t\t\tclaims);\n\t\tthis.accessTokenHttpResponseConverter.write(sendAccessTokenResponse, MediaType.APPLICATION_JSON, httpResponse);\n\t}\n\n\t/**\n\t * 删除令牌\n\t * @param token 令牌\n\t * @return 删除结果\n\t */\n\t@Inner\n\t@DeleteMapping(\"/token/remove/{token}\")\n\t@Operation(summary = \"删除令牌\", description = \"删除令牌\")\n\tpublic R<Boolean> removeToken(@PathVariable(\"token\") String token) {\n\t\tOAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);\n\t\tif (authorization == null) {\n\t\t\treturn R.ok();\n\t\t}\n\n\t\tOAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();\n\t\tif (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) {\n\t\t\treturn R.ok();\n\t\t}\n\t\t// 清空用户信息（立即删除）\n\t\tcacheManager.getCache(CacheConstants.USER_DETAILS).evictIfPresent(authorization.getPrincipalName());\n\t\t// 清空access token\n\t\tauthorizationService.remove(authorization);\n\t\t// 处理自定义退出事件，保存相关日志\n\t\tSpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken(\n\t\t\t\tauthorization.getPrincipalName(), authorization.getRegisteredClientId())));\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 分页查询令牌列表\n\t * @param params 请求参数，包含分页参数current和size\n\t * @return 分页结果，包含令牌信息列表\n\t */\n\t@Inner\n\t@PostMapping(\"/token/page\")\n\t@Operation(summary = \"分页查询令牌列表\", description = \"分页查询令牌列表\")\n\tpublic R<Page> tokenList(@RequestBody Map<String, Object> params) {\n\t\t// 根据分页参数获取对应数据\n\t\tString username = MapUtil.getStr(params, SecurityConstants.USERNAME);\n\t\tString pattern = String.format(\"%s::*\", CacheConstants.PROJECT_OAUTH_ACCESS);\n\t\tint current = MapUtil.getInt(params, CommonConstants.CURRENT);\n\t\tint size = MapUtil.getInt(params, CommonConstants.SIZE);\n\t\tPage result = new Page(current, size);\n\n\t\t// 获取总数\n\t\tList<String> allKeys = RedisUtils.scan(pattern);\n\t\tresult.setTotal(allKeys.size());\n\n\t\tList<String> pageKeys = RedisUtils.findKeysForPage(pattern, current - 1, size);\n\t\tList<OAuth2Authorization> pagedAuthorizations = RedisUtils.multiGet(pageKeys);\n\n\t\t// 转换为TokenVo\n\t\tList<TokenVo> tokenVoList = pagedAuthorizations.stream()\n\t\t\t.filter(Objects::nonNull)\n\t\t\t.map(this::convertToTokenVo)\n\t\t\t.filter(tokenVo -> {\n\t\t\t\tif (StrUtil.isBlank(username)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn StrUtil.startWithAnyIgnoreCase(tokenVo.getUsername(), username);\n\t\t\t})\n\t\t\t.toList();\n\n\t\tif (StrUtil.isNotBlank(username)) {\n\t\t\tresult.setTotal(tokenVoList.size());\n\t\t}\n\n\t\tresult.setRecords(tokenVoList);\n\t\treturn R.ok(result);\n\t}\n\n\t/**\n\t * 将OAuth2Authorization转换为TokenVo\n\t * @param authorization OAuth2授权对象\n\t * @return TokenVo对象\n\t */\n\tprivate TokenVo convertToTokenVo(OAuth2Authorization authorization) {\n\t\tTokenVo tokenVo = new TokenVo();\n\t\ttokenVo.setClientId(authorization.getRegisteredClientId());\n\t\ttokenVo.setId(authorization.getId());\n\t\ttokenVo.setUsername(authorization.getPrincipalName());\n\t\tOAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();\n\t\ttokenVo.setAccessToken(accessToken.getToken().getTokenValue());\n\n\t\tString expiresAt = TemporalAccessorUtil.format(accessToken.getToken().getExpiresAt(),\n\t\t\t\tDatePattern.NORM_DATETIME_PATTERN);\n\t\ttokenVo.setExpiresAt(expiresAt);\n\n\t\tString issuedAt = TemporalAccessorUtil.format(accessToken.getToken().getIssuedAt(),\n\t\t\t\tDatePattern.NORM_DATETIME_PATTERN);\n\t\ttokenVo.setIssuedAt(issuedAt);\n\t\treturn tokenVo;\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/CustomeOAuth2AccessTokenGenerator.java",
    "content": "package com.pig4cloud.pig.auth.support;\n\nimport org.springframework.lang.Nullable;\nimport org.springframework.security.crypto.keygen.Base64StringKeyGenerator;\nimport org.springframework.security.crypto.keygen.StringKeyGenerator;\nimport org.springframework.security.oauth2.core.ClaimAccessor;\nimport org.springframework.security.oauth2.core.OAuth2AccessToken;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.oauth2.server.authorization.OAuth2TokenType;\nimport org.springframework.security.oauth2.server.authorization.client.RegisteredClient;\nimport org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;\nimport org.springframework.security.oauth2.server.authorization.token.*;\nimport org.springframework.util.Assert;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\nimport java.io.Serial;\nimport java.time.Instant;\nimport java.util.*;\n\n/**\n * 自定义OAuth2访问令牌生成器\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic class CustomeOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {\n\n\tprivate OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;\n\n\tprivate final StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator(\n\t\t\tBase64.getUrlEncoder().withoutPadding(), 96);\n\n\t/**\n\t * 生成OAuth2访问令牌\n\t * @param context OAuth2令牌上下文\n\t * @return 生成的访问令牌，如果令牌类型不是ACCESS_TOKEN或格式不是REFERENCE则返回null\n\t * @see OAuth2TokenContext\n\t * @see OAuth2AccessToken\n\t */\n\t@Nullable\n\t@Override\n\tpublic OAuth2AccessToken generate(OAuth2TokenContext context) {\n\t\tif (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE\n\t\t\t.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {\n\t\t\treturn null;\n\t\t}\n\n\t\tString issuer = null;\n\t\tif (context.getAuthorizationServerContext() != null) {\n\t\t\tissuer = context.getAuthorizationServerContext().getIssuer();\n\t\t}\n\t\tRegisteredClient registeredClient = context.getRegisteredClient();\n\n\t\tInstant issuedAt = Instant.now();\n\t\tInstant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());\n\n\t\t// @formatter:off\n        OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();\n        if (StringUtils.hasText(issuer)) {\n            claimsBuilder.issuer(issuer);\n        }\n        claimsBuilder\n                .subject(context.getPrincipal().getName())\n                .audience(Collections.singletonList(registeredClient.getClientId()))\n                .issuedAt(issuedAt)\n                .expiresAt(expiresAt)\n                .notBefore(issuedAt)\n                .id(UUID.randomUUID().toString());\n        if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {\n            claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());\n        }\n        // @formatter:on\n\n\t\tif (this.accessTokenCustomizer != null) {\n\t\t\t// @formatter:off\n            OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)\n                    .registeredClient(context.getRegisteredClient())\n                    .principal(context.getPrincipal())\n                    .authorizationServerContext(context.getAuthorizationServerContext())\n                    .authorizedScopes(context.getAuthorizedScopes())\n                    .tokenType(context.getTokenType())\n                    .authorizationGrantType(context.getAuthorizationGrantType());\n            if (context.getAuthorization() != null) {\n                accessTokenContextBuilder.authorization(context.getAuthorization());\n            }\n            if (context.getAuthorizationGrant() != null) {\n                accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());\n            }\n            // @formatter:on\n\n\t\t\tOAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();\n\t\t\tthis.accessTokenCustomizer.customize(accessTokenContext);\n\t\t}\n\n\t\tOAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();\n\t\treturn new CustomeOAuth2AccessTokenGenerator.OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER,\n\t\t\t\tthis.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(),\n\t\t\t\taccessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims());\n\t}\n\n\t/**\n\t * 设置用于定制{@link OAuth2AccessToken}的{@link OAuth2TokenClaimsContext#getClaims()}的{@link OAuth2TokenCustomizer}\n\t * @param accessTokenCustomizer\n\t * 用于定制{@code OAuth2AccessToken}声明的{@link OAuth2TokenCustomizer}\n\t * @throws IllegalArgumentException 当accessTokenCustomizer为null时抛出\n\t */\n\tpublic void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {\n\t\tAssert.notNull(accessTokenCustomizer, \"accessTokenCustomizer cannot be null\");\n\t\tthis.accessTokenCustomizer = accessTokenCustomizer;\n\t}\n\n\t/**\n\t * OAuth2访问令牌声明类，继承自OAuth2AccessToken并实现ClaimAccessor接口\n\t *\n\t * @author lengleng\n\t * @date 2025/05/30\n\t */\n\tprivate static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {\n\n\t\t@Serial\n\t\tprivate static final long serialVersionUID = 1L;\n\n\t\tprivate final Map<String, Object> claims;\n\n\t\t/**\n\t\t * 构造OAuth2访问令牌声明\n\t\t * @param tokenType 令牌类型\n\t\t * @param tokenValue 令牌值\n\t\t * @param issuedAt 颁发时间\n\t\t * @param expiresAt 过期时间\n\t\t * @param scopes 权限范围集合\n\t\t * @param claims 声明信息映射\n\t\t */\n\t\tprivate OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,\n\t\t\t\tSet<String> scopes, Map<String, Object> claims) {\n\t\t\tsuper(tokenType, tokenValue, issuedAt, expiresAt, scopes);\n\t\t\tthis.claims = claims;\n\t\t}\n\n\t\t/**\n\t\t * 获取claims集合\n\t\t * @return claims键值对集合\n\t\t */\n\t\t@Override\n\t\tpublic Map<String, Object> getClaims() {\n\t\t\treturn this.claims;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java",
    "content": "package com.pig4cloud.pig.auth.support.base;\n\nimport com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.oauth2.core.OAuth2ErrorCodes;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.web.authentication.AuthenticationConverter;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * OAuth2资源所有者基础认证转换器抽象类\n *\n * @param <T> 继承自OAuth2ResourceOwnerBaseAuthenticationToken的泛型类型\n * @author lengleng\n * @date 2025/05/30\n */\npublic abstract class OAuth2ResourceOwnerBaseAuthenticationConverter<T extends OAuth2ResourceOwnerBaseAuthenticationToken>\n\t\timplements AuthenticationConverter {\n\n\t/**\n\t * 是否支持此convert\n\t * @param grantType 授权类型\n\t * @return\n\t */\n\tpublic abstract boolean support(String grantType);\n\n\t/**\n\t * 校验参数\n\t * @param request 请求\n\t */\n\tpublic void checkParams(HttpServletRequest request) {\n\n\t}\n\n\t/**\n\t * 构建具体类型的token\n\t * @param clientPrincipal 客户端认证信息\n\t * @param requestedScopes 请求的作用域集合\n\t * @param additionalParameters 附加参数映射\n\t * @return 构建完成的token对象\n\t */\n\tpublic abstract T buildToken(Authentication clientPrincipal, Set<String> requestedScopes,\n\t\t\tMap<String, Object> additionalParameters);\n\n\t/**\n\t * 将HttpServletRequest转换为Authentication对象\n\t * @param request HTTP请求对象\n\t * @return 认证信息对象\n\t * @throws OAuth2AuthenticationException 当请求参数不合法或客户端未认证时抛出异常\n\t */\n\t@Override\n\tpublic Authentication convert(HttpServletRequest request) {\n\n\t\t// grant_type (REQUIRED)\n\t\tString grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);\n\t\tif (!support(grantType)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tMultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);\n\t\t// scope (OPTIONAL)\n\t\tString scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);\n\t\tif (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {\n\t\t\tOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE,\n\t\t\t\t\tOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);\n\t\t}\n\n\t\tSet<String> requestedScopes = null;\n\t\tif (StringUtils.hasText(scope)) {\n\t\t\trequestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, \" \")));\n\t\t}\n\n\t\t// 校验个性化参数\n\t\tcheckParams(request);\n\n\t\t// 获取当前已经认证的客户端信息\n\t\tAuthentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();\n\t\tif (clientPrincipal == null) {\n\t\t\tOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,\n\t\t\t\t\tOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);\n\t\t}\n\n\t\t// 扩展信息\n\t\tMap<String, Object> additionalParameters = parameters.entrySet()\n\t\t\t.stream()\n\t\t\t.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)\n\t\t\t\t\t&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))\n\t\t\t.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));\n\n\t\t// 创建token\n\t\treturn buildToken(clientPrincipal, requestedScopes, additionalParameters);\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java",
    "content": "package com.pig4cloud.pig.auth.support.base;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand;\nimport com.pig4cloud.pig.common.security.util.ScopeException;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\nimport org.springframework.context.support.MessageSourceAccessor;\nimport org.springframework.security.authentication.*;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\nimport org.springframework.security.oauth2.core.*;\nimport org.springframework.security.oauth2.server.authorization.OAuth2Authorization;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.OAuth2TokenType;\nimport org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;\nimport org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;\nimport org.springframework.security.oauth2.server.authorization.client.RegisteredClient;\nimport org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;\nimport org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;\nimport org.springframework.util.Assert;\nimport org.springframework.util.CollectionUtils;\n\nimport java.security.Principal;\nimport java.time.Instant;\nimport java.util.*;\nimport java.util.function.Supplier;\n\n/**\n * OAuth2资源所有者基础认证提供者抽象类，用于处理资源所有者密码凭证授权流程\n *\n * @param <T> OAuth2资源所有者基础认证令牌类型\n * @author lengleng\n * @date 2025/05/30\n */\npublic abstract class OAuth2ResourceOwnerBaseAuthenticationProvider<T extends OAuth2ResourceOwnerBaseAuthenticationToken>\n\t\timplements AuthenticationProvider {\n\n\tprivate static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerBaseAuthenticationProvider.class);\n\n\tprivate static final String ERROR_URI = \"https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1\";\n\n\tprivate final OAuth2AuthorizationService authorizationService;\n\n\tprivate final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;\n\n\tprivate final AuthenticationManager authenticationManager;\n\n\tprivate final MessageSourceAccessor messages;\n\n\t@Deprecated\n\tprivate Supplier<String> refreshTokenGenerator;\n\n\t/**\n\t * 构造一个基于资源所有者密码模式的OAuth2认证提供者\n\t * @param authenticationManager 认证管理器\n\t * @param authorizationService 授权服务\n\t * @param tokenGenerator token生成器\n\t * @throws IllegalArgumentException 当authorizationService或tokenGenerator为null时抛出\n\t * @since 0.2.3\n\t */\n\tpublic OAuth2ResourceOwnerBaseAuthenticationProvider(AuthenticationManager authenticationManager,\n\t\t\tOAuth2AuthorizationService authorizationService,\n\t\t\tOAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {\n\t\tAssert.notNull(authorizationService, \"authorizationService cannot be null\");\n\t\tAssert.notNull(tokenGenerator, \"tokenGenerator cannot be null\");\n\t\tthis.authenticationManager = authenticationManager;\n\t\tthis.authorizationService = authorizationService;\n\t\tthis.tokenGenerator = tokenGenerator;\n\n\t\t// 国际化配置\n\t\tthis.messages = new MessageSourceAccessor(SpringUtil.getBean(\"securityMessageSource\"), Locale.CHINA);\n\t}\n\n\t/**\n\t * 设置刷新令牌生成器\n\t * @param refreshTokenGenerator 刷新令牌生成器，不能为null\n\t * @deprecated 该方法已废弃\n\t */\n\t@Deprecated\n\tpublic void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {\n\t\tAssert.notNull(refreshTokenGenerator, \"refreshTokenGenerator cannot be null\");\n\t\tthis.refreshTokenGenerator = refreshTokenGenerator;\n\t}\n\n\t/**\n\t * 构建用户名密码认证令牌\n\t * @param reqParameters 请求参数映射\n\t * @return 用户名密码认证令牌\n\t */\n\tpublic abstract UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters);\n\n\t/**\n\t * 当前provider是否支持此令牌类型\n\t * @param authentication\n\t * @return\n\t */\n\t@Override\n\tpublic abstract boolean supports(Class<?> authentication);\n\n\t/**\n\t * 当前的请求客户端是否支持此模式\n\t * @param registeredClient\n\t */\n\tpublic abstract void checkClient(RegisteredClient registeredClient);\n\n\t/**\n\t * 执行认证操作，遵循与{@link AuthenticationManager#authenticate(Authentication)}相同的契约\n\t * @param authentication 认证请求对象\n\t * @return 包含凭证的完整认证对象，如果当前认证提供者无法处理传入的认证对象可能返回null\n\t * @throws AuthenticationException 认证失败时抛出\n\t * @throws OAuth2AuthenticationException 当scope无效或token生成失败时抛出\n\t */\n\t@Override\n\tpublic Authentication authenticate(Authentication authentication) throws AuthenticationException {\n\n\t\tT resouceOwnerBaseAuthentication = (T) authentication;\n\n\t\tOAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(\n\t\t\t\tresouceOwnerBaseAuthentication);\n\n\t\tRegisteredClient registeredClient = clientPrincipal.getRegisteredClient();\n\t\tcheckClient(registeredClient);\n\n\t\tSet<String> authorizedScopes;\n\t\t// Default to configured scopes\n\t\tif (!CollectionUtils.isEmpty(resouceOwnerBaseAuthentication.getScopes())) {\n\t\t\tfor (String requestedScope : resouceOwnerBaseAuthentication.getScopes()) {\n\t\t\t\tif (!registeredClient.getScopes().contains(requestedScope)) {\n\t\t\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);\n\t\t\t\t}\n\t\t\t}\n\t\t\tauthorizedScopes = new LinkedHashSet<>(resouceOwnerBaseAuthentication.getScopes());\n\t\t}\n\t\telse {\n\t\t\tauthorizedScopes = new LinkedHashSet<>();\n\t\t}\n\n\t\tMap<String, Object> reqParameters = resouceOwnerBaseAuthentication.getAdditionalParameters();\n\t\ttry {\n\n\t\t\tUsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = buildToken(reqParameters);\n\n\t\t\tLOGGER.debug(\"got usernamePasswordAuthenticationToken=\" + usernamePasswordAuthenticationToken);\n\n\t\t\tAuthentication usernamePasswordAuthentication = authenticationManager\n\t\t\t\t.authenticate(usernamePasswordAuthenticationToken);\n\n\t\t\t// @formatter:off\n\t\t\tDefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()\n\t\t\t\t\t.registeredClient(registeredClient)\n\t\t\t\t\t.principal(usernamePasswordAuthentication)\n\t\t\t\t\t.authorizationServerContext(AuthorizationServerContextHolder.getContext())\n\t\t\t\t\t.authorizedScopes(authorizedScopes)\n\t\t\t\t\t.authorizationGrantType(resouceOwnerBaseAuthentication.getAuthorizationGrantType())\n\t\t\t\t\t.authorizationGrant(resouceOwnerBaseAuthentication);\n\t\t\t// @formatter:on\n\n\t\t\tOAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization\n\t\t\t\t.withRegisteredClient(registeredClient)\n\t\t\t\t.principalName(usernamePasswordAuthentication.getName())\n\t\t\t\t.authorizationGrantType(resouceOwnerBaseAuthentication.getAuthorizationGrantType())\n\t\t\t\t// 0.4.0 新增的方法\n\t\t\t\t.authorizedScopes(authorizedScopes);\n\n\t\t\t// ----- Access token -----\n\t\t\tOAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();\n\t\t\tOAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);\n\t\t\tif (generatedAccessToken == null) {\n\t\t\t\tOAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,\n\t\t\t\t\t\t\"The token generator failed to generate the access token.\", ERROR_URI);\n\t\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t\t}\n\t\t\tOAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,\n\t\t\t\t\tgeneratedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),\n\t\t\t\t\tgeneratedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());\n\t\t\tif (generatedAccessToken instanceof ClaimAccessor) {\n\t\t\t\tauthorizationBuilder.id(accessToken.getTokenValue())\n\t\t\t\t\t.token(accessToken,\n\t\t\t\t\t\t\t(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME,\n\t\t\t\t\t\t\t\t\t((ClaimAccessor) generatedAccessToken).getClaims()))\n\t\t\t\t\t// 0.4.0 新增的方法\n\t\t\t\t\t.authorizedScopes(authorizedScopes)\n\t\t\t\t\t.attribute(Principal.class.getName(), usernamePasswordAuthentication);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tauthorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken);\n\t\t\t}\n\n\t\t\t// ----- Refresh token -----\n\t\t\tOAuth2RefreshToken refreshToken = null;\n\t\t\tif (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&\n\t\t\t// Do not issue refresh token to public client\n\t\t\t\t\t!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {\n\n\t\t\t\tif (this.refreshTokenGenerator != null) {\n\t\t\t\t\tInstant issuedAt = Instant.now();\n\t\t\t\t\tInstant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getRefreshTokenTimeToLive());\n\t\t\t\t\trefreshToken = new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ttokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();\n\t\t\t\t\tOAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);\n\t\t\t\t\tif (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {\n\t\t\t\t\t\tOAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,\n\t\t\t\t\t\t\t\t\"The token generator failed to generate the refresh token.\", ERROR_URI);\n\t\t\t\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t\t\t\t}\n\t\t\t\t\trefreshToken = (OAuth2RefreshToken) generatedRefreshToken;\n\t\t\t\t}\n\t\t\t\tauthorizationBuilder.refreshToken(refreshToken);\n\t\t\t}\n\n\t\t\tOAuth2Authorization authorization = authorizationBuilder.build();\n\n\t\t\tthis.authorizationService.save(authorization);\n\n\t\t\tLOGGER.debug(\"returning OAuth2AccessTokenAuthenticationToken\");\n\n\t\t\treturn new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken,\n\t\t\t\t\trefreshToken, Objects.requireNonNull(authorization.getAccessToken().getClaims()));\n\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tLOGGER.error(\"problem in authenticate\", ex);\n\t\t\tthrow oAuth2AuthenticationException(authentication, (AuthenticationException) ex);\n\t\t}\n\n\t}\n\n\t/**\n\t * 登录异常转换为oauth2异常\n\t * @param authentication 身份验证\n\t * @param authenticationException 身份验证异常\n\t * @return {@link OAuth2AuthenticationException}\n\t */\n\tprivate OAuth2AuthenticationException oAuth2AuthenticationException(Authentication authentication,\n\t\t\tAuthenticationException authenticationException) {\n\t\tif (authenticationException instanceof UsernameNotFoundException) {\n\t\t\treturn new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USERNAME_NOT_FOUND,\n\t\t\t\t\tthis.messages.getMessage(\"JdbcDaoImpl.notFound\", new Object[] { authentication.getName() },\n\t\t\t\t\t\t\t\"Username {0} not found\"),\n\t\t\t\t\t\"\"));\n\t\t}\n\t\tif (authenticationException instanceof BadCredentialsException) {\n\t\t\treturn new OAuth2AuthenticationException(\n\t\t\t\t\tnew OAuth2Error(OAuth2ErrorCodesExpand.BAD_CREDENTIALS, this.messages.getMessage(\n\t\t\t\t\t\t\t\"AbstractUserDetailsAuthenticationProvider.badCredentials\", \"Bad credentials\"), \"\"));\n\t\t}\n\t\tif (authenticationException instanceof LockedException) {\n\t\t\treturn new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_LOCKED, this.messages\n\t\t\t\t.getMessage(\"AbstractUserDetailsAuthenticationProvider.locked\", \"User account is locked\"), \"\"));\n\t\t}\n\t\tif (authenticationException instanceof DisabledException) {\n\t\t\treturn new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_DISABLE,\n\t\t\t\t\tthis.messages.getMessage(\"AbstractUserDetailsAuthenticationProvider.disabled\", \"User is disabled\"),\n\t\t\t\t\t\"\"));\n\t\t}\n\t\tif (authenticationException instanceof AccountExpiredException) {\n\t\t\treturn new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_EXPIRED, this.messages\n\t\t\t\t.getMessage(\"AbstractUserDetailsAuthenticationProvider.expired\", \"User account has expired\"), \"\"));\n\t\t}\n\t\tif (authenticationException instanceof CredentialsExpiredException) {\n\t\t\treturn new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.CREDENTIALS_EXPIRED,\n\t\t\t\t\tthis.messages.getMessage(\"AbstractUserDetailsAuthenticationProvider.credentialsExpired\",\n\t\t\t\t\t\t\t\"User credentials have expired\"),\n\t\t\t\t\t\"\"));\n\t\t}\n\t\tif (authenticationException instanceof ScopeException) {\n\t\t\treturn new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,\n\t\t\t\t\tthis.messages.getMessage(\"AbstractAccessDecisionManager.accessDenied\", \"invalid_scope\"), \"\"));\n\t\t}\n\t\treturn new OAuth2AuthenticationException(OAuth2ErrorCodesExpand.UN_KNOW_LOGIN_ERROR);\n\t}\n\n\t/**\n\t * 获取已认证的客户端主体，否则抛出无效客户端异常\n\t * @param authentication 认证信息\n\t * @return 已认证的客户端主体\n\t * @throws OAuth2AuthenticationException 客户端未认证时抛出异常\n\t */\n\tprivate OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(\n\t\t\tAuthentication authentication) {\n\n\t\tOAuth2ClientAuthenticationToken clientPrincipal = null;\n\n\t\tif (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {\n\t\t\tclientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();\n\t\t}\n\n\t\tif (clientPrincipal != null && clientPrincipal.isAuthenticated()) {\n\t\t\treturn clientPrincipal;\n\t\t}\n\n\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java",
    "content": "package com.pig4cloud.pig.auth.support.base;\n\nimport lombok.Getter;\nimport org.springframework.lang.Nullable;\nimport org.springframework.security.authentication.AbstractAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.util.Assert;\n\nimport java.io.Serial;\nimport java.util.*;\n\n/**\n * OAuth2资源所有者基础认证令牌抽象类\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic abstract class OAuth2ResourceOwnerBaseAuthenticationToken extends AbstractAuthenticationToken {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t@Getter\n\tprivate final AuthorizationGrantType authorizationGrantType;\n\n\t@Getter\n\tprivate final Authentication clientPrincipal;\n\n\t@Getter\n\tprivate final Set<String> scopes;\n\n\t@Getter\n\tprivate final Map<String, Object> additionalParameters;\n\n\tpublic OAuth2ResourceOwnerBaseAuthenticationToken(AuthorizationGrantType authorizationGrantType,\n\t\t\tAuthentication clientPrincipal, @Nullable Set<String> scopes,\n\t\t\t@Nullable Map<String, Object> additionalParameters) {\n\t\tsuper(Collections.emptyList());\n\t\tAssert.notNull(authorizationGrantType, \"authorizationGrantType cannot be null\");\n\t\tAssert.notNull(clientPrincipal, \"clientPrincipal cannot be null\");\n\t\tthis.authorizationGrantType = authorizationGrantType;\n\t\tthis.clientPrincipal = clientPrincipal;\n\t\tthis.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());\n\t\tthis.additionalParameters = Collections.unmodifiableMap(\n\t\t\t\tadditionalParameters != null ? new HashMap<>(additionalParameters) : Collections.emptyMap());\n\t}\n\n\t/**\n\t * 扩展模式一般不需要密码\n\t */\n\t@Override\n\tpublic Object getCredentials() {\n\t\treturn \"\";\n\t}\n\n\t/**\n\t * 获取用户名\n\t */\n\t@Override\n\tpublic Object getPrincipal() {\n\t\treturn this.clientPrincipal;\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/package-info.java",
    "content": "/**\n * 自定义认证模式接入的抽象实现\n */\npackage com.pig4cloud.pig.auth.support.base;\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/CustomeOAuth2TokenCustomizer.java",
    "content": "package com.pig4cloud.pig.auth.support.core;\n\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.security.service.PigUser;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;\n\n/**\n * OAuth2 Token 自定义增强实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic class CustomeOAuth2TokenCustomizer implements OAuth2TokenCustomizer<OAuth2TokenClaimsContext> {\n\n\t/**\n\t * 自定义OAuth 2.0 Token属性\n\t * @param context 包含OAuth 2.0 Token属性的上下文\n\t */\n\t@Override\n\tpublic void customize(OAuth2TokenClaimsContext context) {\n\t\tOAuth2TokenClaimsSet.Builder claims = context.getClaims();\n\t\tclaims.claim(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);\n\t\tString clientId = context.getAuthorizationGrant().getName();\n\t\tclaims.claim(SecurityConstants.CLIENT_ID, clientId);\n\t\t// 客户端模式不返回具体用户信息\n\t\tif (SecurityConstants.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType().getValue())) {\n\t\t\treturn;\n\t\t}\n\n\t\tPigUser pigUser = (PigUser) context.getPrincipal().getPrincipal();\n\t\tclaims.claim(SecurityConstants.DETAILS_USER, pigUser);\n\t\tclaims.claim(SecurityConstants.DETAILS_USER_ID, pigUser.getId());\n\t\tclaims.claim(SecurityConstants.USERNAME, pigUser.getUsername());\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/FormIdentityLoginConfigurer.java",
    "content": "package com.pig4cloud.pig.auth.support.core;\n\nimport com.pig4cloud.pig.auth.support.handler.FormAuthenticationFailureHandler;\nimport com.pig4cloud.pig.auth.support.handler.SsoLogoutSuccessHandler;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;\n\n/**\n * 基于授权码模式的统一认证登录配置类，适用于Spring Security和SAS\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic final class FormIdentityLoginConfigurer\n\t\textends AbstractHttpConfigurer<FormIdentityLoginConfigurer, HttpSecurity> {\n\n\t@Override\n\tpublic void init(HttpSecurity http) throws Exception {\n\t\thttp.formLogin(formLogin -> {\n\t\t\tformLogin.loginPage(\"/token/login\");\n\t\t\tformLogin.loginProcessingUrl(\"/oauth2/form\");\n\t\t\tformLogin.failureHandler(new FormAuthenticationFailureHandler());\n\n\t\t})\n\t\t\t.logout(logout -> logout.logoutUrl(\"/oauth2/logout\")\n\t\t\t\t.logoutSuccessHandler(new SsoLogoutSuccessHandler())\n\t\t\t\t.deleteCookies(\"JSESSIONID\")\n\t\t\t\t.invalidateHttpSession(true)) // SSO登出成功处理\n\n\t\t\t.csrf(AbstractHttpConfigurer::disable);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/PigDaoAuthenticationProvider.java",
    "content": "package com.pig4cloud.pig.auth.support.core;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.pig4cloud.pig.common.core.util.WebUtils;\nimport com.pig4cloud.pig.common.security.service.PigUserDetailsService;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.SneakyThrows;\nimport org.springframework.core.Ordered;\nimport org.springframework.security.authentication.BadCredentialsException;\nimport org.springframework.security.authentication.InternalAuthenticationServiceException;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UserDetailsPasswordService;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\nimport org.springframework.security.crypto.factory.PasswordEncoderFactories;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationConverter;\nimport org.springframework.util.Assert;\n\nimport java.util.Comparator;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\nimport static com.pig4cloud.pig.common.core.constant.SecurityConstants.PASSWORD;\n\n/**\n * 基于DAO的认证提供者实现，用于处理用户名密码认证\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {\n\n\t/**\n\t * 用户未找到时用于PasswordEncoder#matches(CharSequence, String)的明文密码，避免SEC-2056问题\n\t */\n\tprivate static final String USER_NOT_FOUND_PASSWORD = \"userNotFoundPassword\";\n\n\tprivate final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();\n\n\t/**\n\t * 密码编码器\n\t */\n\tprivate PasswordEncoder passwordEncoder;\n\n\t/**\n\t * 用户未找到时的加密密码，用于避免SEC-2056问题，某些密码编码器在密码格式无效时会短路处理\n\t */\n\tprivate volatile String userNotFoundEncodedPassword;\n\n\tprivate UserDetailsService userDetailsService;\n\n\tprivate UserDetailsPasswordService userDetailsPasswordService;\n\n\tpublic PigDaoAuthenticationProvider() {\n\t\tsetMessageSource(SpringUtil.getBean(\"securityMessageSource\"));\n\t\tsetPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());\n\t}\n\n\t/**\n\t * 执行额外的身份验证检查\n\t * @param userDetails 用户详细信息\n\t * @param authentication 身份验证令牌\n\t * @throws AuthenticationException 身份验证失败时抛出异常\n\t */\n\t@Override\n\tprotected void additionalAuthenticationChecks(UserDetails userDetails,\n\t\t\tUsernamePasswordAuthenticationToken authentication) throws AuthenticationException {\n\n\t\t// 只有密码模式需要校验密码\n\t\tString grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);\n\t\tif (!StrUtil.equals(PASSWORD, grantType)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (authentication.getCredentials() == null) {\n\t\t\tthis.logger.debug(\"Failed to authenticate since no credentials provided\");\n\t\t\tthrow new BadCredentialsException(this.messages\n\t\t\t\t.getMessage(\"AbstractUserDetailsAuthenticationProvider.badCredentials\", \"Bad credentials\"));\n\t\t}\n\t\tString presentedPassword = authentication.getCredentials().toString();\n\t\tif (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {\n\t\t\tthis.logger.debug(\"Failed to authenticate since password does not match stored value\");\n\t\t\tthrow new BadCredentialsException(this.messages\n\t\t\t\t.getMessage(\"AbstractUserDetailsAuthenticationProvider.badCredentials\", \"Bad credentials\"));\n\t\t}\n\t}\n\n\t/**\n\t * 根据用户名检索用户详情\n\t * @param username 用户名\n\t * @param authentication 认证令牌\n\t * @return 用户详情信息\n\t * @throws InternalAuthenticationServiceException\n\t * 当无法获取请求、未注册UserDetailsService或加载用户失败时抛出\n\t * @throws UsernameNotFoundException 当用户名不存在时抛出\n\t */\n\t@SneakyThrows\n\t@Override\n\tprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {\n\t\tprepareTimingAttackProtection();\n\t\tHttpServletRequest request = WebUtils.getRequest()\n\t\t\t.orElseThrow(\n\t\t\t\t\t(Supplier<Throwable>) () -> new InternalAuthenticationServiceException(\"web request is empty\"));\n\n\t\tString grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);\n\t\tString clientId = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.CLIENT_ID);\n\n\t\tif (StrUtil.isBlank(clientId)) {\n\t\t\tclientId = Optional.ofNullable(basicConvert.convert(request))\n\t\t\t\t.map(UsernamePasswordAuthenticationToken::getName)\n\t\t\t\t.orElse(null);\n\t\t}\n\n\t\tMap<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil\n\t\t\t.getBeansOfType(PigUserDetailsService.class);\n\n\t\tString finalClientId = clientId;\n\t\tOptional<PigUserDetailsService> optional = userDetailsServiceMap.values()\n\t\t\t.stream()\n\t\t\t.filter(service -> service.support(finalClientId, grantType))\n\t\t\t.max(Comparator.comparingInt(Ordered::getOrder));\n\n\t\tif (optional.isEmpty()) {\n\t\t\tthrow new InternalAuthenticationServiceException(\"UserDetailsService error , not register\");\n\t\t}\n\n\t\ttry {\n\t\t\tUserDetails loadedUser = optional.get().loadUserByUsername(username);\n\t\t\tif (loadedUser == null) {\n\t\t\t\tthrow new InternalAuthenticationServiceException(\n\t\t\t\t\t\t\"UserDetailsService returned null, which is an interface contract violation\");\n\t\t\t}\n\t\t\treturn loadedUser;\n\t\t}\n\t\tcatch (UsernameNotFoundException ex) {\n\t\t\tmitigateAgainstTimingAttack(authentication);\n\t\t\tthrow ex;\n\t\t}\n\t\tcatch (InternalAuthenticationServiceException ex) {\n\t\t\tthrow ex;\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthrow new InternalAuthenticationServiceException(ex.getMessage(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * 创建认证成功后的Authentication对象\n\t * @param principal 认证主体\n\t * @param authentication 认证信息\n\t * @param user 用户详情\n\t * @return 认证成功后的Authentication对象\n\t */\n\t@Override\n\tprotected Authentication createSuccessAuthentication(Object principal, Authentication authentication,\n\t\t\tUserDetails user) {\n\t\tboolean upgradeEncoding = this.userDetailsPasswordService != null\n\t\t\t\t&& this.passwordEncoder.upgradeEncoding(user.getPassword());\n\t\tif (upgradeEncoding) {\n\t\t\tString presentedPassword = authentication.getCredentials().toString();\n\t\t\tString newPassword = this.passwordEncoder.encode(presentedPassword);\n\t\t\tuser = this.userDetailsPasswordService.updatePassword(user, newPassword);\n\t\t}\n\t\treturn super.createSuccessAuthentication(principal, authentication, user);\n\t}\n\n\t/**\n\t * 准备定时攻击保护，如果未找到用户编码密码为空则进行编码\n\t */\n\tprivate void prepareTimingAttackProtection() {\n\t\tif (this.userNotFoundEncodedPassword == null) {\n\t\t\tthis.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);\n\t\t}\n\t}\n\n\t/**\n\t * 防止时序攻击的缓解措施\n\t * @param authentication 用户名密码认证令牌\n\t */\n\tprivate void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {\n\t\tif (authentication.getCredentials() != null) {\n\t\t\tString presentedPassword = authentication.getCredentials().toString();\n\t\t\tthis.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);\n\t\t}\n\t}\n\n\t/**\n\t * 设置用于编码和验证密码的PasswordEncoder实例\n\t * @param passwordEncoder 密码编码器实例，不能为null\n\t */\n\tpublic void setPasswordEncoder(PasswordEncoder passwordEncoder) {\n\t\tAssert.notNull(passwordEncoder, \"passwordEncoder cannot be null\");\n\t\tthis.passwordEncoder = passwordEncoder;\n\t\tthis.userNotFoundEncodedPassword = null;\n\t}\n\n\tprotected PasswordEncoder getPasswordEncoder() {\n\t\treturn this.passwordEncoder;\n\t}\n\n\t/**\n\t * 设置用户详情服务\n\t * @param userDetailsService 用户详情服务\n\t */\n\tpublic void setUserDetailsService(UserDetailsService userDetailsService) {\n\t\tthis.userDetailsService = userDetailsService;\n\t}\n\n\tprotected UserDetailsService getUserDetailsService() {\n\t\treturn this.userDetailsService;\n\t}\n\n\t/**\n\t * 设置用户详情密码服务\n\t * @param userDetailsPasswordService 用户详情密码服务\n\t */\n\tpublic void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {\n\t\tthis.userDetailsPasswordService = userDetailsPasswordService;\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/AuthSecurityConfigProperties.java",
    "content": "package com.pig4cloud.pig.auth.support.filter;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\n\n/**\n * 安全认证配置属性类\n *\n * <p>\n * 用于配置网关安全相关属性\n * </p>\n *\n * @author lengleng\n * @date 2025/05/30\n * @since 2020/10/4\n */\n@Data\n@Component\n@RefreshScope\n@ConfigurationProperties(\"security\")\npublic class AuthSecurityConfigProperties {\n\n\t/**\n\t * 是否是微服务架构\n\t */\n\tprivate boolean isMicro;\n\n\t/**\n\t * 网关解密登录前端密码 秘钥\n\t */\n\tprivate String encodeKey;\n\n\t/**\n\t * 网关不需要校验验证码的客户端\n\t */\n\tprivate List<String> ignoreClients;\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/PasswordDecoderFilter.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.support.filter;\n\nimport java.io.IOException;\nimport java.util.Map;\n\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\n\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.servlet.RepeatBodyRequestWrapper;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.Mode;\nimport cn.hutool.crypto.Padding;\nimport cn.hutool.crypto.SecureUtil;\nimport cn.hutool.crypto.symmetric.AES;\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 密码解密过滤器：用于处理登录请求中的密码解密\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Component\n@RequiredArgsConstructor\npublic class PasswordDecoderFilter extends OncePerRequestFilter {\n\n\tprivate final AuthSecurityConfigProperties authSecurityConfigProperties;\n\n\tprivate static final String PASSWORD = \"password\";\n\n\tprivate static final String KEY_ALGORITHM = \"AES\";\n\n\tstatic {\n\t\t// 关闭hutool 强制关闭Bouncy Castle库的依赖\n\t\tSecureUtil.disableBouncyCastle();\n\t}\n\n\t/**\n\t * 过滤器内部处理逻辑，用于处理登录请求中的密码解密\n\t * @param request HTTP请求对象\n\t * @param response HTTP响应对象\n\t * @param chain 过滤器链\n\t * @throws ServletException 如果发生servlet相关异常\n\t * @throws IOException 如果发生I/O异常\n\t */\n\t@Override\n\tprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n\t\t\tthrows ServletException, IOException {\n\t\t// 不是登录请求，直接向下执行\n\t\tif (!StrUtil.containsAnyIgnoreCase(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)) {\n\t\t\tchain.doFilter(request, response);\n\t\t\treturn;\n\t\t}\n\n\t\t// 将请求流转换为可多次读取的请求流\n\t\tRepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);\n\t\tMap<String, String[]> parameterMap = requestWrapper.getParameterMap();\n\n\t\t// 构建前端对应解密AES 因子\n\t\tAES aes = new AES(Mode.CFB, Padding.NoPadding,\n\t\t\t\tnew SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM),\n\t\t\t\tnew IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes()));\n\n\t\tparameterMap.forEach((k, v) -> {\n\t\t\tString[] values = parameterMap.get(k);\n\t\t\tif (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// 解密密码\n\t\t\tString decryptPassword = aes.decryptStr(values[0]);\n\t\t\tparameterMap.put(k, new String[] { decryptPassword });\n\t\t});\n\t\tchain.doFilter(requestWrapper, response);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/ValidateCodeFilter.java",
    "content": "package com.pig4cloud.pig.auth.support.filter;\n\nimport java.io.IOException;\nimport java.util.Optional;\n\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.exception.ValidateCodeException;\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport com.pig4cloud.pig.common.core.util.WebUtils;\n\n/**\n * 登录前处理器\n *\n * @author lengleng\n * @date 2024/4/3\n */\n\nimport cn.hutool.core.util.StrUtil;\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 验证码过滤器：用于处理登录请求中的验证码校验\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Component\n@RequiredArgsConstructor\npublic class ValidateCodeFilter extends OncePerRequestFilter {\n\n\tprivate final AuthSecurityConfigProperties authSecurityConfigProperties;\n\n\t/**\n\t * 过滤器内部处理逻辑，用于验证码校验\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param filterChain 过滤器链\n\t * @throws ServletException Servlet异常\n\t * @throws IOException IO异常\n\t */\n\t@Override\n\tprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)\n\t\t\tthrows ServletException, IOException {\n\n\t\tString requestUrl = request.getServletPath();\n\n\t\t// 不是登录URL 请求直接跳过\n\t\tif (!SecurityConstants.OAUTH_TOKEN_URL.equals(requestUrl)) {\n\t\t\tfilterChain.doFilter(request, response);\n\t\t\treturn;\n\t\t}\n\n\t\t// 如果登录URL 但是刷新token的请求，直接向下执行\n\t\tString grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);\n\t\tif (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {\n\t\t\tfilterChain.doFilter(request, response);\n\t\t\treturn;\n\t\t}\n\n\t\t// 如果是密码模式 && 客户端不需要校验验证码\n\t\tboolean isIgnoreClient = authSecurityConfigProperties.getIgnoreClients().contains(WebUtils.getClientId());\n\t\tif (StrUtil.equalsAnyIgnoreCase(grantType, SecurityConstants.PASSWORD, SecurityConstants.CLIENT_CREDENTIALS,\n\t\t\t\tSecurityConstants.AUTHORIZATION_CODE) && isIgnoreClient) {\n\t\t\tfilterChain.doFilter(request, response);\n\t\t\treturn;\n\t\t}\n\n\t\t// 校验验证码 1. 客户端开启验证码 2. 短信模式\n\t\ttry {\n\t\t\tcheckCode();\n\t\t\tfilterChain.doFilter(request, response);\n\t\t}\n\t\tcatch (ValidateCodeException validateCodeException) {\n\t\t\tthrow new OAuth2AuthenticationException(validateCodeException.getMessage());\n\t\t}\n\t}\n\n\t/**\n\t * 校验验证码\n\t */\n\tprivate void checkCode() throws ValidateCodeException {\n\t\tOptional<HttpServletRequest> request = WebUtils.getRequest();\n\t\tString code = request.get().getParameter(\"code\");\n\n\t\tif (StrUtil.isBlank(code)) {\n\t\t\tthrow new ValidateCodeException(\"验证码不能为空\");\n\t\t}\n\n\t\tString randomStr = request.get().getParameter(\"randomStr\");\n\n\t\t// https://gitee.com/log4j/pig/issues/IWA0D\n\t\tString mobile = request.get().getParameter(\"mobile\");\n\t\tif (StrUtil.isNotBlank(mobile)) {\n\t\t\trandomStr = mobile;\n\t\t}\n\n\t\tString key = CacheConstants.DEFAULT_CODE_KEY + randomStr;\n\t\tif (!RedisUtils.hasKey(key)) {\n\t\t\tthrow new ValidateCodeException(\"验证码不合法\");\n\t\t}\n\n\t\tString saveCode = RedisUtils.get(key);\n\n\t\tif (StrUtil.isBlank(saveCode)) {\n\t\t\tRedisUtils.delete(key);\n\t\t\tthrow new ValidateCodeException(\"验证码不合法\");\n\t\t}\n\n\t\tif (!StrUtil.equals(saveCode, code)) {\n\t\t\tRedisUtils.delete(key);\n\t\t\tthrow new ValidateCodeException(\"验证码不合法\");\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/FormAuthenticationFailureHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.support.handler;\n\nimport cn.hutool.core.util.CharsetUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.pig4cloud.pig.common.core.util.WebUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.web.authentication.AuthenticationFailureHandler;\n\nimport java.io.IOException;\n\n/**\n * 表单登录失败处理逻辑\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\npublic class FormAuthenticationFailureHandler implements AuthenticationFailureHandler {\n\n\t/**\n\t * 当认证失败时调用\n\t * @param request 认证尝试发生的请求\n\t * @param response 响应对象\n\t * @param exception 拒绝认证时抛出的异常\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,\n\t\t\tAuthenticationException exception) {\n\t\tlog.debug(\"表单登录失败:{}\", exception.getLocalizedMessage());\n\n\t\t// 获取当前请求的context-path\n\t\tString contextPath = request.getContextPath();\n\n\t\t// 构建重定向URL，加入context-path\n\t\tString url = HttpUtil.encodeParams(\n\t\t\t\tString.format(\"%s/token/login?error=%s\", contextPath, exception.getMessage()),\n\t\t\t\tCharsetUtil.CHARSET_UTF_8);\n\n\t\ttry {\n\t\t\tWebUtils.getResponse().sendRedirect(url);\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tlog.error(\"重定向失败\", e);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationFailureEventHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.support.handler;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.log.event.SysLogEvent;\nimport com.pig4cloud.pig.common.log.util.LogTypeEnum;\nimport com.pig4cloud.pig.common.log.util.SysLogUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;\nimport org.springframework.http.server.ServletServerHttpResponse;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.web.authentication.AuthenticationFailureHandler;\n\nimport java.io.IOException;\n\n/**\n * 认证失败处理器：处理用户认证失败事件并记录日志\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\npublic class PigAuthenticationFailureEventHandler implements AuthenticationFailureHandler {\n\n\tprivate final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter();\n\n\t/**\n\t * 当认证失败时调用\n\t * @param request 认证请求\n\t * @param response 认证响应\n\t * @param exception 认证失败的异常\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,\n\t\t\tAuthenticationException exception) {\n\t\tString username = request.getParameter(OAuth2ParameterNames.USERNAME);\n\n\t\tlog.info(\"用户：{} 登录失败，异常：{}\", username, exception.getLocalizedMessage());\n\t\tSysLog logVo = SysLogUtils.getSysLog();\n\t\tlogVo.setTitle(\"登录失败\");\n\t\tlogVo.setLogType(LogTypeEnum.ERROR.getType());\n\t\tlogVo.setException(exception.getLocalizedMessage());\n\t\t// 发送异步日志事件\n\t\tString startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);\n\t\tif (StrUtil.isNotBlank(startTimeStr)) {\n\t\t\tLong startTime = Long.parseLong(startTimeStr);\n\t\t\tLong endTime = System.currentTimeMillis();\n\t\t\tlogVo.setTime(endTime - startTime);\n\t\t}\n\t\tlogVo.setCreateBy(username);\n\t\tSpringContextHolder.publishEvent(new SysLogEvent(logVo));\n\t\t// 写出错误信息\n\t\tsendErrorResponse(request, response, exception);\n\t}\n\n\t/**\n\t * 发送错误响应\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param exception 认证异常\n\t * @throws IOException 写入响应时发生IO异常\n\t */\n\tprivate void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,\n\t\t\tAuthenticationException exception) throws IOException {\n\t\tServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);\n\t\thttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);\n\t\tString errorMessage;\n\n\t\tif (exception instanceof OAuth2AuthenticationException) {\n\t\t\tOAuth2AuthenticationException authorizationException = (OAuth2AuthenticationException) exception;\n\t\t\terrorMessage = StrUtil.isBlank(authorizationException.getError().getDescription())\n\t\t\t\t\t? authorizationException.getError().getErrorCode()\n\t\t\t\t\t: authorizationException.getError().getDescription();\n\t\t}\n\t\telse {\n\t\t\terrorMessage = exception.getLocalizedMessage();\n\t\t}\n\n\t\tthis.errorHttpResponseConverter.write(R.failed(errorMessage), MediaType.APPLICATION_JSON, httpResponse);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationSuccessEventHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.support.handler;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.log.event.SysLogEvent;\nimport com.pig4cloud.pig.common.log.util.SysLogUtils;\nimport com.pig4cloud.pig.common.security.component.PigCustomOAuth2AccessTokenResponseHttpMessageConverter;\nimport com.pig4cloud.pig.common.security.service.PigUser;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.converter.HttpMessageConverter;\nimport org.springframework.http.server.ServletServerHttpResponse;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.oauth2.core.OAuth2AccessToken;\nimport org.springframework.security.oauth2.core.OAuth2RefreshToken;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;\nimport org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;\nimport org.springframework.security.web.authentication.AuthenticationSuccessHandler;\nimport org.springframework.util.CollectionUtils;\n\nimport java.io.IOException;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Map;\n\n/**\n * 处理认证成功事件的处理器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\npublic class PigAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler {\n\n\tprivate final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new PigCustomOAuth2AccessTokenResponseHttpMessageConverter();\n\n\t/**\n\t * 用户认证成功时调用\n\t * @param request 触发认证成功的请求\n\t * @param response 响应对象\n\t * @param authentication 认证过程中创建的认证对象\n\t */\n\t@SneakyThrows\n\t@Override\n\tpublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,\n\t\t\tAuthentication authentication) {\n\t\tOAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;\n\t\tMap<String, Object> map = accessTokenAuthentication.getAdditionalParameters();\n\t\tif (MapUtil.isNotEmpty(map)) {\n\t\t\t// 发送异步日志事件\n\t\t\tPigUser userInfo = (PigUser) map.get(SecurityConstants.DETAILS_USER);\n\t\t\tlog.info(\"用户：{} 登录成功\", userInfo.getName());\n\t\t\tSecurityContextHolder.getContext().setAuthentication(accessTokenAuthentication);\n\t\t\tSysLog logVo = SysLogUtils.getSysLog();\n\t\t\tlogVo.setTitle(\"登录成功\");\n\t\t\tString startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);\n\t\t\tif (StrUtil.isNotBlank(startTimeStr)) {\n\t\t\t\tLong startTime = Long.parseLong(startTimeStr);\n\t\t\t\tLong endTime = System.currentTimeMillis();\n\t\t\t\tlogVo.setTime(endTime - startTime);\n\t\t\t}\n\t\t\tlogVo.setCreateBy(userInfo.getName());\n\t\t\tSpringContextHolder.publishEvent(new SysLogEvent(logVo));\n\t\t}\n\n\t\t// 输出token\n\t\tsendAccessTokenResponse(request, response, authentication);\n\t}\n\n\t/**\n\t * 发送访问令牌响应\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param authentication 认证信息\n\t * @throws IOException 写入响应时可能抛出IO异常\n\t */\n\tprivate void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,\n\t\t\tAuthentication authentication) throws IOException {\n\n\t\tOAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;\n\n\t\tOAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();\n\t\tOAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();\n\t\tMap<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();\n\n\t\tOAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())\n\t\t\t.tokenType(accessToken.getTokenType())\n\t\t\t.scopes(accessToken.getScopes());\n\t\tif (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {\n\t\t\tbuilder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));\n\t\t}\n\t\tif (refreshToken != null) {\n\t\t\tbuilder.refreshToken(refreshToken.getTokenValue());\n\t\t}\n\t\tif (!CollectionUtils.isEmpty(additionalParameters)) {\n\t\t\tbuilder.additionalParameters(additionalParameters);\n\t\t}\n\t\tOAuth2AccessTokenResponse accessTokenResponse = builder.build();\n\t\tServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);\n\n\t\t// 无状态 注意删除 context 上下文的信息\n\t\tSecurityContextHolder.clearContext();\n\n\t\tthis.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigLogoutSuccessEventHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.auth.support.handler;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.core.util.WebUtils;\nimport com.pig4cloud.pig.common.log.event.SysLogEvent;\nimport com.pig4cloud.pig.common.log.util.SysLogUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.ApplicationListener;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.security.authentication.event.LogoutSuccessEvent;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;\nimport org.springframework.stereotype.Component;\n\n/**\n * 处理用户退出成功事件处理器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\n@Component\npublic class PigLogoutSuccessEventHandler implements ApplicationListener<LogoutSuccessEvent> {\n\n\t/**\n\t * 处理登出成功事件\n\t * @param event 登出成功事件\n\t */\n\t@Override\n\tpublic void onApplicationEvent(LogoutSuccessEvent event) {\n\t\tAuthentication authentication = (Authentication) event.getSource();\n\t\tif (authentication instanceof PreAuthenticatedAuthenticationToken) {\n\t\t\thandle(authentication);\n\t\t}\n\t}\n\n\t/**\n\t * 处理退出成功方法\n\t * <p>\n\t * 获取到登录的authentication 对象\n\t * @param authentication 登录对象\n\t */\n\tpublic void handle(Authentication authentication) {\n\t\tlog.info(\"用户：{} 退出成功\", authentication.getPrincipal());\n\t\tSysLog logVo = SysLogUtils.getSysLog();\n\t\tlogVo.setTitle(\"退出成功\");\n\n\t\t// 设置对应的token\n\t\tWebUtils.getRequest().ifPresent(request -> {\n\t\t\tlogVo.setParams(request.getHeader(HttpHeaders.AUTHORIZATION));\n\t\t\t// 计算请求耗时\n\t\t\tString startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);\n\t\t\tif (StrUtil.isNotBlank(startTimeStr)) {\n\t\t\t\tLong startTime = Long.parseLong(startTimeStr);\n\t\t\t\tLong endTime = System.currentTimeMillis();\n\t\t\t\tlogVo.setTime(endTime - startTime);\n\t\t\t}\n\t\t});\n\n\t\t// 这边设置ServiceId\n\t\tif (authentication instanceof PreAuthenticatedAuthenticationToken) {\n\t\t\tlogVo.setServiceId(authentication.getCredentials().toString());\n\t\t}\n\t\tlogVo.setCreateBy(authentication.getName());\n\t\t// 发送异步日志事件\n\t\tSpringContextHolder.publishEvent(new SysLogEvent(logVo));\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/SsoLogoutSuccessHandler.java",
    "content": "package com.pig4cloud.pig.auth.support.handler;\n\nimport cn.hutool.core.util.StrUtil;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.web.authentication.logout.LogoutSuccessHandler;\n\nimport java.io.IOException;\n\n/**\n * SSO 登出成功处理器，根据客户端传入的跳转地址进行重定向\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic class SsoLogoutSuccessHandler implements LogoutSuccessHandler {\n\n\tprivate static final String REDIRECT_URL = \"redirect_url\";\n\n\t/**\n\t * 登出成功处理逻辑\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param authentication 认证信息\n\t * @throws IOException 重定向失败时抛出IO异常\n\t */\n\t@Override\n\tpublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)\n\t\t\tthrows IOException {\n\t\tif (response == null) {\n\t\t\treturn;\n\t\t}\n\n\t\t// 获取请求参数中是否包含 回调地址\n\t\tString redirectUrl = request.getParameter(REDIRECT_URL);\n\t\tif (StrUtil.isNotBlank(redirectUrl)) {\n\t\t\tresponse.sendRedirect(redirectUrl);\n\t\t}\n\t\telse if (StrUtil.isNotBlank(request.getHeader(HttpHeaders.REFERER))) {\n\t\t\t// 默认跳转referer 地址\n\t\t\tString referer = request.getHeader(HttpHeaders.REFERER);\n\t\t\tresponse.sendRedirect(referer);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java",
    "content": "package com.pig4cloud.pig.auth.support.password;\n\nimport com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;\nimport com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.security.oauth2.core.OAuth2ErrorCodes;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Map;\nimport java.util.Set;\n\nimport static com.pig4cloud.pig.common.core.constant.SecurityConstants.PASSWORD;\n\n/**\n * OAuth2 资源所有者密码认证转换器\n *\n * @author lengleng\n * @author jumuning\n * @date 2025/05/30\n */\npublic class OAuth2ResourceOwnerPasswordAuthenticationConverter\n\t\textends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerPasswordAuthenticationToken> {\n\n\t/**\n\t * 支持密码模式\n\t * @param grantType 授权类型\n\t */\n\t@Override\n\tpublic boolean support(String grantType) {\n\t\treturn PASSWORD.equals(grantType);\n\t}\n\n\t/**\n\t * 构建OAuth2资源所有者密码认证令牌\n\t * @param clientPrincipal 客户端主体认证信息\n\t * @param requestedScopes 请求的作用域集合\n\t * @param additionalParameters 附加参数映射\n\t * @return 构建完成的OAuth2资源所有者密码认证令牌\n\t */\n\t@Override\n\tpublic OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal,\n\t\t\tSet requestedScopes, Map additionalParameters) {\n\t\treturn new OAuth2ResourceOwnerPasswordAuthenticationToken(new AuthorizationGrantType(PASSWORD), clientPrincipal,\n\t\t\t\trequestedScopes, additionalParameters);\n\t}\n\n\t/**\n\t * 校验扩展参数 密码模式密码必须不为空\n\t * @param request 参数列表\n\t */\n\t@Override\n\tpublic void checkParams(HttpServletRequest request) {\n\t\tMultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);\n\t\t// username (REQUIRED)\n\t\tString username = parameters.getFirst(OAuth2ParameterNames.USERNAME);\n\t\tif (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {\n\t\t\tOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME,\n\t\t\t\t\tOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);\n\t\t}\n\n\t\t// password (REQUIRED)\n\t\tString password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);\n\t\tif (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {\n\t\t\tOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD,\n\t\t\t\t\tOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java",
    "content": "package com.pig4cloud.pig.auth.support.password;\n\nimport com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.core.OAuth2ErrorCodes;\nimport org.springframework.security.oauth2.core.OAuth2Token;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.client.RegisteredClient;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;\n\nimport java.util.Map;\n\nimport static com.pig4cloud.pig.common.core.constant.SecurityConstants.PASSWORD;\n\n/**\n * OAuth2 资源所有者密码认证提供者\n *\n * @author lengleng\n * @author jumuning\n * @date 2025/05/30\n * @since 0.2.3\n */\npublic class OAuth2ResourceOwnerPasswordAuthenticationProvider\n\t\textends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerPasswordAuthenticationToken> {\n\n\tprivate static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerPasswordAuthenticationProvider.class);\n\n\t/**\n\t * 使用提供的参数构造一个OAuth2ResourceOwnerPasswordAuthenticationProvider\n\t * @param authenticationManager 认证管理器\n\t * @param authorizationService 授权服务\n\t * @param tokenGenerator 令牌生成器\n\t * @since 0.2.3\n\t */\n\tpublic OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager,\n\t\t\tOAuth2AuthorizationService authorizationService,\n\t\t\tOAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {\n\t\tsuper(authenticationManager, authorizationService, tokenGenerator);\n\t}\n\n\t/**\n\t * 构建用户名密码认证令牌\n\t * @param reqParameters 请求参数映射，包含用户名和密码\n\t * @return 用户名密码认证令牌\n\t */\n\t@Override\n\tpublic UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {\n\t\tString username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME);\n\t\tString password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD);\n\t\treturn new UsernamePasswordAuthenticationToken(username, password);\n\t}\n\n\t/**\n\t * 判断是否支持指定的认证类型\n\t * @param authentication 待验证的认证类型\n\t * @return 如果支持该认证类型则返回true，否则返回false\n\t */\n\t@Override\n\tpublic boolean supports(Class<?> authentication) {\n\t\tboolean supports = OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);\n\t\tLOGGER.debug(\"supports authentication=\" + authentication + \" returning \" + supports);\n\t\treturn supports;\n\t}\n\n\t/**\n\t * 检查客户端是否支持密码授权模式\n\t * @param registeredClient 已注册的客户端\n\t * @throws OAuth2AuthenticationException 当客户端不支持密码授权模式时抛出异常\n\t */\n\t@Override\n\tpublic void checkClient(RegisteredClient registeredClient) {\n\t\tassert registeredClient != null;\n\t\tif (!registeredClient.getAuthorizationGrantTypes().contains(new AuthorizationGrantType(PASSWORD))) {\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java",
    "content": "package com.pig4cloud.pig.auth.support.password;\n\nimport com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\n\nimport java.io.Serial;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * OAuth2资源所有者密码认证令牌\n *\n * @author lengleng\n * @author jumuning\n * @date 2025/05/30\n */\npublic class OAuth2ResourceOwnerPasswordAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 构造OAuth2资源所有者密码认证令牌\n\t * @param authorizationGrantType 授权类型\n\t * @param clientPrincipal 客户端认证主体\n\t * @param scopes 权限范围集合\n\t * @param additionalParameters 附加参数映射\n\t */\n\tpublic OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType authorizationGrantType,\n\t\t\tAuthentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {\n\t\tsuper(authorizationGrantType, clientPrincipal, scopes, additionalParameters);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/package-info.java",
    "content": "/**\n * 密码模式\n */\npackage com.pig4cloud.pig.auth.support.password;\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java",
    "content": "package com.pig4cloud.pig.auth.support.sms;\n\nimport com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.security.oauth2.core.OAuth2ErrorCodes;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * @author lengleng\n * @date 2022-05-31\n *\n * 短信登录转换器\n */\npublic class OAuth2ResourceOwnerSmsAuthenticationConverter\n\t\textends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerSmsAuthenticationToken> {\n\n\t/**\n\t * 是否支持此convert\n\t * @param grantType 授权类型\n\t * @return\n\t */\n\t@Override\n\tpublic boolean support(String grantType) {\n\t\treturn SecurityConstants.MOBILE.equals(grantType);\n\t}\n\n\t@Override\n\tpublic OAuth2ResourceOwnerSmsAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes,\n\t\t\tMap additionalParameters) {\n\t\treturn new OAuth2ResourceOwnerSmsAuthenticationToken(new AuthorizationGrantType(SecurityConstants.MOBILE),\n\t\t\t\tclientPrincipal, requestedScopes, additionalParameters);\n\t}\n\n\t/**\n\t * 校验扩展参数 密码模式密码必须不为空\n\t * @param request 参数列表\n\t */\n\t@Override\n\tpublic void checkParams(HttpServletRequest request) {\n\t\tMultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);\n\t\t// PHONE (REQUIRED)\n\t\tString phone = parameters.getFirst(SecurityConstants.SMS_PARAMETER_NAME);\n\t\tif (!StringUtils.hasText(phone) || parameters.get(SecurityConstants.SMS_PARAMETER_NAME).size() != 1) {\n\t\t\tOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.SMS_PARAMETER_NAME,\n\t\t\t\t\tOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java",
    "content": "package com.pig4cloud.pig.auth.support.sms;\n\nimport com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.core.OAuth2ErrorCodes;\nimport org.springframework.security.oauth2.core.OAuth2Token;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.client.RegisteredClient;\nimport org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;\n\nimport java.util.Map;\n\n/**\n * @author lengleng\n * @date date\n *\n * 短信登录的核心处理\n */\npublic class OAuth2ResourceOwnerSmsAuthenticationProvider\n\t\textends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerSmsAuthenticationToken> {\n\n\tprivate static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerSmsAuthenticationProvider.class);\n\n\t/**\n\t * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the\n\t * provided parameters.\n\t * @param authenticationManager\n\t * @param authorizationService the authorization service\n\t * @param tokenGenerator the token generator\n\t * @since 0.2.3\n\t */\n\tpublic OAuth2ResourceOwnerSmsAuthenticationProvider(AuthenticationManager authenticationManager,\n\t\t\tOAuth2AuthorizationService authorizationService,\n\t\t\tOAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {\n\t\tsuper(authenticationManager, authorizationService, tokenGenerator);\n\t}\n\n\t@Override\n\tpublic boolean supports(Class<?> authentication) {\n\t\tboolean supports = OAuth2ResourceOwnerSmsAuthenticationToken.class.isAssignableFrom(authentication);\n\t\tLOGGER.debug(\"supports authentication=\" + authentication + \" returning \" + supports);\n\t\treturn supports;\n\t}\n\n\t@Override\n\tpublic void checkClient(RegisteredClient registeredClient) {\n\t\tassert registeredClient != null;\n\t\tif (!registeredClient.getAuthorizationGrantTypes()\n\t\t\t.contains(new AuthorizationGrantType(SecurityConstants.MOBILE))) {\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);\n\t\t}\n\t}\n\n\t@Override\n\tpublic UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {\n\t\tString phone = (String) reqParameters.get(SecurityConstants.SMS_PARAMETER_NAME);\n\t\treturn new UsernamePasswordAuthenticationToken(phone, null);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java",
    "content": "package com.pig4cloud.pig.auth.support.sms;\n\nimport java.io.Serial;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\n\nimport com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken;\n\n/**\n * @author lengleng\n * @description 短信登录token信息\n */\npublic class OAuth2ResourceOwnerSmsAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tpublic OAuth2ResourceOwnerSmsAuthenticationToken(AuthorizationGrantType authorizationGrantType,\n\t\t\tAuthentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {\n\t\tsuper(authorizationGrantType, clientPrincipal, scopes, additionalParameters);\n\t}\n\n}\n"
  },
  {
    "path": "pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/package-info.java",
    "content": "/**\n * 短信模式\n */\npackage com.pig4cloud.pig.auth.support.sms;\n"
  },
  {
    "path": "pig-auth/src/main/resources/application.yml",
    "content": "server:\n  port: 3000\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      username: @nacos.username@\n      password: @nacos.password@\n      discovery:\n        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}\n      config:\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n  config:\n    import:\n      - nacos:application-@profiles.active@.yml\n      - nacos:${spring.application.name}-@profiles.active@.yml\n\n\n\n"
  },
  {
    "path": "pig-auth/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration debug=\"false\" scan=\"false\">\n\t<springProperty scop=\"context\" name=\"spring.application.name\" source=\"spring.application.name\" defaultValue=\"\"/>\n\t<property name=\"log.path\" value=\"logs/${spring.application.name}\"/>\n\t<!-- 彩色日志格式 -->\n\t<property name=\"CONSOLE_LOG_PATTERN\"\n\t\t\t  value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n\t<conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n\t<conversionRule conversionWord=\"wex\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n\t<conversionRule conversionWord=\"wEx\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n\t<!-- Console log output -->\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>${CONSOLE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file debug output -->\n\t<appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/debug.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file error output -->\n\t<appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/error.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t\t<filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n\t\t\t<level>ERROR</level>\n\t\t</filter>\n\t</appender>\n\n\t<!--nacos 心跳 INFO 屏蔽-->\n\t<logger name=\"com.alibaba.nacos\" level=\"OFF\">\n\t\t<appender-ref ref=\"error\"/>\n\t</logger>\n\n\t<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n\t<root level=\"DEBUG\">\n\t\t<appender-ref ref=\"console\"/>\n\t\t<appender-ref ref=\"debug\"/>\n\t\t<appender-ref ref=\"error\"/>\n\t</root>\n</configuration>\n"
  },
  {
    "path": "pig-auth/src/main/resources/templates/ftl/confirm.ftl",
    "content": "<#assign content>\n\t<div class=\"mb-7\">\n\t\t<h3 class=\"font-semibold text-2xl text-gray-800 dark:text-gray-200 text-center transition-colors\">应用授权确认</h3>\n\t\t<div class=\"mt-4 flex items-center justify-center text-sm\">\n\t\t\t<div class=\"px-4 py-2 bg-gray-50 dark:bg-gray-800 rounded-full transition-all\">\n\t\t\t\t<div class=\"text-gray-700 dark:text-gray-300\">\n                    <#if principalName==\"anonymousUser\">\n\t\t\t\t\t\t<span class=\"text-gray-500 dark:text-gray-400\">未登录用户</span>\n                    <#else>\n\t\t\t\t\t\t<a href=\"https://pig4cloud.com\" class=\"text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 font-medium transition-colors\">\n                            ${principalName}\n\t\t\t\t\t\t</a>\n                    </#if>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<form id='confirmationForm' name='confirmationForm' action=\"${request.contextPath}/oauth2/authorize\" method='post'>\n\t\t<input type=\"hidden\" name=\"client_id\" value=\"${clientId}\">\n\t\t<input type=\"hidden\" name=\"state\" value=\"${state}\">\n\n\t\t<div class=\"space-y-6\">\n\t\t\t<div class=\"mb-4\">\n\t\t\t\t<p class=\"text-gray-700 dark:text-gray-300 mb-3 transition-colors\">将获得以下权限：</p>\n\t\t\t\t<div class=\"space-y-3 bg-gray-50 dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700 transition-all\">\n                    <#list scopeList as scope>\n\t\t\t\t\t\t<div class=\"flex items-center\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" checked=\"checked\" name=\"scope\" value=\"${scope}\"\n\t\t\t\t\t\t\t       class=\"h-4 w-4 text-purple-600 focus:ring-purple-500 dark:focus:ring-purple-400 border-gray-300 dark:border-gray-600 rounded transition-colors\">\n\t\t\t\t\t\t\t<label class=\"ml-3 text-gray-600 dark:text-gray-400 transition-colors\">${scope}</label>\n\t\t\t\t\t\t</div>\n                    </#list>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class=\"text-sm text-gray-500 dark:text-gray-400 mb-4 transition-colors\">\n\t\t\t\t授权后表明你已同意\n\t\t\t\t<a href=\"#\" class=\"text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 transition-colors\">服务协议</a>\n\t\t\t</div>\n\n\t\t\t<div>\n\t\t\t\t<button type=\"submit\" id=\"write-email-btn\"\n\t\t\t\t        class=\"w-full flex justify-center bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600 text-gray-100 p-3 rounded-lg tracking-wide font-semibold cursor-pointer transition-all duration-300 transform hover:scale-[1.02]\">\n\t\t\t\t\t确认授权\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t</form>\n</#assign>\n\n<#include \"layout/base.ftl\">\n"
  },
  {
    "path": "pig-auth/src/main/resources/templates/ftl/layout/base.ftl",
    "content": "<!doctype html>\n<html class=\"dark\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title><#if title??>${title}<#else>Pig 统一身份认证</#if></title>\n    <script src=\"https://cdn.tailwindcss.com\"></script>\n    <script>\n        tailwind.config = {\n            darkMode: 'class',\n            theme: {\n                extend: {\n                    colors: {\n                        primary: '#6366f1'\n                    }\n                }\n            }\n        }\n    </script>\n    <style>\n        .dark-mode-toggle {\n            transition: all 0.3s ease;\n        }\n        .dark-mode-toggle:hover {\n            transform: scale(1.1);\n        }\n        .animate-spin-slow {\n            animation: spin 2s linear infinite;\n        }\n    </style>\n    <#if extraHead??>${extraHead}</#if>\n</head>\n<body class=\"min-h-screen\">\n<!-- 暗黑模式切换按钮 -->\n<div class=\"fixed top-6 right-6 z-50\">\n    <button id=\"theme-toggle\" class=\"dark-mode-toggle p-3 rounded-xl bg-purple-100/90 dark:bg-gray-800/90 text-purple-600 dark:text-yellow-300 hover:bg-purple-200 dark:hover:bg-gray-700 focus:outline-none transition-all duration-300 shadow-lg\">\n        <!-- 月亮图标 -->\n        <svg id=\"moon-icon\" class=\"w-6 h-6 hidden dark:block\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n            <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z\"></path>\n        </svg>\n        <!-- 太阳图标 -->\n        <svg id=\"sun-icon\" class=\"w-6 h-6 block dark:hidden\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n            <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z\"></path>\n        </svg>\n    </button>\n</div>\n\n<!-- 背景渐变 -->\n<div class=\"bg-purple-900 fixed top-0 left-0 bg-gradient-to-b from-gray-900 via-gray-900 to-purple-800 w-full h-full -z-10\">\n</div>\n\n<div class=\"relative min-h-screen flex flex-col md:flex-row items-center justify-center px-4 py-12 md:px-8\">\n    <!-- 左侧说明文本 -->\n    <div class=\"flex-1 max-w-xl mb-8 md:mb-0 md:mr-12 z-10\">\n        <div class=\"hidden md:block\">\n            <h2 class=\"text-3xl font-bold text-white mb-6\">统一身份认证平台</h2>\n            <p class=\"text-gray-300 text-lg leading-relaxed opacity-90\">\n                为企业提供一套集中式的账号、权限、认证、审计工具，帮助企业打通身份数据孤岛，实现\"一个账号、一次认证、多点通行\"的效果，强化企业安全体系的同时，提升组织管理效率，助力企业数字化升级转型。\n            </p>\n        </div>\n    </div>\n\n    <!-- 右侧内容区 -->\n    <div class=\"flex-1 w-full max-w-md z-10\">\n        <div class=\"p-8 md:p-10 bg-white dark:bg-gray-900 rounded-2xl shadow-2xl backdrop-blur-sm transition-all duration-300\">\n            <#if content??>${content}</#if>\n\n            <!-- 版权信息 -->\n            <div class=\"mt-8 pt-6 text-center text-gray-400 dark:text-gray-500 text-sm border-t border-gray-100 dark:border-gray-800\">\n                <span>\n                    Copyright © 2021-2025\n                    <a href=\"#\" class=\"text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 transition-colors\">PIGCLOUD</a>\n                </span>\n            </div>\n        </div>\n    </div>\n</div>\n\n<!-- 底部波浪装饰 -->\n<svg class=\"fixed bottom-0 left-0 w-full transition-colors -z-5\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1440 320\">\n    <path class=\"fill-white dark:fill-gray-900 transition-colors\"\n          d=\"M0,0L40,42.7C80,85,160,171,240,197.3C320,224,400,192,480,154.7C560,117,640,75,720,74.7C800,75,880,117,960,154.7C1040,192,1120,224,1200,213.3C1280,203,1360,149,1400,122.7L1440,96L1440,320L1400,320C1360,320,1280,320,1200,320C1120,320,1040,320,960,320C880,320,800,320,720,320C640,320,560,320,480,320C400,320,320,320,240,320C160,320,80,320,40,320L0,320Z\">\n    </path>\n</svg>\n\n<script>\n    // 主题切换功能\n    const themeToggle = document.getElementById('theme-toggle');\n    const html = document.documentElement;\n\n    // 检查本地存储中的主题设置\n    if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {\n        html.classList.add('dark');\n    } else {\n        html.classList.remove('dark');\n    }\n\n    // 切换主题\n    themeToggle.addEventListener('click', () => {\n        html.classList.toggle('dark');\n\n        // 保存主题设置到本地存储\n        if (html.classList.contains('dark')) {\n            localStorage.theme = 'dark';\n        } else {\n            localStorage.theme = 'light';\n        }\n\n        // 添加点击动画效果\n        themeToggle.classList.add('animate-spin-slow');\n        setTimeout(() => {\n            themeToggle.classList.remove('animate-spin-slow');\n        }, 300);\n    });\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "pig-auth/src/main/resources/templates/ftl/login.ftl",
    "content": "<#assign content>\n    <div class=\"mb-8 text-center\">\n        <svg class=\"w-16 h-16 mx-auto mb-4 text-purple-600 dark:text-purple-400\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M12 14.5V16.5M7 10.0288C7.47142 10.0288 7.86284 9.63734 7.86284 9.16592C7.86284 8.69449 7.47142 8.30307 7 8.30307C6.52858 8.30307 6.13716 8.69449 6.13716 9.16592C6.13716 9.63734 6.52858 10.0288 7 10.0288ZM17 10.0288C17.4714 10.0288 17.8628 9.63734 17.8628 9.16592C17.8628 8.69449 17.4714 8.30307 17 8.30307C16.5286 8.30307 16.1372 8.69449 16.1372 9.16592C16.1372 9.63734 16.5286 10.0288 17 10.0288ZM12 12.5C13.6569 12.5 15 11.1569 15 9.5C15 7.84315 13.6569 6.5 12 6.5C10.3431 6.5 9 7.84315 9 9.5C9 11.1569 10.3431 12.5 12 12.5Z\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n            <path d=\"M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n            <path d=\"M17.6972 19.7C16.0993 18.0307 14.125 17 12 17C9.87499 17 7.90072 18.0307 6.30283 19.7\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n        </svg>\n        <p class=\"mt-2 text-sm text-gray-600 dark:text-gray-400\">安全便捷的企业级认证服务</p>\n    </div>\n\n    <form class=\"form-signin\" action=\"${request.contextPath}/oauth2/form\" method=\"post\">\n        <input type=\"hidden\" name=\"client_id\" value=\"pig\">\n        <input type=\"hidden\" name=\"grant_type\" value=\"password\">\n        <div class=\"space-y-6\">\n            <div class=\"\">\n                <input class=\"w-full text-sm px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:border-purple-400 dark:focus:border-purple-500 dark:text-gray-300 transition-colors\"\n                       type=\"text\" placeholder=\"账号\" name=\"username\" required>\n            </div>\n\n            <div class=\"relative\">\n                <input placeholder=\"密码\" type=\"password\" name=\"password\" required\n                       class=\"w-full text-sm px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:border-purple-400 dark:focus:border-purple-500 dark:text-gray-300 transition-colors\">\n            </div>\n\n            <#if error??>\n                <div class=\"relative text-center\">\n                    <span class=\"text-red-600 dark:text-red-400\">${error}</span>\n                </div>\n            </#if>\n\n            <div>\n                <button type=\"submit\"\n                        class=\"w-full flex justify-center bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600 text-gray-100 p-3 rounded-lg tracking-wide font-semibold cursor-pointer transition-all duration-300 transform hover:scale-[1.02]\">\n                    登 录\n                </button>\n            </div>\n        </div>\n    </form>\n</#assign>\n\n<#include \"layout/base.ftl\">\n"
  },
  {
    "path": "pig-boot/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-boot\n\nARG JAR_FILE=target/pig-boot.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 9999\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-boot/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. ~ ~ Licensed \n\tunder the Apache License, Version 2.0 (the \"License\"); ~ you may not use \n\tthis file except in compliance with the License. ~ You may obtain a copy \n\tof the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless \n\trequired by applicable law or agreed to in writing, software ~ distributed \n\tunder the License is distributed on an \"AS IS\" BASIS, ~ WITHOUT WARRANTIES \n\tOR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for \n\tthe specific language governing permissions and ~ limitations under the License. -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txmlns=\"http://maven.apache.org/POM/4.0.0\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>com.pig4cloud</groupId>\n\t\t<artifactId>pig</artifactId>\n\t\t<version>${revision}</version>\n\t</parent>\n\n\t<artifactId>pig-boot</artifactId>\n\t<packaging>jar</packaging>\n\n\t<description>pig 单体版本启动</description>\n\n\t<dependencies>\n\t\t<!--必备：认证中心模块 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-auth</artifactId>\n\t\t\t<version>${revision}</version>\n\t\t</dependency>\n\t\t<!--必备：用户管理模块 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-upms-biz</artifactId>\n\t\t\t<version>${revision}</version>\n\t\t</dependency>\n\t\t<!--可选：代码生成模块 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-codegen</artifactId>\n\t\t\t<version>${revision}</version>\n\t\t</dependency>\n\t\t<!--可选：定时任务模块 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-quartz</artifactId>\n\t\t\t<version>${revision}</version>\n\t\t</dependency>\n\t\t<!--安全模块 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-common-security</artifactId>\n\t\t</dependency>\n\t\t<!-- 接口文档UI -->\n\t\t<dependency>\n\t\t\t<groupId>org.springdoc</groupId>\n\t\t\t<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springdoc</groupId>\n\t\t\t<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>\n\t\t</dependency>\n\t\t<!-- knife4j -->\n\t\t<dependency>\n\t\t\t<groupId>com.github.xiaoymin</groupId>\n\t\t\t<artifactId>knife4j-openapi3-ui</artifactId>\n\t\t</dependency>\n\t\t<!--接口文档 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-common-swagger</artifactId>\n\t\t</dependency>\n\t\t<!--undertow容器 -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-undertow</artifactId>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t\t<artifactId>docker-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "pig-boot/src/main/java/com/pig4cloud/pig/PigBootApplication.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig;\n\nimport com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;\nimport com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n/**\n * 单体版本启动器，运行此模块即可启动整个系统\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@SpringBootApplication\n@EnablePigResourceServer\n@EnablePigDoc(value = \"admin\", isMicro = false)\npublic class PigBootApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigBootApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-boot/src/main/resources/application-dev.yml",
    "content": "spring:\n  cache:\n    type: redis # 缓存类型 Redis\n  data:\n    redis:\n      database: 5\n      host: 127.0.0.1\n  # 数据库相关配置\n  datasource:\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://127.0.0.1:3306/pig?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true\n\n# 本地文件系统\nfile:\n  local:\n    enable: true\n    base-path: /Users/lengleng/Downloads/img\n\n## 登录配置\nsecurity:\n  # 登录报文加密根密钥 ，必须是16位\n  encodeKey: thanks,pig4cloud\n  # 跳过验证码校验的客户端\n  ignore-clients:\n    - test\n\n# 配置文件加密根密码\njasypt:\n  encryptor:\n    password: pig  # 加密根密码\n    algorithm: PBEWithMD5AndDES  # 加密算法\n    iv-generator-classname: org.jasypt.iv.NoIvGenerator # 无向量生成器\n\n# 短信插件配置：https://www.yuque.com/vxixfq/pig/zw8udk\nsms:\n  is-print: false # 是否打印日志\n  config-type: yaml # 配置类型，yaml"
  },
  {
    "path": "pig-boot/src/main/resources/application.yml",
    "content": "server:\n  port: 9999    # 项目端口\n  servlet:\n    context-path: /admin  # 项目访问路径\n\nspring:\n  application:\n    name: @project.artifactId@  # 服务名称，取 pom.xml 中的 artifactId\n  # 上传文件大小限制\n  servlet:\n    multipart:\n      max-file-size: 100MB # 单个文件最大\n      max-request-size: 100MB # 接收的最大请求大小\n  cloud:\n    nacos:  # 单机版本关闭nacos 服务发现和配置管理的能力\n      config:\n        enabled: false\n      discovery:\n        enabled: false\n  freemarker: # freemarker 配置，授权码模式页面渲染使用\n    suffix: .ftl\n    template-loader-path: classpath:/templates/\n    request-context-attribute: request\n  main:\n    allow-bean-definition-overriding: true # 允许覆盖bean定义\n  profiles:\n    active: dev  # 激活dev，对应 application-dev.yml\n\n\n## spring security 对外暴露接口设置（不鉴权直接可访问）\nsecurity:\n  micro: false\n  oauth2:\n    ignore:\n      urls:\n        - /webjars/**\n        - /v3/api-docs/**\n        - /doc.html\n        - /swagger-ui.html\n        - /swagger-ui/**\n        - /swagger-resources\n        - /code/image\n        - /error\n        - /token/**\n        - /actuator/**\n\n#--------------如下配置尽量不要变动-------------\n# swagger 配置\nswagger:\n  token-url: ${swagger.gateway}/admin/oauth2/token\n\n# mybatis-plus 配置\nmybatis-plus:\n  mapper-locations: classpath*:/mapper/*Mapper.xml # mapper文件位置\n  global-config:\n    banner: false # 是否打印 mybatis-plus banner\n    db-config:\n      id-type: auto  # 主键类型\n      where-strategy: not_empty # where 条件策略\n      insert-strategy: not_empty # 插入策略\n      update-strategy: not_null  # 更新策略\n  type-handlers-package: com.pig4cloud.pig.common.mybatis.handler # 类型处理器包\n  configuration:\n    jdbc-type-for-null: 'null' # 是否设置字段为null\n    call-setters-on-nulls: true # 是否调用set方法时传入null值\n    shrink-whitespaces-in-sql: true # 去掉sql中多余的空格\n"
  },
  {
    "path": "pig-boot/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n    小技巧: 在根pom里面设置统一存放路径，统一管理方便维护\n    <properties>\n        <log-path>/Users/lengleng</log-path>\n    </properties>\n    1. 其他模块加日志输出，直接copy本文件放在resources 目录即可\n    2. 注意修改 <property name=\"${log-path}/log.path\" value=\"\"/> 的value模块\n-->\n<configuration debug=\"false\" scan=\"false\">\n    <property name=\"log.path\" value=\"logs/${project.artifactId}\"/>\n    <!-- 彩色日志格式 -->\n    <property name=\"CONSOLE_LOG_PATTERN\"\n              value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n    <!-- 彩色日志依赖的渲染类 -->\n    <conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n    <conversionRule conversionWord=\"wex\"\n                    class=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n    <conversionRule conversionWord=\"wEx\"\n                    class=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n    <!-- Console log output -->\n    <appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>${CONSOLE_LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Log file debug output -->\n    <appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/debug.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n            <maxFileSize>50MB</maxFileSize>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${CONSOLE_LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Log file error output -->\n    <appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/error.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n            <maxFileSize>50MB</maxFileSize>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${CONSOLE_LOG_PATTERN}</pattern>\n        </encoder>\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>ERROR</level>\n        </filter>\n    </appender>\n\n\n\n    <!--nacos 心跳 INFO 屏蔽-->\n    <logger name=\"com.alibaba.nacos\" level=\"OFF\">\n        <appender-ref ref=\"error\"/>\n    </logger>\n\n\n    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n    <root level=\"INFO\">\n        <appender-ref ref=\"console\"/>\n        <appender-ref ref=\"debug\"/>\n        <appender-ref ref=\"error\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "pig-common/pig-common-bom/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>com.pig4cloud</groupId>\n\t<artifactId>pig-common-bom</artifactId>\n\t<version>${revision}</version>\n\t<packaging>pom</packaging>\n\n\t<name>pig-common-bom</name>\n\t<url>pig cloud parent</url>\n\t<description>pig cloud parent</description>\n\n\t<properties>\n\t\t<!-- 项目版本号 -->\n\t\t<revision>3.9.2</revision>\n\t\t<asm.version>7.1</asm.version>\n\t\t<sms.version>3.3.5</sms.version>\n\t\t<aws.version>2.29.45</aws.version>\n\t\t<mysql.version>9.2.0</mysql.version>\n\t\t<seata.version>1.7.0</seata.version>\n\t\t<excel.version>3.4.3</excel.version>\n\t\t<screw.version>0.0.3</screw.version>\n\t\t<hutool.version>5.8.42</hutool.version>\n\t\t<velocity.version>2.4</velocity.version>\n\t\t<captcha.version>2.2.5</captcha.version>\n\t\t<knife4j.version>4.5.0</knife4j.version>\n\t\t<gateway.version>4.3.2</gateway.version>\n\t\t<sentinel.version>1.8.4</sentinel.version>\n\t\t<git.commit.plugin>9.0.1</git.commit.plugin>\n\t\t<springdoc.version>2.8.14</springdoc.version>\n\t\t<common.io.version>2.18.0</common.io.version>\n\t\t<dynamic-ds.version>4.5.0</dynamic-ds.version>\n\t\t<swagger.fox.version>3.0.0</swagger.fox.version>\n\t\t<maven.compiler.source>17</maven.compiler.source>\n\t\t<maven.compiler.target>17</maven.compiler.target>\n\t\t<nacos.client.version>3.1.0</nacos.client.version>\n\t\t<velocity.tool.version>3.1</velocity.tool.version>\n\t\t<configuration.version>1.10</configuration.version>\n\t\t<swagger.core.version>2.2.39</swagger.core.version>\n\t\t<mybatis-plus.version>3.5.16</mybatis-plus.version>\n\t\t<shardingsphere.version>5.4.1</shardingsphere.version>\n\t\t<fastjson.version>1.2.83_noneautotype</fastjson.version>\n\t\t<spring.checkstyle.plugin>0.0.47</spring.checkstyle.plugin>\n\t\t<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n\n\t<!-- 定义全局jar版本,模块使用需要再次引入但不用写版本号 -->\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-core</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-datasource</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-log</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-mybatis</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-oss</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-security</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-feign</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-swagger</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-xss</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-excel</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-websocket</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-upms-api</artifactId>\n\t\t\t\t<version>${revision}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.mysql</groupId>\n\t\t\t\t<artifactId>mysql-connector-j</artifactId>\n\t\t\t\t<version>${mysql.version}</version>\n\t\t\t</dependency>\n\t\t\t<!--springdoc -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springdoc</groupId>\n\t\t\t\t<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>\n\t\t\t\t<version>${springdoc.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springdoc</groupId>\n\t\t\t\t<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n\t\t\t\t<version>${springdoc.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springdoc</groupId>\n\t\t\t\t<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>\n\t\t\t\t<version>${springdoc.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>io.swagger.core.v3</groupId>\n\t\t\t\t<artifactId>swagger-annotations-jakarta</artifactId>\n\t\t\t\t<version>${swagger.core.version}</version>\n\t\t\t</dependency>\n\t\t\t<!--fastjson 版本 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.alibaba</groupId>\n\t\t\t\t<artifactId>fastjson</artifactId>\n\t\t\t\t<version>${fastjson.version}</version>\n\t\t\t</dependency>\n\t\t\t<!--代码生成模板引擎 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.apache.velocity</groupId>\n\t\t\t\t<artifactId>velocity-engine-core</artifactId>\n\t\t\t\t<version>${velocity.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.apache.velocity.tools</groupId>\n\t\t\t\t<artifactId>velocity-tools-generic</artifactId>\n\t\t\t\t<version>${velocity.tool.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- 调用验证码核心模块 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud.plugin</groupId>\n\t\t\t\t<artifactId>captcha-core</artifactId>\n\t\t\t\t<version>${captcha.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- excel 导入导出 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud.excel</groupId>\n\t\t\t\t<artifactId>excel-spring-boot-starter</artifactId>\n\t\t\t\t<version>${excel.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- commons.io 覆盖easyexcel -->\n\t\t\t<dependency>\n\t\t\t\t<artifactId>commons-io</artifactId>\n\t\t\t\t<groupId>commons-io</groupId>\n\t\t\t\t<version>${common.io.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.apache.shardingsphere</groupId>\n\t\t\t\t<artifactId>shardingsphere-jdbc-core</artifactId>\n\t\t\t\t<version>${shardingsphere.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- 多数据源依赖 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.baomidou</groupId>\n\t\t\t\t<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>\n\t\t\t\t<version>${dynamic-ds.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- 短信工具：https://sms4j.com -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.dromara.sms4j</groupId>\n\t\t\t\t<artifactId>sms4j-spring-boot-starter</artifactId>\n\t\t\t\t<version>${sms.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- 网关 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-gateway-server</artifactId>\n\t\t\t\t<version>${gateway.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- knife4j -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.github.xiaoymin</groupId>\n\t\t\t\t<artifactId>knife4j-openapi3-ui</artifactId>\n\t\t\t\t<version>${knife4j.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- s3 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>software.amazon.awssdk</groupId>\n\t\t\t\t<artifactId>s3</artifactId>\n\t\t\t\t<version>${aws.version}</version>\n\t\t\t</dependency>\n\t\t\t<!--Nacos fix: https://gitee.com/log4j/pig/issues/ID2OA3-->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.alibaba.nacos</groupId>\n\t\t\t\t<artifactId>nacos-client</artifactId>\n\t\t\t\t<version>${nacos.client.version}</version>\n\t\t\t</dependency>\n\t\t\t<!--orm 相关 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.baomidou</groupId>\n\t\t\t\t<artifactId>mybatis-plus-bom</artifactId>\n\t\t\t\t<version>${mybatis-plus.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<!-- hutool bom 工具类 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>cn.hutool</groupId>\n\t\t\t\t<artifactId>hutool-bom</artifactId>\n\t\t\t\t<version>${hutool.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<!--打包jar 与git commit 关联插件 -->\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.github.git-commit-id</groupId>\n\t\t\t\t<artifactId>git-commit-id-maven-plugin</artifactId>\n\t\t\t\t<version>${git.commit.plugin}</version>\n\t\t\t</plugin>\n\t\t\t<!--代码格式插件，默认使用spring 规则 -->\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.spring.javaformat</groupId>\n\t\t\t\t<artifactId>spring-javaformat-maven-plugin</artifactId>\n\t\t\t\t<version>${spring.checkstyle.plugin}</version>\n\t\t\t</plugin>\n\t\t\t<!-- 统一 revision 版本 -->\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.codehaus.mojo</groupId>\n\t\t\t\t<artifactId>flatten-maven-plugin</artifactId>\n\t\t\t\t<version>${flatten-maven-plugin.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<flattenMode>resolveCiFriendliesOnly</flattenMode>\n\t\t\t\t\t<updatePomFile>true</updatePomFile>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>flatten</id>\n\t\t\t\t\t\t<phase>process-resources</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>flatten</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>flatten.clean</id>\n\t\t\t\t\t\t<phase>clean</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>clean</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-core</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 公共工具类核心包</description>\n\n\n    <dependencies>\n        <!--hutool-->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-core</artifactId>\n        </dependency>\n        <!--redis-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-redis</artifactId>\n        </dependency>\n        <!--server-api-->\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n        </dependency>\n        <!--hibernate-validator-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <!--json模块-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-json</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-commons</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/JacksonConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.config;\n\nimport cn.hutool.core.date.DatePattern;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.pig4cloud.pig.common.core.jackson.PigJavaTimeModule;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;\nimport org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\nimport java.time.ZoneId;\nimport java.util.Locale;\nimport java.util.TimeZone;\n\n/**\n * Jackson配置类，用于自定义Jackson的ObjectMapper配置\n *\n * @author lengleng\n * @author L.cm\n * @author lishangbu\n * @date 2025/05/30\n */\n@AutoConfiguration\n@ConditionalOnClass(ObjectMapper.class)\n@AutoConfigureBefore(JacksonAutoConfiguration.class)\npublic class JacksonConfiguration {\n\n\t/**\n\t * 自定义Jackson2ObjectMapperBuilder配置\n\t * @return Jackson2ObjectMapperBuilderCustomizer实例，包含以下配置： 1. 设置地区为中国 2. 设置系统默认时区 3.\n\t * 设置默认日期时间格式 4. 配置Long类型序列化为字符串 5. 注册自定义时间模块\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic Jackson2ObjectMapperBuilderCustomizer customizer() {\n\t\treturn builder -> {\n\t\t\tbuilder.locale(Locale.CHINA);\n\t\t\tbuilder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));\n\t\t\tbuilder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);\n\t\t\tbuilder.serializerByType(Long.class, ToStringSerializer.instance);\n\t\t\tbuilder.modules(new PigJavaTimeModule());\n\t\t};\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RedisTemplateConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.*;\nimport org.springframework.data.redis.serializer.RedisSerializer;\n\n/**\n * Redis 配置类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@EnableCaching\n@AutoConfiguration\n@AutoConfigureBefore(RedisAutoConfiguration.class)\npublic class RedisTemplateConfiguration {\n\n\t/**\n\t * 创建并配置RedisTemplate实例\n\t * @param factory Redis连接工厂\n\t * @return 配置好的RedisTemplate实例\n\t */\n\t@Bean\n\t@Primary\n\tpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {\n\t\tRedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();\n\t\tredisTemplate.setKeySerializer(RedisSerializer.string());\n\t\tredisTemplate.setHashKeySerializer(RedisSerializer.string());\n\t\tredisTemplate.setValueSerializer(RedisSerializer.java());\n\t\tredisTemplate.setHashValueSerializer(RedisSerializer.java());\n\t\tredisTemplate.setConnectionFactory(factory);\n\t\treturn redisTemplate;\n\t}\n\n\t/**\n\t * 创建并返回HashOperations实例\n\t * @param redisTemplate Redis模板\n\t * @return HashOperations实例\n\t */\n\t@Bean\n\tpublic HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {\n\t\treturn redisTemplate.opsForHash();\n\t}\n\n\t/**\n\t * 创建并返回用于操作Redis String类型数据的ValueOperations实例\n\t * @param redisTemplate Redis模板，用于操作Redis\n\t * @return ValueOperations实例，提供对Redis String类型数据的操作\n\t */\n\t@Bean\n\tpublic ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {\n\t\treturn redisTemplate.opsForValue();\n\t}\n\n\t/**\n\t * 创建并返回ListOperations实例\n\t * @param redisTemplate Redis模板\n\t * @return ListOperations实例\n\t */\n\t@Bean\n\tpublic ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {\n\t\treturn redisTemplate.opsForList();\n\t}\n\n\t/**\n\t * 创建并返回SetOperations实例\n\t * @param redisTemplate Redis模板\n\t * @return SetOperations实例\n\t */\n\t@Bean\n\tpublic SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {\n\t\treturn redisTemplate.opsForSet();\n\t}\n\n\t/**\n\t * 创建并返回ZSetOperations实例\n\t * @param redisTemplate Redis模板对象\n\t * @return ZSetOperations实例\n\t */\n\t@Bean\n\tpublic ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {\n\t\treturn redisTemplate.opsForZSet();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RestTemplateConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.client.RestClient;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * RestTemplate 自动配置类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@AutoConfiguration\npublic class RestTemplateConfiguration {\n\n\t/**\n\t * 创建动态REST模板\n\t * @return {@link RestTemplate} REST模板实例\n\t */\n\t@Bean\n\t@LoadBalanced\n\t@ConditionalOnProperty(value = \"spring.cloud.nacos.discovery.enabled\", havingValue = \"true\", matchIfMissing = true)\n\tpublic RestTemplate restTemplate() {\n\t\treturn new RestTemplate();\n\t}\n\n\t/**\n\t * 创建支持负载均衡的REST客户端构建器\n\t * @return {@link RestClient.Builder} REST客户端构建器\n\t */\n\t@Bean\n\t@LoadBalanced\n\t@ConditionalOnProperty(value = \"spring.cloud.nacos.discovery.enabled\", havingValue = \"true\", matchIfMissing = true)\n\tRestClient.Builder restClientBuilder() {\n\t\treturn RestClient.builder();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/WebMvcConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.config;\n\nimport cn.hutool.core.date.DatePattern;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.support.ReloadableResourceBundleMessageSource;\nimport org.springframework.format.FormatterRegistry;\nimport org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;\n\n/**\n * WebMvc配置类：用于自定义Spring MVC配置\n * <p>\n * 包含GET请求参数时间类型转换和系统国际化配置\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@AutoConfiguration\n@ConditionalOnWebApplication(type = SERVLET)\npublic class WebMvcConfiguration implements WebMvcConfigurer {\n\n\t/**\n\t * 增加GET请求参数中时间类型转换\n\t * @param registry 格式化注册器\n\t */\n\t@Override\n\tpublic void addFormatters(FormatterRegistry registry) {\n\t\tDateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();\n\t\tregistrar.setTimeFormatter(DatePattern.NORM_TIME_FORMATTER);\n\t\tregistrar.setDateFormatter(DatePattern.NORM_DATE_FORMATTER);\n\t\tregistrar.setDateTimeFormatter(DatePattern.NORM_DATETIME_FORMATTER);\n\t\tregistrar.registerFormatters(registry);\n\t}\n\n\t/**\n\t * 创建并配置国际化消息源\n\t * @return 可重载的资源包消息源\n\t */\n\t@Bean\n\tpublic MessageSource messageSource() {\n\t\tReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();\n\t\tmessageSource.setBasename(\"classpath:i18n/messages\");\n\t\treturn messageSource;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CacheConstants.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant;\n\n/**\n * @author lengleng\n * @date 2020年01月01日\n * <p>\n * 缓存的key 常量\n */\npublic interface CacheConstants {\n\n\t/**\n\t * oauth 缓存前缀\n\t */\n\tString PROJECT_OAUTH_ACCESS = \"token::access_token\";\n\n\t/**\n\t * 验证码前缀\n\t */\n\tString DEFAULT_CODE_KEY = \"DEFAULT_CODE_KEY:\";\n\n\t/**\n\t * 菜单信息缓存\n\t */\n\tString MENU_DETAILS = \"menu_details\";\n\n\t/**\n\t * 用户信息缓存\n\t */\n\tString USER_DETAILS = \"user_details\";\n\n\t/**\n\t * 字典信息缓存\n\t */\n\tString DICT_DETAILS = \"dict_details\";\n\n\t/**\n\t * 角色信息缓存\n\t */\n\tString ROLE_DETAILS = \"role_details\";\n\n\t/**\n\t * oauth 客户端信息\n\t */\n\tString CLIENT_DETAILS_KEY = \"client:details\";\n\n\t/**\n\t * 参数缓存\n\t */\n\tString PARAMS_DETAILS = \"params_details\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CommonConstants.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant;\n\n/**\n * @author lengleng\n * @date 2019/2/1\n */\npublic interface CommonConstants {\n\n\t/**\n\t * 删除\n\t */\n\tString STATUS_DEL = \"1\";\n\n\t/**\n\t * 正常\n\t */\n\tString STATUS_NORMAL = \"0\";\n\n\t/**\n\t * 锁定\n\t */\n\tString STATUS_LOCK = \"9\";\n\n\t/**\n\t * 菜单树根节点\n\t */\n\tLong MENU_TREE_ROOT_ID = -1L;\n\n\t/**\n\t * 菜单\n\t */\n\tString MENU = \"0\";\n\n\t/**\n\t * 编码\n\t */\n\tString UTF8 = \"UTF-8\";\n\n\t/**\n\t * JSON 资源\n\t */\n\tString CONTENT_TYPE = \"application/json; charset=utf-8\";\n\n\t/**\n\t * 前端工程名\n\t */\n\tString FRONT_END_PROJECT = \"pig-ui\";\n\n\t/**\n\t * 后端工程名\n\t */\n\tString BACK_END_PROJECT = \"pig\";\n\n\t/**\n\t * 成功标记\n\t */\n\tInteger SUCCESS = 0;\n\n\t/**\n\t * 失败标记\n\t */\n\tInteger FAIL = 1;\n\n\t/**\n\t * 当前页\n\t */\n\tString CURRENT = \"current\";\n\n\t/**\n\t * size\n\t */\n\tString SIZE = \"size\";\n\n\t/**\n\t * 请求开始时间\n\t */\n\tString REQUEST_START_TIME = \"REQUEST-START-TIME\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant;\n\n/**\n * @author lengleng\n * @date 2019/2/1\n */\npublic interface SecurityConstants {\n\n\t/**\n\t * 角色前缀\n\t */\n\tString ROLE = \"ROLE_\";\n\n\t/**\n\t * 前缀\n\t */\n\tString PROJECT_PREFIX = \"pig\";\n\n\t/**\n\t * 项目的license\n\t */\n\tString PROJECT_LICENSE = \"https://pig4cloud.com\";\n\n\t/**\n\t * 内部\n\t */\n\tString FROM_IN = \"Y\";\n\n\t/**\n\t * 标志\n\t */\n\tString FROM = \"from\";\n\n\t/**\n\t * 默认登录URL\n\t */\n\tString OAUTH_TOKEN_URL = \"/oauth2/token\";\n\n\t/**\n\t * grant_type\n\t */\n\tString REFRESH_TOKEN = \"refresh_token\";\n\n\t/**\n\t * password 模式\n\t */\n\tString PASSWORD = \"password\";\n\n\t/**\n\t * 授权码\n\t */\n\tString AUTHORIZATION_CODE = \"authorization_code\";\n\n\t/**\n\t * 手机号登录\n\t */\n\tString MOBILE = \"mobile\";\n\n\t/**\n\t * {bcrypt} 加密的特征码\n\t */\n\tString BCRYPT = \"{bcrypt}\";\n\n\t/**\n\t * {noop} 加密的特征码\n\t */\n\tString NOOP = \"{noop}\";\n\n\t/**\n\t * 用户名\n\t */\n\tString USERNAME = \"username\";\n\n\t/**\n\t * 用户信息\n\t */\n\tString DETAILS_USER = \"user_info\";\n\n\t/**\n\t * 用户ID\n\t */\n\tString DETAILS_USER_ID = \"user_id\";\n\n\t/**\n\t * 协议字段\n\t */\n\tString DETAILS_LICENSE = \"license\";\n\n\t/**\n\t * 验证码有效期,默认 60秒\n\t */\n\tlong CODE_TIME = 60;\n\n\t/**\n\t * 验证码长度\n\t */\n\tString CODE_SIZE = \"6\";\n\n\t/**\n\t * 客户端模式\n\t */\n\tString CLIENT_CREDENTIALS = \"client_credentials\";\n\n\t/**\n\t * 客户端ID\n\t */\n\tString CLIENT_ID = \"clientId\";\n\n\t/**\n\t * 短信登录 参数名称\n\t */\n\tString SMS_PARAMETER_NAME = \"mobile\";\n\n\t/**\n\t * 授权码模式confirm\n\t */\n\tString CUSTOM_CONSENT_PAGE_URI = \"/oauth2/confirm_access\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/ServiceNameConstants.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant;\n\n/**\n * @author lengleng\n * @date 2018年06月22日16:41:01 服务名称\n */\npublic interface ServiceNameConstants {\n\n\t/**\n\t * 认证服务的SERVICEID\n\t */\n\tString AUTH_SERVICE = \"pig-auth\";\n\n\t/**\n\t * UPMS模块\n\t */\n\tString UPMS_SERVICE = \"pig-upms-biz\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/DictTypeEnum.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant.enums;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * @author lengleng\n * @date 2019-05-16\n * <p>\n * 字典类型\n */\n@Getter\n@RequiredArgsConstructor\npublic enum DictTypeEnum {\n\n\t/**\n\t * 字典类型-系统内置（不可修改）\n\t */\n\tSYSTEM(\"1\", \"系统内置\"),\n\n\t/**\n\t * 字典类型-业务类型\n\t */\n\tBIZ(\"0\", \"业务类\");\n\n\t/**\n\t * 类型\n\t */\n\tprivate final String type;\n\n\t/**\n\t * 描述\n\t */\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/LoginTypeEnum.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant.enums;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * @author lengleng\n * @date 2018/8/15 社交登录类型\n */\n@Getter\n@RequiredArgsConstructor\npublic enum LoginTypeEnum {\n\n\t/**\n\t * 账号密码登录\n\t */\n\tPWD(\"PWD\", \"账号密码登录\"),\n\n\t/**\n\t * 验证码登录\n\t */\n\tSMS(\"SMS\", \"验证码登录\");\n\n\t/**\n\t * 类型\n\t */\n\tprivate final String type;\n\n\t/**\n\t * 描述\n\t */\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/MenuTypeEnum.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.constant.enums;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * @author lengleng\n * @date 2020-02-17\n * <p>\n * 菜单类型\n */\n@Getter\n@RequiredArgsConstructor\npublic enum MenuTypeEnum {\n\n\t/**\n\t * 左侧菜单\n\t */\n\tLEFT_MENU(\"0\", \"left\"),\n\n\t/**\n\t * 顶部菜单\n\t */\n\tTOP_MENU(\"2\", \"top\"),\n\n\t/**\n\t * 按钮\n\t */\n\tBUTTON(\"1\", \"button\");\n\n\t/**\n\t * 类型\n\t */\n\tprivate final String type;\n\n\t/**\n\t * 描述\n\t */\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/CheckedException.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.exception;\n\nimport lombok.NoArgsConstructor;\n\n/**\n * 受检异常类，继承自RuntimeException\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@NoArgsConstructor\npublic class CheckedException extends RuntimeException {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\tpublic CheckedException(String message) {\n\t\tsuper(message);\n\t}\n\n\tpublic CheckedException(Throwable cause) {\n\t\tsuper(cause);\n\t}\n\n\tpublic CheckedException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n\n\tpublic CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {\n\t\tsuper(message, cause, enableSuppression, writableStackTrace);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ErrorCodes.java",
    "content": "package com.pig4cloud.pig.common.core.exception;\n\n/**\n * 错误编码\n *\n * @author lengleng\n * @date 2022/3/30\n */\npublic interface ErrorCodes {\n\n\t/**\n\t * 系统编码错误\n\t */\n\tString SYS_PARAM_CONFIG_ERROR = \"sys.param.config.error\";\n\n\t/**\n\t * 系统内置参数不能删除\n\t */\n\tString SYS_PARAM_DELETE_SYSTEM = \"sys.param.delete.system\";\n\n\t/**\n\t * 用户已存在\n\t */\n\tString SYS_USER_EXISTING = \"sys.user.existing\";\n\n\t/**\n\t * 用户名已存在\n\t */\n\tString SYS_USER_USERNAME_EXISTING = \"sys.user.username.existing\";\n\n\t/**\n\t * 用户原密码错误，修改失败\n\t */\n\tString SYS_USER_UPDATE_PASSWORDERROR = \"sys.user.update.passwordError\";\n\n\t/**\n\t * 用户信息为空\n\t */\n\tString SYS_USER_USERINFO_EMPTY = \"sys.user.userInfo.empty\";\n\n\t/**\n\t * 获取当前用户信息失败\n\t */\n\tString SYS_USER_QUERY_ERROR = \"sys.user.query.error\";\n\n\t/**\n\t * 部门名称不存在\n\t */\n\tString SYS_DEPT_DEPTNAME_INEXISTENCE = \"sys.dept.deptName.inexistence\";\n\n\t/**\n\t * 岗位名称不存在\n\t */\n\tString SYS_POST_POSTNAME_INEXISTENCE = \"sys.post.postName.inexistence\";\n\n\t/**\n\t * 岗位名称或编码已经存在\n\t */\n\tString SYS_POST_NAMEORCODE_EXISTING = \"sys.post.nameOrCode.existing\";\n\n\t/**\n\t * 角色名称不存在\n\t */\n\tString SYS_ROLE_ROLENAME_INEXISTENCE = \"sys.role.roleName.inexistence\";\n\n\t/**\n\t * 角色名或角色编码已经存在\n\t */\n\tString SYS_ROLE_NAMEORCODE_EXISTING = \"sys.role.nameOrCode.existing\";\n\n\t/**\n\t * 菜单存在下级节点 删除失败\n\t */\n\tString SYS_MENU_DELETE_EXISTING = \"sys.menu.delete.existing\";\n\n\t/**\n\t * 系统内置字典不允许删除\n\t */\n\tString SYS_DICT_DELETE_SYSTEM = \"sys.dict.delete.system\";\n\n\t/**\n\t * 系统内置字典不能修改\n\t */\n\tString SYS_DICT_UPDATE_SYSTEM = \"sys.dict.update.system\";\n\n\t/**\n\t * 验证码发送频繁\n\t */\n\tString SYS_APP_SMS_OFTEN = \"sys.app.sms.often\";\n\n\t/**\n\t * 验证码错误\n\t */\n\tString SYS_APP_SMS_ERROR = \"sys.app.sms.error\";\n\n\t/**\n\t * 手机号未注册\n\t */\n\tString SYS_APP_PHONE_UNREGISTERED = \"sys.app.phone.unregistered\";\n\n\t/**\n\t * 未注册用户的短信混合系统配置键\n\t */\n\tString SYS_SMS_BLEND_UNREGISTERED = \"sys.app.sms.blend.unregistered\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/PigDeniedException.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.exception;\n\nimport lombok.NoArgsConstructor;\n\n/**\n * 授权拒绝异常类\n *\n * @author lengleng\n * @date 2018/06/22\n */\n@NoArgsConstructor\npublic class PigDeniedException extends RuntimeException {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\tpublic PigDeniedException(String message) {\n\t\tsuper(message);\n\t}\n\n\tpublic PigDeniedException(Throwable cause) {\n\t\tsuper(cause);\n\t}\n\n\tpublic PigDeniedException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n\n\tpublic PigDeniedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {\n\t\tsuper(message, cause, enableSuppression, writableStackTrace);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ValidateCodeException.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.exception;\n\n/**\n * 验证码异常类\n *\n * @author lengleng\n * @date 2018/06/22\n */\npublic class ValidateCodeException extends RuntimeException {\n\n\tprivate static final long serialVersionUID = -7285211528095468156L;\n\n\tpublic ValidateCodeException() {\n\t}\n\n\tpublic ValidateCodeException(String msg) {\n\t\tsuper(msg);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/factory/YamlPropertySourceFactory.java",
    "content": "package com.pig4cloud.pig.common.core.factory;\n\nimport org.springframework.beans.factory.config.YamlPropertiesFactoryBean;\nimport org.springframework.core.env.PropertiesPropertySource;\nimport org.springframework.core.env.PropertySource;\nimport org.springframework.core.io.support.EncodedResource;\nimport org.springframework.core.io.support.PropertySourceFactory;\nimport org.springframework.lang.Nullable;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.Properties;\n\n/**\n * YAML属性源工厂类：用于读取自定义YAML文件并转换为属性源\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic class YamlPropertySourceFactory implements PropertySourceFactory {\n\n\t/**\n\t * 创建属性源\n\t * @param name 属性源名称，可为空\n\t * @param resource 编码资源\n\t * @return 属性源对象\n\t * @throws IOException 读取资源时可能抛出IO异常\n\t */\n\t@Override\n\tpublic PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {\n\t\tProperties propertiesFromYaml = loadYamlIntoProperties(resource);\n\t\tString sourceName = name != null ? name : resource.getResource().getFilename();\n\t\treturn new PropertiesPropertySource(sourceName, propertiesFromYaml);\n\t}\n\n\t/**\n\t * 将YAML资源加载为Properties对象\n\t * @param resource 编码后的资源对象\n\t * @return 加载后的Properties对象\n\t * @throws FileNotFoundException 当资源文件不存在时抛出\n\t */\n\tprivate Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {\n\t\ttry {\n\t\t\tYamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();\n\t\t\tfactory.setResources(resource.getResource());\n\t\t\tfactory.afterPropertiesSet();\n\t\t\treturn factory.getObject();\n\t\t}\n\t\tcatch (IllegalStateException e) {\n\t\t\tThrowable cause = e.getCause();\n\t\t\tif (cause instanceof FileNotFoundException) {\n\t\t\t\tthrow (FileNotFoundException) e.getCause();\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/jackson/PigJavaTimeModule.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.pig4cloud.pig.common.core.jackson;\n\nimport cn.hutool.core.date.DatePattern;\nimport com.fasterxml.jackson.databind.module.SimpleModule;\nimport com.fasterxml.jackson.datatype.jsr310.PackageVersion;\nimport com.fasterxml.jackson.datatype.jsr310.deser.*;\nimport com.fasterxml.jackson.datatype.jsr310.ser.*;\n\nimport java.io.Serial;\nimport java.time.*;\nimport java.time.format.DateTimeFormatter;\n\n/**\n * Java 8 时间默认序列化模块\n *\n * @author L.cm\n * @author lishanbu\n * @author lengleng\n * @date 2025/05/30\n */\n\npublic class PigJavaTimeModule extends SimpleModule {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * PigJavaTimeModule构造函数，用于初始化时间序列化和反序列化规则\n\t */\n\tpublic PigJavaTimeModule() {\n\t\tsuper(PackageVersion.VERSION);\n\n\t\t// ======================= 时间序列化规则 ===============================\n\t\t// yyyy-MM-dd HH:mm:ss\n\t\tthis.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER));\n\t\t// yyyy-MM-dd\n\t\tthis.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));\n\t\t// HH:mm:ss\n\t\tthis.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));\n\t\t// Instant 类型序列化\n\t\tthis.addSerializer(Instant.class, InstantSerializer.INSTANCE);\n\t\t// Duration 类型序列化\n\t\tthis.addSerializer(Duration.class, DurationSerializer.INSTANCE);\n\n\t\t// ======================= 时间反序列化规则 ==============================\n\t\t// yyyy-MM-dd HH:mm:ss\n\t\tthis.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER));\n\t\t// yyyy-MM-dd\n\t\tthis.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));\n\t\t// HH:mm:ss\n\t\tthis.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));\n\t\t// Instant 反序列化\n\t\tthis.addDeserializer(Instant.class, InstantDeserializer.INSTANT);\n\t\t// Duration 反序列化\n\t\tthis.addDeserializer(Duration.class, DurationDeserializer.INSTANCE);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/servlet/RepeatBodyRequestWrapper.java",
    "content": "/*\n * Copyright 2023-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.servlet;\n\nimport jakarta.servlet.ReadListener;\nimport jakarta.servlet.ServletInputStream;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletRequestWrapper;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.util.ObjectUtils;\nimport org.springframework.util.StreamUtils;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Request包装类：允许body重复读取\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\npublic class RepeatBodyRequestWrapper extends HttpServletRequestWrapper {\n\n\tprivate final byte[] bodyByteArray;\n\n\tprivate final Map<String, String[]> parameterMap;\n\n\tpublic RepeatBodyRequestWrapper(HttpServletRequest request) {\n\t\tsuper(request);\n\t\tthis.bodyByteArray = getByteBody(request);\n\t\t// 使用 HashMap 以便后续可以修改\n\t\tthis.parameterMap = new HashMap<>(request.getParameterMap());\n\t}\n\n\t/**\n\t * 获取BufferedReader对象\n\t * @return 如果bodyByteArray为空则返回null，否则返回对应的BufferedReader\n\t */\n\t@Override\n\tpublic BufferedReader getReader() {\n\t\treturn ObjectUtils.isEmpty(this.bodyByteArray) ? null\n\t\t\t\t: new BufferedReader(new InputStreamReader(getInputStream()));\n\t}\n\n\t/**\n\t * 获取Servlet输入流\n\t * @return ServletInputStream 基于bodyByteArray的输入流\n\t */\n\t@Override\n\tpublic ServletInputStream getInputStream() {\n\t\tfinal ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bodyByteArray);\n\t\treturn new ServletInputStream() {\n\t\t\t@Override\n\t\t\tpublic boolean isFinished() {\n\t\t\t\treturn byteArrayInputStream.available() == 0;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean isReady() {\n\t\t\t\treturn true; // 可以读取\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void setReadListener(ReadListener readListener) {\n\t\t\t\t// doNothing\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic int read() {\n\t\t\t\treturn byteArrayInputStream.read();\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * 从HttpServletRequest中获取字节数组形式的请求体\n\t * @param request HTTP请求对象\n\t * @return 请求体字节数组，解析失败时返回空数组\n\t */\n\tprivate static byte[] getByteBody(HttpServletRequest request) {\n\t\tbyte[] body = new byte[0];\n\t\ttry {\n\t\t\tbody = StreamUtils.copyToByteArray(request.getInputStream());\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tlog.error(\"解析流中数据异常\", e);\n\t\t}\n\t\treturn body;\n\t}\n\n\t/**\n\t * 获取参数映射表\n\t * @return 可变的参数映射表\n\t */\n\t@Override\n\tpublic Map<String, String[]> getParameterMap() {\n\t\treturn this.parameterMap; // 返回可变的 parameterMap\n\t}\n\n\t/**\n\t * 设置新的参数映射\n\t * @param parameterMap 新的参数映射，将替换现有参数映射\n\t */\n\tpublic void setParameterMap(Map<String, String[]> parameterMap) {\n\t\tthis.parameterMap.clear();\n\t\tthis.parameterMap.putAll(parameterMap);\n\t}\n\n\t/**\n\t * 根据参数名获取参数值\n\t * @param name 参数名\n\t * @return 参数值，如果不存在则返回null\n\t */\n\t@Override\n\tpublic String getParameter(String name) {\n\t\tString[] values = parameterMap.get(name);\n\t\treturn (values != null && values.length > 0) ? values[0] : null;\n\t}\n\n\t/**\n\t * 根据参数名获取参数值数组\n\t * @param name 参数名\n\t * @return 参数值数组，如果不存在则返回null\n\t */\n\t@Override\n\tpublic String[] getParameterValues(String name) {\n\t\treturn parameterMap.get(name);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/ClassUtils.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.util;\n\nimport lombok.experimental.UtilityClass;\nimport org.springframework.core.BridgeMethodResolver;\nimport org.springframework.core.DefaultParameterNameDiscoverer;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.core.ParameterNameDiscoverer;\nimport org.springframework.core.annotation.AnnotatedElementUtils;\nimport org.springframework.core.annotation.SynthesizingMethodParameter;\nimport org.springframework.web.method.HandlerMethod;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\n\n/**\n * 类工具类\n *\n * @author L.cm\n */\n@UtilityClass\npublic class ClassUtils extends org.springframework.util.ClassUtils {\n\n\tprivate final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer();\n\n\t/**\n\t * 获取方法参数信息\n\t * @param constructor 构造器\n\t * @param parameterIndex 参数序号\n\t * @return {MethodParameter}\n\t */\n\tpublic MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {\n\t\tMethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);\n\t\tmethodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);\n\t\treturn methodParameter;\n\t}\n\n\t/**\n\t * 获取方法参数信息\n\t * @param method 方法\n\t * @param parameterIndex 参数序号\n\t * @return {MethodParameter}\n\t */\n\tpublic MethodParameter getMethodParameter(Method method, int parameterIndex) {\n\t\tMethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);\n\t\tmethodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);\n\t\treturn methodParameter;\n\t}\n\n\t/**\n\t * 获取Annotation\n\t * @param method Method\n\t * @param annotationType 注解类\n\t * @param <A> 泛型标记\n\t * @return {Annotation}\n\t */\n\tpublic <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {\n\t\tClass<?> targetClass = method.getDeclaringClass();\n\t\t// The method may be on an interface, but we need attributes from the target\n\t\t// class.\n\t\t// If the target class is null, the method will be unchanged.\n\t\tMethod specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);\n\t\t// If we are dealing with method with generic parameters, find the original\n\t\t// method.\n\t\tspecificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);\n\t\t// 先找方法，再找方法上的类\n\t\tA annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);\n\t\tif (null != annotation) {\n\t\t\treturn annotation;\n\t\t}\n\t\t// 获取类上面的Annotation，可能包含组合注解，故采用spring的工具类\n\t\treturn AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);\n\t}\n\n\t/**\n\t * 获取Annotation\n\t * @param handlerMethod HandlerMethod\n\t * @param annotationType 注解类\n\t * @param <A> 泛型标记\n\t * @return {Annotation}\n\t */\n\tpublic <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {\n\t\t// 先找方法，再找方法上的类\n\t\tA annotation = handlerMethod.getMethodAnnotation(annotationType);\n\t\tif (null != annotation) {\n\t\t\treturn annotation;\n\t\t}\n\t\t// 获取类上面的Annotation，可能包含组合注解，故采用spring的工具类\n\t\tClass<?> beanType = handlerMethod.getBeanType();\n\t\treturn AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/MsgUtils.java",
    "content": "package com.pig4cloud.pig.common.core.util;\n\nimport lombok.experimental.UtilityClass;\nimport org.springframework.context.MessageSource;\n\nimport java.util.Locale;\n\n/**\n * i18n 工具类\n *\n * @author lengleng\n * @date 2022/3/30\n */\n@UtilityClass\npublic class MsgUtils {\n\n\t/**\n\t * 根据错误码获取中文错误信息\n\t * @param code 错误码\n\t * @return 对应的中文错误信息\n\t */\n\tpublic String getMessage(String code) {\n\t\tMessageSource messageSource = SpringContextHolder.getBean(\"messageSource\");\n\t\treturn messageSource.getMessage(code, null, Locale.CHINA);\n\t}\n\n\t/**\n\t * 通过错误码和参数获取中文错误信息\n\t * @param code 错误码\n\t * @param objects 格式化参数\n\t * @return 格式化后的中文错误信息\n\t */\n\tpublic String getMessage(String code, Object... objects) {\n\t\tMessageSource messageSource = SpringContextHolder.getBean(\"messageSource\");\n\t\treturn messageSource.getMessage(code, objects, Locale.CHINA);\n\t}\n\n\t/**\n\t * 通过错误码和参数获取中文错误信息\n\t * @param code 错误码\n\t * @param objects 格式化参数\n\t * @return 格式化后的中文错误信息\n\t */\n\tpublic String getSecurityMessage(String code, Object... objects) {\n\t\tMessageSource messageSource = SpringContextHolder.getBean(\"securityMessageSource\");\n\t\treturn messageSource.getMessage(code, objects, Locale.CHINA);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/R.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.util;\n\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport lombok.*;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldNameConstants;\n\nimport java.io.Serializable;\n\n/**\n * 响应信息主体\n *\n * @param <T>\n * @author lengleng\n */\n@ToString\n@NoArgsConstructor\n@AllArgsConstructor\n@Accessors(chain = true)\n@FieldNameConstants\npublic class R<T> implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t@Getter\n\t@Setter\n\tprivate int code;\n\n\t@Getter\n\t@Setter\n\tprivate String msg;\n\n\t@Getter\n\t@Setter\n\tprivate T data;\n\n\tpublic static <T> R<T> ok() {\n\t\treturn restResult(null, CommonConstants.SUCCESS, null);\n\t}\n\n\tpublic static <T> R<T> ok(T data) {\n\t\treturn restResult(data, CommonConstants.SUCCESS, null);\n\t}\n\n\tpublic static <T> R<T> ok(T data, String msg) {\n\t\treturn restResult(data, CommonConstants.SUCCESS, msg);\n\t}\n\n\tpublic static <T> R<T> failed() {\n\t\treturn restResult(null, CommonConstants.FAIL, null);\n\t}\n\n\tpublic static <T> R<T> failed(String msg) {\n\t\treturn restResult(null, CommonConstants.FAIL, msg);\n\t}\n\n\tpublic static <T> R<T> failed(T data) {\n\t\treturn restResult(data, CommonConstants.FAIL, null);\n\t}\n\n\tpublic static <T> R<T> failed(T data, String msg) {\n\t\treturn restResult(data, CommonConstants.FAIL, msg);\n\t}\n\n\tpublic static <T> R<T> restResult(T data, int code, String msg) {\n\t\tR<T> apiResult = new R<>();\n\t\tapiResult.setCode(code);\n\t\tapiResult.setData(data);\n\t\tapiResult.setMsg(msg);\n\t\treturn apiResult;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RedisUtils.java",
    "content": "package com.pig4cloud.pig.common.core.util;\n\nimport cn.hutool.core.convert.Convert;\nimport lombok.experimental.UtilityClass;\nimport org.springframework.data.redis.connection.RedisConnection;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.*;\nimport org.springframework.data.redis.core.script.DefaultRedisScript;\nimport org.springframework.data.redis.core.script.RedisScript;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 缓存工具类，注意这里都是基于RedisTemplate 来操作的\n *\n * @author XX\n * @date 2023/05/12\n */\n@UtilityClass\npublic class RedisUtils {\n\n\tprivate static final Long SUCCESS = 1L;\n\n\t/**\n\t * 指定缓存失效时间\n\t * @param key 键\n\t * @param time 时间(秒)\n\t */\n\tpublic boolean expire(String key, long time) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tOptional.ofNullable(redisTemplate)\n\t\t\t.filter(template -> time > 0)\n\t\t\t.ifPresent(template -> template.expire(key, time, TimeUnit.SECONDS));\n\t\treturn true;\n\t}\n\n\t/**\n\t * 根据 key 获取过期时间\n\t * @param key 键 不能为null\n\t * @return 时间(秒) 返回0代表为永久有效\n\t */\n\tpublic long getExpire(String key) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate)\n\t\t\t.map(template -> template.getExpire(key, TimeUnit.SECONDS))\n\t\t\t.orElse(-1L);\n\t}\n\n\t/**\n\t * 查找匹配key\n\t * @param pattern key\n\t * @return /\n\t */\n\tpublic List<String> scan(String pattern) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tScanOptions options = ScanOptions.scanOptions().match(pattern).build();\n\t\treturn Optional.ofNullable(redisTemplate).map(template -> {\n\t\t\tRedisConnectionFactory factory = template.getConnectionFactory();\n\t\t\tRedisConnection rc = Objects.requireNonNull(factory).getConnection();\n\t\t\tCursor<byte[]> cursor = rc.keyCommands().scan(options);\n\t\t\tList<String> result = new ArrayList<>();\n\t\t\twhile (cursor.hasNext()) {\n\t\t\t\tresult.add(new String(cursor.next()));\n\t\t\t}\n\t\t\tRedisConnectionUtils.releaseConnection(rc, factory);\n\t\t\treturn result;\n\t\t}).orElse(Collections.emptyList());\n\t}\n\n\t/**\n\t * 查找匹配key (使用KEYS命令)\n\t * @param pattern key模式，支持通配符 * ? [] 等\n\t * @return 匹配的key列表\n\t * @apiNote 注意：KEYS命令会阻塞Redis服务器，生产环境建议使用scan方法\n\t */\n\tpublic Set<String> keys(String pattern) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate)\n\t\t\t.map(template -> template.keys(pattern))\n\t\t\t.orElse(Collections.emptySet());\n\t}\n\n\t/**\n\t * 分页查询 key\n\t * @param patternKey key\n\t * @param page 页码\n\t * @param size 每页数目\n\t * @return /\n\t */\n\tpublic List<String> findKeysForPage(String patternKey, int page, int size) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tScanOptions options = ScanOptions.scanOptions().match(patternKey).build();\n\t\tRedisConnectionFactory factory = redisTemplate.getConnectionFactory();\n\t\tRedisConnection rc = Objects.requireNonNull(factory).getConnection();\n\t\tCursor<byte[]> cursor = rc.keyCommands().scan(options);\n\t\tList<String> result = new ArrayList<>(size);\n\t\tint tmpIndex = 0;\n\t\tint fromIndex = page * size;\n\t\tint toIndex = page * size + size;\n\t\twhile (cursor.hasNext()) {\n\t\t\tif (tmpIndex >= fromIndex && tmpIndex < toIndex) {\n\t\t\t\tresult.add(new String(cursor.next()));\n\t\t\t\ttmpIndex++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// 获取到满足条件的数据后,就可以退出了\n\t\t\tif (tmpIndex >= toIndex) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttmpIndex++;\n\t\t\tcursor.next();\n\t\t}\n\t\tRedisConnectionUtils.releaseConnection(rc, factory);\n\t\treturn result;\n\t}\n\n\t/**\n\t * 判断key是否存在\n\t * @param key 键\n\t * @return true 存在 false不存在\n\t */\n\tpublic boolean hasKey(String key) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate).map(template -> template.hasKey(key)).orElse(false);\n\t}\n\n\t/**\n\t * 删除缓存\n\t * @param keys 可以传一个值 或多个\n\t */\n\tpublic void delete(String... keys) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tif (keys != null) {\n\t\t\tArrays.stream(keys).forEach(redisTemplate::delete);\n\t\t}\n\t}\n\n\t/**\n\t * 获取锁\n\t * @param lockKey 锁key\n\t * @param value value\n\t * @param expireTime：单位-秒\n\t * @return boolean\n\t */\n\tpublic boolean getLock(String lockKey, String value, int expireTime) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate)\n\t\t\t.map(template -> template.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS))\n\t\t\t.orElse(false);\n\t}\n\n\t/**\n\t * 释放锁\n\t * @param lockKey 锁key\n\t * @param value value\n\t * @return boolean\n\t */\n\tpublic boolean releaseLock(String lockKey, String value) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tString script = \"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end\";\n\t\tRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);\n\t\treturn Optional.ofNullable(redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value))\n\t\t\t.map(Convert::toLong)\n\t\t\t.filter(SUCCESS::equals)\n\t\t\t.isPresent();\n\t}\n\n\t// ============================String=============================\n\n\t/**\n\t * 普通缓存获取\n\t * @param key 键\n\t * @return 值\n\t */\n\tpublic <T> T get(String key) {\n\t\tRedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForValue().get(key);\n\t}\n\n\t/**\n\t * 批量获取\n\t * @param keys\n\t * @return\n\t */\n\tpublic <T> List<T> multiGet(List<String> keys) {\n\t\tRedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForValue().multiGet(keys);\n\t}\n\n\t/**\n\t * 普通缓存放入\n\t * @param key 键\n\t * @param value 值\n\t * @return true成功 false失败\n\t */\n\tpublic boolean set(String key, Object value) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tOptional.ofNullable(redisTemplate).map(template -> {\n\t\t\ttemplate.opsForValue().set(key, value);\n\t\t\treturn true;\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * 普通缓存放入并设置时间\n\t * @param key 键\n\t * @param value 值\n\t * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期\n\t * @return true成功 false 失败\n\t */\n\tpublic boolean set(String key, Object value, long time) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate).map(template -> {\n\t\t\tif (time > 0) {\n\t\t\t\ttemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttemplate.opsForValue().set(key, value);\n\t\t\t}\n\t\t\treturn true;\n\t\t}).orElse(false);\n\t}\n\n\t/**\n\t * 普通缓存放入并设置时间\n\t * @param key 键\n\t * @param value 值\n\t * @param time 时间\n\t * @param timeUnit 类型\n\t * @return true成功 false 失败\n\t */\n\tpublic <T> boolean set(String key, T value, long time, TimeUnit timeUnit) {\n\t\tRedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tOptional.ofNullable(redisTemplate).map(template -> {\n\t\t\tif (time > 0) {\n\t\t\t\ttemplate.opsForValue().set(key, value, time, timeUnit);\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttemplate.opsForValue().set(key, value);\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * 执行 Redis 命令回调\n\t * @param callback Redis回调函数\n\t * @return 执行结果\n\t */\n\tpublic <T> T execute(RedisCallback<T> callback) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn (T) redisTemplate.execute(callback);\n\t}\n\n\t// ================================Map=================================\n\n\t/**\n\t * HashGet\n\t * @param key 键 不能为null\n\t * @param hashKey 项 不能为null\n\t * @return 值\n\t */\n\tpublic <HK, HV> HV hget(String key, HK hashKey) {\n\t\tRedisTemplate<String, HV> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.<HK, HV>opsForHash().get(key, hashKey);\n\t}\n\n\t/**\n\t * 获取hashKey对应的所有键值\n\t * @param key 键\n\t * @return 对应的多个键值\n\t */\n\tpublic <HK, HV> Map<HK, HV> hmget(String key) {\n\t\tRedisTemplate<String, HV> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.<HK, HV>opsForHash().entries(key);\n\t}\n\n\t/**\n\t * HashSet\n\t * @param key 键\n\t * @param map 对应多个键值\n\t * @return true 成功 false 失败\n\t */\n\tpublic boolean hmset(String key, Map<String, Object> map) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tOptional.ofNullable(redisTemplate).map(template -> {\n\t\t\ttemplate.opsForHash().putAll(key, map);\n\t\t\treturn true;\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * HashSet 并设置时间\n\t * @param key 键\n\t * @param map 对应多个键值\n\t * @param time 时间(秒)\n\t * @return true成功 false失败\n\t */\n\tpublic boolean hmset(String key, Map<String, Object> map, long time) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tOptional.ofNullable(redisTemplate).map(template -> {\n\t\t\ttemplate.opsForHash().putAll(key, map);\n\t\t\tif (time > 0) {\n\t\t\t\ttemplate.expire(key, time, TimeUnit.SECONDS);\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * 向一张hash表中放入数据,如果不存在将创建\n\t * @param key 键\n\t * @param item 项\n\t * @param value 值\n\t * @return true 成功 false失败\n\t */\n\tpublic boolean hset(String key, String item, Object value) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate).map(template -> {\n\t\t\ttemplate.opsForHash().put(key, item, value);\n\t\t\treturn true;\n\t\t}).orElse(false);\n\t}\n\n\t/**\n\t * 向一张hash表中放入数据,如果不存在将创建\n\t * @param key 键\n\t * @param item 项\n\t * @param value 值\n\t * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间\n\t * @return true 成功 false失败\n\t */\n\tpublic boolean hset(String key, String item, Object value, long time) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn Optional.ofNullable(redisTemplate).map(template -> {\n\t\t\ttemplate.opsForHash().put(key, item, value);\n\t\t\tif (time > 0) {\n\t\t\t\ttemplate.expire(key, time, TimeUnit.SECONDS);\n\t\t\t}\n\t\t\treturn true;\n\t\t}).orElse(false);\n\t}\n\n\t/**\n\t * 删除hash表中的值\n\t * @param key 键 不能为null\n\t * @param item 项 可以使多个 不能为null\n\t */\n\tpublic void hdel(String key, Object... item) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tredisTemplate.opsForHash().delete(key, item);\n\t}\n\n\t/**\n\t * 判断hash表中是否有该项的值\n\t * @param key 键 不能为null\n\t * @param item 项 不能为null\n\t * @return true 存在 false不存在\n\t */\n\tpublic boolean hHasKey(String key, String item) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForHash().hasKey(key, item);\n\t}\n\n\t/**\n\t * hash递增 如果不存在,就会创建一个 并把新增后的值返回\n\t * @param key 键\n\t * @param item 项\n\t * @param by 要增加几(大于0)\n\t * @return\n\t */\n\tpublic double hincr(String key, String item, double by) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForHash().increment(key, item, by);\n\t}\n\n\t/**\n\t * hash递减\n\t * @param key 键\n\t * @param item 项\n\t * @param by 要减少记(小于0)\n\t * @return\n\t */\n\tpublic double hdecr(String key, String item, double by) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForHash().increment(key, item, -by);\n\t}\n\n\t// ============================set=============================\n\n\t/**\n\t * 根据key获取Set中的所有值\n\t * @param key 键\n\t * @return\n\t */\n\tpublic <T> Set<T> sGet(String key) {\n\t\tRedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForSet().members(key);\n\t}\n\n\t/**\n\t * 根据value从一个set中查询,是否存在\n\t * @param key 键\n\t * @param value 值\n\t * @return true 存在 false不存在\n\t */\n\tpublic boolean sHasKey(String key, Object value) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForSet().isMember(key, value);\n\t}\n\n\t/**\n\t * 将数据放入set缓存\n\t * @param key 键\n\t * @param values 值 可以是多个\n\t * @return 成功个数\n\t */\n\tpublic long sSet(String key, Object... values) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForSet().add(key, values);\n\t}\n\n\t/**\n\t * 将set数据放入缓存\n\t * @param key 键\n\t * @param time 时间(秒)\n\t * @param values 值 可以是多个\n\t * @return 成功个数\n\t */\n\tpublic long sSetAndTime(String key, long time, Object... values) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tLong count = redisTemplate.opsForSet().add(key, values);\n\t\tif (time > 0) {\n\t\t\texpire(key, time);\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * 获取set缓存的长度\n\t * @param key 键\n\t * @return\n\t */\n\tpublic long sGetSetSize(String key) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForSet().size(key);\n\t}\n\n\t/**\n\t * 移除值为value的\n\t * @param key 键\n\t * @param values 值 可以是多个\n\t * @return 移除的个数\n\t */\n\tpublic long setRemove(String key, Object... values) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tLong count = redisTemplate.opsForSet().remove(key, values);\n\t\treturn count;\n\t}\n\n\t/**\n\t * 获集合key1和集合key2的差集元素\n\t * @param key 键\n\t * @return\n\t */\n\tpublic <T> Set<T> sDifference(String key, String otherKey) {\n\t\tRedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForSet().difference(key, otherKey);\n\t}\n\n\t// ===============================list=================================\n\n\t/**\n\t * 获取list缓存的内容\n\t * @param key 键\n\t * @param start 开始\n\t * @param end 结束 0 到 -1代表所有值\n\t * @return\n\t */\n\tpublic <T> List<T> lGet(String key, long start, long end) {\n\t\tRedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForList().range(key, start, end);\n\t}\n\n\t/**\n\t * 获取list缓存的长度\n\t * @param key 键\n\t * @return\n\t */\n\tpublic long lGetListSize(String key) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForList().size(key);\n\t}\n\n\t/**\n\t * 通过索引 获取list中的值\n\t * @param key 键\n\t * @param index 索引 index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推\n\t * @return\n\t */\n\tpublic Object lGetIndex(String key, long index) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForList().index(key, index);\n\t}\n\n\t/**\n\t * 将list放入缓存\n\t * @param key 键\n\t * @param value 值\n\t * @return\n\t */\n\tpublic boolean lSet(String key, Object value) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tredisTemplate.opsForList().rightPush(key, value);\n\t\treturn true;\n\t}\n\n\t/**\n\t * 将list放入缓存\n\t * @param key 键\n\t * @param value 值\n\t * @param time 时间(秒)\n\t * @return\n\t */\n\tpublic boolean lSet(String key, Object value, long time) {\n\t\tRedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tredisTemplate.opsForList().rightPush(key, value);\n\t\tif (time > 0) {\n\t\t\tOptional.ofNullable(redisTemplate).ifPresent(template -> template.expire(key, time, TimeUnit.SECONDS));\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 将list放入缓存\n\t * @param key 键\n\t * @param value 值\n\t * @return\n\t */\n\tpublic boolean lSet(String key, List<Object> value) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tredisTemplate.opsForList().rightPushAll(key, value);\n\t\treturn true;\n\t}\n\n\t/**\n\t * 将list放入缓存\n\t * @param key 键\n\t * @param value 值\n\t * @param time 时间(秒)\n\t * @return\n\t */\n\tpublic boolean lSet(String key, List<Object> value, long time) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tredisTemplate.opsForList().rightPushAll(key, value);\n\t\tif (time > 0) {\n\t\t\texpire(key, time);\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 根据索引修改list中的某条数据\n\t * @param key 键\n\t * @param index 索引\n\t * @param value 值\n\t * @return /\n\t */\n\tpublic boolean lUpdateIndex(String key, long index, Object value) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tredisTemplate.opsForList().set(key, index, value);\n\t\treturn true;\n\t}\n\n\t/**\n\t * 移除N个值为value\n\t * @param key 键\n\t * @param count 移除多少个\n\t * @param value 值\n\t * @return 移除的个数\n\t */\n\tpublic long lRemove(String key, long count, Object value) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForList().remove(key, count, value);\n\t}\n\n\t/**\n\t * 将zSet数据放入缓存\n\t * @param key\n\t * @param time\n\t * @param tuples\n\t * @return\n\t */\n\tpublic long zSetAndTime(String key, long time, Set<ZSetOperations.TypedTuple<Object>> tuples) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tLong count = redisTemplate.opsForZSet().add(key, tuples);\n\t\tif (time > 0) {\n\t\t\texpire(key, time);\n\t\t}\n\t\treturn count;\n\n\t}\n\n\t/**\n\t * Sorted set:有序集合获取\n\t * @param key\n\t * @param min\n\t * @param max\n\t * @return\n\t */\n\tpublic Set<Object> zRangeByScore(String key, double min, double max) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tZSetOperations<String, Object> zset = redisTemplate.opsForZSet();\n\t\treturn zset.rangeByScore(key, min, max);\n\n\t}\n\n\t/**\n\t * Sorted set:有序集合获取 正序\n\t * @param key\n\t * @param start\n\t * @param end\n\t * @return\n\t */\n\tpublic Set<Object> zRange(String key, long start, long end) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tZSetOperations<String, Object> zset = redisTemplate.opsForZSet();\n\t\treturn zset.range(key, start, end);\n\n\t}\n\n\t/**\n\t * Sorted set:有序集合获取 倒叙\n\t * @param key\n\t * @param start\n\t * @param end\n\t * @return\n\t */\n\tpublic Set<Object> zReverseRange(String key, long start, long end) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\tZSetOperations<String, Object> zset = redisTemplate.opsForZSet();\n\t\treturn zset.reverseRange(key, start, end);\n\n\t}\n\n\t/**\n\t * 获取zSet缓存的长度\n\t * @param key 键\n\t * @return\n\t */\n\tpublic long zGetSetSize(String key) {\n\t\tRedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);\n\t\treturn redisTemplate.opsForZSet().size(key);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RetOps.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.common.core.util;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * 简化{@code R<T>} 的访问操作,例子 <pre>\n * R<Integer> result = R.ok(0);\n * // 使用场景1: 链式操作: 断言然后消费\n * RetOps.of(result)\n * \t\t.assertCode(-1,r -> new RuntimeException(\"error \"+r.getCode()))\n * \t\t.assertDataNotEmpty(r -> new IllegalStateException(\"oops!\"))\n * \t\t.useData(System.out::println);\n *\n * // 使用场景2: 读取原始值(data),这里返回的是Optional\n * RetOps.of(result).getData().orElse(null);\n *\n * // 使用场景3: 类型转换\n * R<String> s = RetOps.of(result)\n *        .assertDataNotNull(r -> new IllegalStateException(\"nani??\"))\n *        .map(i -> Integer.toHexString(i))\n *        .peek();\n * </pre>\n *\n * @author CJ (power4j@outlook.com)\n * @date 2022/5/12\n * @since 4.4\n */\npublic class RetOps<T> {\n\n\t/** 状态码为成功 */\n\tpublic static final Predicate<R<?>> CODE_SUCCESS = r -> CommonConstants.SUCCESS == r.getCode();\n\n\t/** 数据有值 */\n\tpublic static final Predicate<R<?>> HAS_DATA = r -> ObjectUtil.isNotEmpty(r.getData());\n\n\t/** 数据有值,并且包含元素 */\n\tpublic static final Predicate<R<?>> HAS_ELEMENT = r -> ObjectUtil.isNotEmpty(r.getData());\n\n\t/** 状态码为成功并且有值 */\n\tpublic static final Predicate<R<?>> DATA_AVAILABLE = CODE_SUCCESS.and(HAS_DATA);\n\n\tprivate final R<T> original;\n\n\t// ~ 初始化\n\t// ===================================================================================================\n\n\tRetOps(R<T> original) {\n\t\tthis.original = original;\n\t}\n\n\tpublic static <T> RetOps<T> of(R<T> original) {\n\t\treturn new RetOps<>(Objects.requireNonNull(original));\n\t}\n\n\t// ~ 杂项方法\n\t// ===================================================================================================\n\n\t/**\n\t * 观察原始值\n\t * @return R\n\t */\n\tpublic R<T> peek() {\n\t\treturn original;\n\t}\n\n\t/**\n\t * 读取{@code code}的值\n\t * @return 返回code的值\n\t */\n\tpublic int getCode() {\n\t\treturn original.getCode();\n\t}\n\n\t/**\n\t * 读取{@code data}的值\n\t * @return 返回 Optional 包装的data\n\t */\n\tpublic Optional<T> getData() {\n\t\treturn Optional.ofNullable(original.getData());\n\t}\n\n\t/**\n\t * 有条件地读取{@code data}的值\n\t * @param predicate 断言函数\n\t * @return 返回 Optional 包装的data,如果断言失败返回empty\n\t */\n\tpublic Optional<T> getDataIf(Predicate<? super R<?>> predicate) {\n\t\treturn predicate.test(original) ? getData() : Optional.empty();\n\t}\n\n\t/**\n\t * 读取{@code msg}的值\n\t * @return 返回Optional包装的 msg\n\t */\n\tpublic Optional<String> getMsg() {\n\t\treturn Optional.of(original.getMsg());\n\t}\n\n\t/**\n\t * 对{@code code}的值进行相等性测试\n\t * @param value 基准值\n\t * @return 返回ture表示相等\n\t */\n\tpublic boolean codeEquals(int value) {\n\t\treturn original.getCode() == value;\n\t}\n\n\t/**\n\t * 对{@code code}的值进行相等性测试\n\t * @param value 基准值\n\t * @return 返回ture表示不相等\n\t */\n\tpublic boolean codeNotEquals(int value) {\n\t\treturn !codeEquals(value);\n\t}\n\n\t/**\n\t * 是否成功\n\t * @return 返回ture表示成功\n\t * @see CommonConstants#SUCCESS\n\t */\n\tpublic boolean isSuccess() {\n\t\treturn codeEquals(CommonConstants.SUCCESS);\n\t}\n\n\t/**\n\t * 是否失败\n\t * @return 返回ture表示失败\n\t */\n\tpublic boolean notSuccess() {\n\t\treturn !isSuccess();\n\t}\n\n\t// ~ 链式操作\n\t// ===================================================================================================\n\n\t/**\n\t * 断言{@code code}的值\n\t * @param expect 预期的值\n\t * @param func 用户函数,负责创建异常对象\n\t * @param <Ex> 异常类型\n\t * @return 返回实例，以便于继续进行链式操作\n\t * @throws Ex 断言失败时抛出\n\t */\n\tpublic <Ex extends Exception> RetOps<T> assertCode(int expect, Function<? super R<T>, ? extends Ex> func)\n\t\t\tthrows Ex {\n\t\tif (codeNotEquals(expect)) {\n\t\t\tthrow func.apply(original);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * 断言成功\n\t * @param func 用户函数,负责创建异常对象\n\t * @param <Ex> 异常类型\n\t * @return 返回实例，以便于继续进行链式操作\n\t * @throws Ex 断言失败时抛出\n\t */\n\tpublic <Ex extends Exception> RetOps<T> assertSuccess(Function<? super R<T>, ? extends Ex> func) throws Ex {\n\t\treturn assertCode(CommonConstants.SUCCESS, func);\n\t}\n\n\t/**\n\t * 断言业务数据有值\n\t * @param func 用户函数,负责创建异常对象\n\t * @param <Ex> 异常类型\n\t * @return 返回实例，以便于继续进行链式操作\n\t * @throws Ex 断言失败时抛出\n\t */\n\tpublic <Ex extends Exception> RetOps<T> assertDataNotNull(Function<? super R<T>, ? extends Ex> func) throws Ex {\n\t\tif (Objects.isNull(original.getData())) {\n\t\t\tthrow func.apply(original);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * 断言业务数据有值,并且包含元素\n\t * @param func 用户函数,负责创建异常对象\n\t * @param <Ex> 异常类型\n\t * @return 返回实例，以便于继续进行链式操作\n\t * @throws Ex 断言失败时抛出\n\t */\n\tpublic <Ex extends Exception> RetOps<T> assertDataNotEmpty(Function<? super R<T>, ? extends Ex> func) throws Ex {\n\t\tif (ObjectUtil.isNotEmpty(original.getData())) {\n\t\t\tthrow func.apply(original);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * 对业务数据(data)转换\n\t * @param mapper 业务数据转换函数\n\t * @param <U> 数据类型\n\t * @return 返回新实例，以便于继续进行链式操作\n\t */\n\tpublic <U> RetOps<U> map(Function<? super T, ? extends U> mapper) {\n\t\tR<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());\n\t\treturn of(result);\n\t}\n\n\t/**\n\t * 对业务数据(data)转换\n\t * @param predicate 断言函数\n\t * @param mapper 业务数据转换函数\n\t * @param <U> 数据类型\n\t * @return 返回新实例，以便于继续进行链式操作\n\t * @see RetOps#CODE_SUCCESS\n\t * @see RetOps#HAS_DATA\n\t * @see RetOps#HAS_ELEMENT\n\t * @see RetOps#DATA_AVAILABLE\n\t */\n\tpublic <U> RetOps<U> mapIf(Predicate<? super R<T>> predicate, Function<? super T, ? extends U> mapper) {\n\t\tR<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());\n\t\treturn of(result);\n\t}\n\n\t// ~ 数据消费\n\t// ===================================================================================================\n\n\t/**\n\t * 消费数据,注意此方法保证数据可用\n\t * @param consumer 消费函数\n\t */\n\tpublic void useData(Consumer<? super T> consumer) {\n\t\tconsumer.accept(original.getData());\n\t}\n\n\t/**\n\t * 条件消费(错误代码匹配某个值)\n\t * @param consumer 消费函数\n\t * @param codes 错误代码集合,匹配任意一个则调用消费函数\n\t */\n\tpublic void useDataOnCode(Consumer<? super T> consumer, int... codes) {\n\t\tuseDataIf(o -> Arrays.stream(codes).filter(c -> original.getCode() == c).findFirst().isPresent(), consumer);\n\t}\n\n\t/**\n\t * 条件消费(错误代码表示成功)\n\t * @param consumer 消费函数\n\t */\n\tpublic void useDataIfSuccess(Consumer<? super T> consumer) {\n\t\tuseDataIf(CODE_SUCCESS, consumer);\n\t}\n\n\t/**\n\t * 条件消费\n\t * @param predicate 断言函数\n\t * @param consumer 消费函数,断言函数返回{@code true}时被调用\n\t * @see RetOps#CODE_SUCCESS\n\t * @see RetOps#HAS_DATA\n\t * @see RetOps#HAS_ELEMENT\n\t * @see RetOps#DATA_AVAILABLE\n\t */\n\tpublic void useDataIf(Predicate<? super R<T>> predicate, Consumer<? super T> consumer) {\n\t\tif (predicate.test(original)) {\n\t\t\tconsumer.accept(original.getData());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/SpringContextHolder.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.util;\n\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.DisposableBean;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.context.ApplicationEvent;\nimport org.springframework.context.EnvironmentAware;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.core.env.Environment;\nimport org.springframework.stereotype.Service;\n\n/**\n * @author lengleng\n * @date 2019/2/1 Spring 工具类\n */\n@Slf4j\n@Service\n@Lazy(false)\npublic class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean {\n\n\tprivate static ApplicationContext applicationContext = null;\n\n\tprivate static Environment environment = null;\n\n\t/**\n\t * 取得存储在静态变量中的ApplicationContext.\n\t */\n\tpublic static ApplicationContext getApplicationContext() {\n\t\treturn applicationContext;\n\t}\n\n\t/**\n\t * 获取环境\n\t * @return {@link Environment }\n\t */\n\tpublic static Environment getEnvironment() {\n\t\treturn environment;\n\t}\n\n\t/**\n\t * 实现ApplicationContextAware接口, 注入Context到静态变量中.\n\t */\n\t@Override\n\tpublic void setApplicationContext(ApplicationContext applicationContext) {\n\t\tSpringContextHolder.applicationContext = applicationContext;\n\t}\n\n\t/**\n\t * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.\n\t */\n\tpublic static <T> T getBean(String name) {\n\t\treturn (T) applicationContext.getBean(name);\n\t}\n\n\t/**\n\t * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.\n\t */\n\tpublic static <T> T getBean(Class<T> requiredType) {\n\t\treturn applicationContext.getBean(requiredType);\n\t}\n\n\t/**\n\t * 清除SpringContextHolder中的ApplicationContext为Null.\n\t */\n\tpublic static void clearHolder() {\n\t\tif (log.isDebugEnabled()) {\n\t\t\tlog.debug(\"清除SpringContextHolder中的ApplicationContext:\" + applicationContext);\n\t\t}\n\t\tapplicationContext = null;\n\t}\n\n\t/**\n\t * 发布事件\n\t * @param event\n\t */\n\tpublic static void publishEvent(ApplicationEvent event) {\n\t\tif (applicationContext == null) {\n\t\t\treturn;\n\t\t}\n\t\tapplicationContext.publishEvent(event);\n\t}\n\n\t/**\n\t * 是否是微服务\n\t * @return boolean\n\t */\n\tpublic static boolean isMicro() {\n\t\treturn environment.getProperty(\"spring.cloud.nacos.discovery.enabled\", Boolean.class, true);\n\t}\n\n\t/**\n\t * 实现DisposableBean接口, 在Context关闭时清理静态变量.\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic void destroy() {\n\t\tSpringContextHolder.clearHolder();\n\t}\n\n\t@Override\n\tpublic void setEnvironment(Environment environment) {\n\t\tSpringContextHolder.environment = environment;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/WebUtils.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.core.util;\n\nimport cn.hutool.core.codec.Base64;\nimport com.pig4cloud.pig.common.core.exception.CheckedException;\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.SneakyThrows;\nimport lombok.experimental.UtilityClass;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\nimport org.springframework.web.method.HandlerMethod;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Optional;\n\n/**\n * Miscellaneous utilities for web applications.\n *\n * @author L.cm\n */\n@UtilityClass\npublic class WebUtils extends org.springframework.web.util.WebUtils {\n\n\tprivate final String BASIC_ = \"Basic \";\n\n\t/**\n\t * 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解\n\t * @param handlerMethod HandlerMethod\n\t * @return 是否ajax请求\n\t */\n\tpublic boolean isBody(HandlerMethod handlerMethod) {\n\t\tResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class);\n\t\treturn responseBody != null;\n\t}\n\n\t/**\n\t * 读取cookie\n\t * @param name cookie name\n\t * @return cookie value\n\t */\n\tpublic String getCookieVal(String name) {\n\t\tif (WebUtils.getRequest().isPresent()) {\n\t\t\treturn getCookieVal(WebUtils.getRequest().get(), name);\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * 读取cookie\n\t * @param request HttpServletRequest\n\t * @param name cookie name\n\t * @return cookie value\n\t */\n\tpublic String getCookieVal(HttpServletRequest request, String name) {\n\t\tCookie cookie = getCookie(request, name);\n\t\treturn cookie != null ? cookie.getValue() : null;\n\t}\n\n\t/**\n\t * 清除 某个指定的cookie\n\t * @param response HttpServletResponse\n\t * @param key cookie key\n\t */\n\tpublic void removeCookie(HttpServletResponse response, String key) {\n\t\tsetCookie(response, key, null, 0);\n\t}\n\n\t/**\n\t * 设置cookie\n\t * @param response HttpServletResponse\n\t * @param name cookie name\n\t * @param value cookie value\n\t * @param maxAgeInSeconds maxage\n\t */\n\tpublic void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {\n\t\tCookie cookie = new Cookie(name, value);\n\t\tcookie.setPath(\"/\");\n\t\tcookie.setMaxAge(maxAgeInSeconds);\n\t\tcookie.setHttpOnly(true);\n\t\tresponse.addCookie(cookie);\n\t}\n\n\t/**\n\t * 获取 HttpServletRequest\n\t * @return {HttpServletRequest}\n\t */\n\tpublic Optional<HttpServletRequest> getRequest() {\n\t\treturn Optional\n\t\t\t.ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());\n\t}\n\n\t/**\n\t * 获取 HttpServletResponse\n\t * @return {HttpServletResponse}\n\t */\n\tpublic HttpServletResponse getResponse() {\n\t\treturn ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();\n\t}\n\n\t/**\n\t * 从request 获取CLIENT_ID\n\t * @return\n\t */\n\t@SneakyThrows\n\tpublic String getClientId(ServerHttpRequest request) {\n\t\tString header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);\n\t\treturn splitClient(header)[0];\n\t}\n\n\t@SneakyThrows\n\tpublic String getClientId() {\n\t\tif (WebUtils.getRequest().isPresent()) {\n\t\t\tString header = WebUtils.getRequest().get().getHeader(HttpHeaders.AUTHORIZATION);\n\t\t\treturn splitClient(header)[0];\n\t\t}\n\t\treturn null;\n\t}\n\n\t@NotNull\n\tprivate static String[] splitClient(String header) {\n\t\tif (header == null || !header.startsWith(BASIC_)) {\n\t\t\tthrow new CheckedException(\"请求头中client信息为空\");\n\t\t}\n\t\tbyte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);\n\t\tbyte[] decoded;\n\t\ttry {\n\t\t\tdecoded = Base64.decode(base64Token);\n\t\t}\n\t\tcatch (IllegalArgumentException e) {\n\t\t\tthrow new CheckedException(\"Failed to decode basic authentication token\");\n\t\t}\n\n\t\tString token = new String(decoded, StandardCharsets.UTF_8);\n\n\t\tint delim = token.indexOf(\":\");\n\n\t\tif (delim == -1) {\n\t\t\tthrow new CheckedException(\"Invalid basic authentication token\");\n\t\t}\n\t\treturn new String[] { token.substring(0, delim), token.substring(delim + 1) };\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.core.config.JacksonConfiguration\ncom.pig4cloud.pig.common.core.config.RedisTemplateConfiguration\ncom.pig4cloud.pig.common.core.config.RestTemplateConfiguration\ncom.pig4cloud.pig.common.core.util.SpringContextHolder\ncom.pig4cloud.pig.common.core.config.WebMvcConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/resources/banner.txt",
    "content": "${AnsiColor.BRIGHT_YELLOW}\n\n                :::::::::       :::::::::::       ::::::::\n               :+:    :+:          :+:          :+:    :+:\n              +:+    +:+          +:+          +:+\n             +#++:++#+           +#+          :#:\n            +#+                 +#+          +#+   +#+#\n           #+#                 #+#          #+#    #+#\n          ###             ###########       ########\n\n                       www.pig4cloud.com\n\n                  Pig Microservice Architecture\n${AnsiColor.DEFAULT}\n\n\n\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/resources/i18n/messages_zh_CN.properties",
    "content": "sys.user.update.passwordError=\\u539F\\u5BC6\\u7801\\u9519\\u8BEF\\uFF0C\\u4FEE\\u6539\\u5931\\u8D25\nsys.user.query.error=\\u83B7\\u53D6\\u5F53\\u524D\\u7528\\u6237\\u4FE1\\u606F\\u5931\\u8D25\nsys.user.existing=\\u7528\\u6237\\u5DF2\\u5B58\\u5728\nsys.user.username.existing={0} \\u7528\\u6237\\u540D\\u5DF2\\u5B58\\u5728\nsys.user.userInfo.empty={0} \\u7528\\u6237\\u4FE1\\u606F\\u4E3A\\u7A7A\n\nsys.dept.deptName.inexistence={0} \\u90E8\\u95E8\\u540D\\u79F0\\u4E0D\\u5B58\\u5728\n\nsys.post.postName.inexistence={0} \\u5C97\\u4F4D\\u540D\\u79F0\\u4E0D\\u5B58\\u5728\nsys.post.nameOrCode.existing={0} {1} \\u5C97\\u4F4D\\u540D\\u6216\\u5C97\\u4F4D\\u7F16\\u7801\\u5DF2\\u7ECF\\u5B58\\u5728\n\nsys.role.roleName.inexistence={0} \\u89D2\\u8272\\u540D\\u79F0\\u4E0D\\u5B58\\u5728\nsys.role.nameOrCode.existing={0} {1} \\u89D2\\u8272\\u540D\\u6216\\u89D2\\u8272\\u7F16\\u7801\\u5DF2\\u7ECF\\u5B58\\u5728\n\nsys.param.delete.system=\\u7CFB\\u7EDF\\u5185\\u7F6E\\u53C2\\u6570\\u4E0D\\u80FD\\u5220\\u9664\nsys.param.config.error={0} \\u7CFB\\u7EDF\\u53C2\\u6570\\u914D\\u7F6E\\u9519\\u8BEF\n\nsys.menu.delete.existing=\\u83DC\\u5355\\u542B\\u6709\\u4E0B\\u7EA7\\u4E0D\\u80FD\\u5220\\u9664\n\nsys.app.sms.often=\\u9A8C\\u8BC1\\u7801\\u53D1\\u9001\\u8FC7\\u9891\\u7E41\nsys.app.sms.error=\\u9A8C\\u8BC1\\u7801\\u9519\\u8BEF\nsys.app.phone.unregistered={0} \\u624B\\u673A\\u53F7\\u672A\\u6CE8\\u518C\nsys.app.sms.blend.unregistered=\\u77ED\\u4FE1\\u6E20\\u9053\\u672A\\u914D\\u7F6E\\uFF0C\\u8BF7\\u8054\\u7CFB\\u7BA1\\u7406\\u5458\n\nsys.dict.delete.system=\\u7CFB\\u7EDF\\u5185\\u7F6E\\u5B57\\u5178\\u9879\\u76EE\\u4E0D\\u80FD\\u5220\\u9664\nsys.dict.update.system=\\u7CFB\\u7EDF\\u5185\\u7F6E\\u5B57\\u5178\\u9879\\u76EE\\u4E0D\\u80FD\\u4FEE\\u6539\n"
  },
  {
    "path": "pig-common/pig-common-core/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration debug=\"false\" scan=\"false\">\n\t<springProperty scop=\"context\" name=\"spring.application.name\" source=\"spring.application.name\" defaultValue=\"\"/>\n\t<property name=\"log.path\" value=\"logs/${spring.application.name}\"/>\n\t<!-- 彩色日志格式 -->\n\t<property name=\"CONSOLE_LOG_PATTERN\"\n\t\t\t  value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n\t<conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n\t<conversionRule conversionWord=\"wex\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n\t<conversionRule conversionWord=\"wEx\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n\t<!-- Console log output -->\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>${CONSOLE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file debug output -->\n\t<appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/debug.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file error output -->\n\t<appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/error.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t\t<filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n\t\t\t<level>ERROR</level>\n\t\t</filter>\n\t</appender>\n\n\t<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n\t<root level=\"INFO\">\n\t\t<appender-ref ref=\"console\"/>\n\t\t<appender-ref ref=\"debug\"/>\n\t\t<appender-ref ref=\"error\"/>\n\t</root>\n</configuration>\n"
  },
  {
    "path": "pig-common/pig-common-datasource/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>pig-common</artifactId>\n        <groupId>com.pig4cloud</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>pig-common-datasource</artifactId>\n    <packaging>jar</packaging>\n    <description>pig 动态切换数据源</description>\n\n    <dependencies>\n        <!--mybatis-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/DynamicDataSourceAutoConfiguration.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.datasource;\n\nimport com.baomidou.dynamic.datasource.creator.DataSourceCreator;\nimport com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;\nimport com.baomidou.dynamic.datasource.creator.hikaricp.HikariDataSourceCreator;\nimport com.baomidou.dynamic.datasource.processor.DsJakartaHeaderProcessor;\nimport com.baomidou.dynamic.datasource.processor.DsJakartaSessionProcessor;\nimport com.baomidou.dynamic.datasource.processor.DsProcessor;\nimport com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;\nimport com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;\nimport com.pig4cloud.pig.common.datasource.config.*;\nimport lombok.RequiredArgsConstructor;\nimport org.jasypt.encryption.StringEncryptor;\nimport org.springframework.beans.factory.BeanFactory;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.expression.BeanFactoryResolver;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 动态数据源切换配置\n *\n * @author lengleng\n * @date 2020-02-06\n */\n@Configuration\n@RequiredArgsConstructor\n@AutoConfigureAfter(DataSourceAutoConfiguration.class)\n@EnableConfigurationProperties(DataSourceProperties.class)\npublic class DynamicDataSourceAutoConfiguration {\n\n\t/**\n\t * 获取动态数据源提供者\n\t * @param defaultDataSourceCreator 默认数据源创建器\n\t * @param stringEncryptor 字符串加密器\n\t * @param properties 数据源属性\n\t * @return 动态数据源提供者\n\t */\n\t@Bean\n\tpublic DynamicDataSourceProvider dynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,\n\t\t\tStringEncryptor stringEncryptor, DataSourceProperties properties) {\n\t\treturn new JdbcDynamicDataSourceProvider(defaultDataSourceCreator, stringEncryptor, properties);\n\t}\n\n\t/**\n\t * 主数据源提供程序\n\t * @param defaultDataSourceCreator 默认数据源创建者\n\t * @param properties 性能\n\t * @return {@link DynamicDataSourceProvider }\n\t */\n\t@Bean\n\tpublic DynamicDataSourceProvider masterDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,\n\t\t\tDataSourceProperties properties) {\n\t\treturn new MasterDataSourceProvider(defaultDataSourceCreator, properties);\n\t}\n\n\t/**\n\t * 获取默认数据源创建器\n\t * @param druidDataSourceCreator Druid数据源创建器\n\t * @return 默认数据源创建器\n\t */\n\t@Bean\n\tpublic DefaultDataSourceCreator defaultDataSourceCreator(HikariDataSourceCreator druidDataSourceCreator) {\n\t\tDefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();\n\t\tList<DataSourceCreator> creators = new ArrayList<>();\n\t\tcreators.add(druidDataSourceCreator);\n\t\tdefaultDataSourceCreator.setCreators(creators);\n\t\treturn defaultDataSourceCreator;\n\t}\n\n\t/**\n\t * 获取数据源处理器\n\t * @return 数据源处理器\n\t */\n\t@Bean\n\tpublic DsProcessor dsProcessor(BeanFactory beanFactory) {\n\t\tDsProcessor lastParamDsProcessor = new LastParamDsProcessor();\n\t\tDsProcessor headerProcessor = new DsJakartaHeaderProcessor();\n\t\tDsProcessor sessionProcessor = new DsJakartaSessionProcessor();\n\t\tDsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();\n\t\tspelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));\n\t\tlastParamDsProcessor.setNextProcessor(headerProcessor);\n\t\theaderProcessor.setNextProcessor(sessionProcessor);\n\t\tsessionProcessor.setNextProcessor(spelExpressionProcessor);\n\t\treturn lastParamDsProcessor;\n\t}\n\n\t/**\n\t * 获取清除TTL数据源过滤器\n\t * @return 清除TTL数据源过滤器\n\t */\n\t@Bean\n\tpublic ClearTtlDataSourceFilter clearTtlDsFilter() {\n\t\treturn new ClearTtlDataSourceFilter();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/annotation/EnableDynamicDataSource.java",
    "content": "package com.pig4cloud.pig.common.datasource.annotation;\n\nimport com.pig4cloud.pig.common.datasource.DynamicDataSourceAutoConfiguration;\nimport org.springframework.context.annotation.Import;\n\nimport java.lang.annotation.*;\n\n/**\n * 开启动态数据源注解\n * <p>\n * 用于启用动态数据源自动配置功能\n *\n * @author lengleng\n * @date 2025/07/14\n */\n@Target({ ElementType.TYPE })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Import(DynamicDataSourceAutoConfiguration.class)\npublic @interface EnableDynamicDataSource {\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/ClearTtlDataSourceFilter.java",
    "content": "package com.pig4cloud.pig.common.datasource.config;\n\nimport com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;\nimport org.springframework.core.Ordered;\nimport org.springframework.web.filter.GenericFilterBean;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.ServletRequest;\nimport jakarta.servlet.ServletResponse;\nimport java.io.IOException;\n\n/**\n * 清空上文的DS 设置避免污染当前线程\n *\n * @author lengleng\n * @date 2020/12/11\n */\npublic class ClearTtlDataSourceFilter extends GenericFilterBean implements Ordered {\n\n\t@Override\n\tpublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)\n\t\t\tthrows IOException, ServletException {\n\t\tDynamicDataSourceContextHolder.clear();\n\t\tfilterChain.doFilter(servletRequest, servletResponse);\n\t\tDynamicDataSourceContextHolder.clear();\n\t}\n\n\t@Override\n\tpublic int getOrder() {\n\t\treturn Integer.MIN_VALUE;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/DataSourceProperties.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.datasource.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n/**\n * 数据源配置属性\n *\n * @author lengleng\n * @date 2025/07/14\n */\n@Data\n@ConfigurationProperties(\"spring.datasource\")\npublic class DataSourceProperties {\n\n\t/**\n\t * 用户名\n\t */\n\tprivate String username;\n\n\t/**\n\t * 密码\n\t */\n\tprivate String password;\n\n\t/**\n\t * jdbcurl\n\t */\n\tprivate String url;\n\n\t/**\n\t * 驱动类型\n\t */\n\tprivate String driverClassName;\n\n\t/**\n\t * 查询数据源的SQL\n\t */\n\tprivate String queryDsSql = \"select * from gen_datasource_conf where del_flag = '0'\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/JdbcDynamicDataSourceProvider.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.datasource.config;\n\nimport com.baomidou.dynamic.datasource.creator.DataSourceProperty;\nimport com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;\nimport com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;\nimport com.pig4cloud.pig.common.datasource.support.DataSourceConstants;\nimport com.pig4cloud.pig.common.datasource.util.DsConfTypeEnum;\nimport com.pig4cloud.pig.common.datasource.util.DsJdbcUrlEnum;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jasypt.encryption.StringEncryptor;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * JDBC动态数据源提供者：从数据源中获取配置信息\n *\n * @author lengleng\n * @date 2025/07/14\n */\n@Slf4j\npublic class JdbcDynamicDataSourceProvider extends AbstractJdbcDataSourceProvider {\n\n\tprivate final DataSourceProperties properties;\n\n\tprivate final StringEncryptor stringEncryptor;\n\n\tpublic JdbcDynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,\n\t\t\tStringEncryptor stringEncryptor, DataSourceProperties properties) {\n\t\tsuper(defaultDataSourceCreator, properties.getDriverClassName(), properties.getUrl(), properties.getUsername(),\n\t\t\t\tproperties.getPassword());\n\t\tthis.stringEncryptor = stringEncryptor;\n\t\tthis.properties = properties;\n\t}\n\n\t/**\n\t * 执行语句获得数据源参数\n\t * @param statement 语句\n\t * @return 数据源参数\n\t * @throws SQLException sql异常\n\t */\n\t@Override\n\tprotected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {\n\n\t\tMap<String, DataSourceProperty> map = new HashMap<>(8);\n\n\t\ttry {\n\t\t\tResultSet rs = statement.executeQuery(properties.getQueryDsSql());\n\n\t\t\twhile (rs.next()) {\n\t\t\t\tString name = rs.getString(DataSourceConstants.NAME);\n\t\t\t\tString username = rs.getString(DataSourceConstants.DS_USER_NAME);\n\t\t\t\tString password = rs.getString(DataSourceConstants.DS_USER_PWD);\n\t\t\t\tInteger confType = rs.getInt(DataSourceConstants.DS_CONFIG_TYPE);\n\t\t\t\tString dsType = rs.getString(DataSourceConstants.DS_TYPE);\n\n\t\t\t\tDataSourceProperty property = new DataSourceProperty();\n\t\t\t\tproperty.setUsername(username);\n\t\t\t\tproperty.setPassword(stringEncryptor.decrypt(password));\n\n\t\t\t\tString url;\n\t\t\t\t// JDBC 配置形式\n\t\t\t\tDsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(dsType);\n\t\t\t\tif (DsConfTypeEnum.JDBC.getType().equals(confType)) {\n\t\t\t\t\turl = rs.getString(DataSourceConstants.DS_JDBC_URL);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tString host = rs.getString(DataSourceConstants.DS_HOST);\n\t\t\t\t\tString port = rs.getString(DataSourceConstants.DS_PORT);\n\t\t\t\t\tString dsName = rs.getString(DataSourceConstants.DS_NAME);\n\t\t\t\t\turl = String.format(urlEnum.getUrl(), host, port, dsName);\n\t\t\t\t}\n\t\t\t\tproperty.setUrl(url);\n\t\t\t\tmap.put(name, property);\n\t\t\t}\n\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlog.warn(\"动态数据源配置表异常:{}\", e.getMessage());\n\t\t}\n\n\t\treturn map;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/LastParamDsProcessor.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.datasource.config;\n\nimport com.baomidou.dynamic.datasource.processor.DsProcessor;\nimport com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;\nimport org.aopalliance.intercept.MethodInvocation;\n\n/**\n * 参数数据源解析 @DS(\"#last\")\n *\n * @author lengleng\n * @date 2020/2/6\n */\npublic class LastParamDsProcessor extends DsProcessor {\n\n\tprivate static final String LAST_PREFIX = \"#last\";\n\n\t/**\n\t * 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器\n\t * @param key DS注解里的内容\n\t * @return 是否匹配\n\t */\n\t@Override\n\tpublic boolean matches(String key) {\n\t\tif (key.startsWith(LAST_PREFIX)) {\n\t\t\t// https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/213\n\t\t\tDynamicDataSourceContextHolder.clear();\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * 抽象最终决定数据源\n\t * @param invocation 方法执行信息\n\t * @param key DS注解里的内容\n\t * @return 数据源名称\n\t */\n\t@Override\n\tpublic String doDetermineDatasource(MethodInvocation invocation, String key) {\n\t\tObject[] arguments = invocation.getArguments();\n\t\treturn String.valueOf(arguments[arguments.length - 1]);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/MasterDataSourceProvider.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.datasource.config;\n\nimport com.baomidou.dynamic.datasource.creator.DataSourceProperty;\nimport com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;\nimport com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;\n\nimport javax.sql.DataSource;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.pig4cloud.pig.common.datasource.support.DataSourceConstants.DS_MASTER;\n\n/**\n * 主数据源提供者，用于保证原有配置有效性并扩展其他数据源，和原有spring.datasource配置兼容。\n *\n * @author lengleng\n * @date 2025/07/14\n */\npublic class MasterDataSourceProvider extends AbstractDataSourceProvider {\n\n\tprivate final DataSourceProperties properties;\n\n\tprivate final DefaultDataSourceCreator defaultDataSourceCreator;\n\n\tpublic MasterDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,\n\t\t\tDataSourceProperties properties) {\n\t\tsuper(defaultDataSourceCreator);\n\t\tthis.properties = properties;\n\t\tthis.defaultDataSourceCreator = defaultDataSourceCreator;\n\t}\n\n\t/**\n\t * 加载所有数据源\n\t * @return 所有数据源，key为数据源名称\n\t */\n\t@Override\n\tpublic Map<String, DataSource> loadDataSources() {\n\t\tMap<String, DataSource> map = new HashMap<>();\n\t\t// 添加默认主数据源\n\t\tDataSourceProperty property = new DataSourceProperty();\n\t\tproperty.setUsername(properties.getUsername());\n\t\tproperty.setPassword(properties.getPassword());\n\t\tproperty.setUrl(properties.getUrl());\n\t\tmap.put(DS_MASTER, defaultDataSourceCreator.createDataSource(property));\n\t\treturn map;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/support/DataSourceConstants.java",
    "content": "package com.pig4cloud.pig.common.datasource.support;\n\n/**\n * 数据源相关常量\n *\n * @author lengleng\n * @date 2019-04-01\n */\npublic interface DataSourceConstants {\n\n\t/**\n\t * 数据源名称\n\t */\n\tString NAME = \"name\";\n\n\t/**\n\t * 默认数据源（master）\n\t */\n\tString DS_MASTER = \"master\";\n\n\t/**\n\t * jdbcurl\n\t */\n\tString DS_JDBC_URL = \"url\";\n\n\t/**\n\t * 配置类型\n\t */\n\tString DS_CONFIG_TYPE = \"conf_type\";\n\n\t/**\n\t * 用户名\n\t */\n\tString DS_USER_NAME = \"username\";\n\n\t/**\n\t * 密码\n\t */\n\tString DS_USER_PWD = \"password\";\n\n\t/**\n\t * 数据库类型\n\t */\n\tString DS_TYPE = \"ds_type\";\n\n\t/**\n\t * 数据库名称\n\t */\n\tString DS_NAME = \"ds_name\";\n\n\t/**\n\t * 主机类型\n\t */\n\tString DS_HOST = \"host\";\n\n\t/**\n\t * 端口\n\t */\n\tString DS_PORT = \"port\";\n\n\t/**\n\t * 实例名称\n\t */\n\tString DS_INSTANCE = \"instance\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/util/DsConfTypeEnum.java",
    "content": "package com.pig4cloud.pig.common.datasource.util;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 数据源配置类型\n *\n * @author lengleng\n * @date 2020/12/11\n */\n@Getter\n@AllArgsConstructor\npublic enum DsConfTypeEnum {\n\n\t/**\n\t * 主机链接\n\t */\n\tHOST(0, \"主机链接\"),\n\n\t/**\n\t * JDBC链接\n\t */\n\tJDBC(1, \"JDBC链接\");\n\n\tprivate final Integer type;\n\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/util/DsJdbcUrlEnum.java",
    "content": "package com.pig4cloud.pig.common.datasource.util;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * JDBC URL 枚举\n *\n * @author lengleng\n * @date 2020/12/11\n */\n@Getter\n@AllArgsConstructor\npublic enum DsJdbcUrlEnum {\n\n\t/**\n\t * mysql 数据库\n\t */\n\tMYSQL(\"mysql\",\n\t\t\t\"jdbc:mysql://%s:%s/%s?characterEncoding=utf8\"\n\t\t\t\t\t+ \"&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true\"\n\t\t\t\t\t+ \"&useLegacyDatetimeCode=false&allowMultiQueries=true&allowPublicKeyRetrieval=true\",\n\t\t\t\"select 1\", \"mysql8 链接\"),\n\n\t/**\n\t * pg 数据库\n\t */\n\tPG(\"pg\", \"jdbc:postgresql://%s:%s/%s\", \"select 1\", \"postgresql 链接\"),\n\n\t/**\n\t * SQL SERVER\n\t */\n\tMSSQL(\"mssql\", \"jdbc:sqlserver://%s:%s;database=%s;characterEncoding=UTF-8\", \"select 1\", \"sqlserver 链接\"),\n\n\t/**\n\t * oracle\n\t */\n\tORACLE(\"oracle\", \"jdbc:oracle:thin:@%s:%s:%s\", \"select 1 from dual\", \"oracle 链接\"),\n\n\t/**\n\t * db2\n\t */\n\tDB2(\"db2\", \"jdbc:db2://%s:%s/%s\", \"select 1 from sysibm.sysdummy1\", \"DB2 TYPE4 连接\"),\n\n\t/**\n\t * 达梦\n\t */\n\tDM(\"dm\", \"jdbc:dm://%s:%s/%s\", \"select 1 from dual\", \"达梦连接\"),\n\n\t/**\n\t * pg 数据库\n\t */\n\tHIGHGO(\"highgo\", \"jdbc:highgo://%s:%s/%s\", \"select 1\", \"highgo 链接\");\n\n\tprivate final String dbName;\n\n\tprivate final String url;\n\n\tprivate final String validationQuery;\n\n\tprivate final String description;\n\n\tpublic static DsJdbcUrlEnum get(String dsType) {\n\t\treturn Arrays.stream(DsJdbcUrlEnum.values())\n\t\t\t.filter(dsJdbcUrlEnum -> dsType.equals(dsJdbcUrlEnum.getDbName()))\n\t\t\t.findFirst()\n\t\t\t.get();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-excel/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ /*\n  ~  *  Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).\n  ~  *  <p>\n  ~  *  Licensed under the GNU Lesser General Public License 3.0 (the \"License\");\n  ~  *  you may not use this file except in compliance with the License.\n  ~  *  You may obtain a copy of the License at\n  ~  *  <p>\n  ~  * https://www.gnu.org/licenses/lgpl.html\n  ~  *  <p>\n  ~  * Unless required by applicable law or agreed to in writing, software\n  ~  * distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~  * See the License for the specific language governing permissions and\n  ~  * limitations under the License.\n  ~  */\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-excel</artifactId>\n    <packaging>jar</packaging>\n\n    <description>excel 导入导出处理模块</description>\n\n    <dependencies>\n        <!--核心依赖,提供字典查询能力-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <!-- excel 导入导出工具类：https://github.com/pig-mesh/excel-spring-boot-starter-->\n        <dependency>\n            <groupId>com.pig4cloud.excel</groupId>\n            <artifactId>excel-spring-boot-starter</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/ExcelAutoConfiguration.java",
    "content": "package com.pig4cloud.pig.common.excel;\n\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.excel.provider.RemoteDictApiService;\nimport com.pig4cloud.pig.common.excel.provider.RemoteDictDataProvider;\nimport com.pig4cloud.plugin.excel.handler.DictDataProvider;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.client.RestClient;\nimport org.springframework.web.client.support.RestClientAdapter;\nimport org.springframework.web.service.invoker.HttpServiceProxyFactory;\n\nimport java.util.Optional;\n\n/**\n * Excel 自动装配类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@AutoConfiguration\npublic class ExcelAutoConfiguration {\n\n\t/**\n\t * 创建远程字典API服务实例\n\t * @param restClientBuilderOptional RestClient构建器的可选对象\n\t * @return {@link RemoteDictApiService} 远程字典API服务实例\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic RemoteDictApiService remoteDictApiService(Optional<RestClient.Builder> restClientBuilderOptional) {\n\t\tRestClient client = restClientBuilderOptional.orElseGet(RestClient::builder)\n\t\t\t.baseUrl(getBaseUrl())\n\t\t\t.defaultHeader(SecurityConstants.FROM, SecurityConstants.FROM_IN)\n\t\t\t.build();\n\t\tHttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client)).build();\n\t\treturn factory.createClient(RemoteDictApiService.class);\n\t}\n\n\t/**\n\t * 创建字典数据提供程序\n\t * @param remoteDictApiService 远程字典API服务\n\t * @return 字典数据提供程序实例\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic DictDataProvider dictDataProvider(RemoteDictApiService remoteDictApiService) {\n\t\treturn new RemoteDictDataProvider(remoteDictApiService);\n\t}\n\n\t/**\n\t * 获取基础URL\n\t * @return 根据当前架构模式组装的基础URL字符串\n\t */\n\tprivate String getBaseUrl() {\n\t\t// 根据当前架构模式，组装URL\n\t\tif (SpringContextHolder.isMicro()) {\n\t\t\treturn String.format(\"http://%s\", ServiceNameConstants.UPMS_SERVICE);\n\t\t}\n\t\telse {\n\t\t\treturn String.format(\"http://%s\", SpringContextHolder.getEnvironment()\n\t\t\t\t.resolvePlaceholders(\"127.0.0.1:${server.port}${server.servlet.context-path}\"));\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictApiService.java",
    "content": "package com.pig4cloud.pig.common.excel.provider;\n\nimport com.pig4cloud.pig.common.core.util.R;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.service.annotation.GetExchange;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 远程字典API服务接口，基于RestClient GetExchange实现\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface RemoteDictApiService {\n\n\t/**\n\t * 根据类型获取字典数据\n\t * @param type 字典类型\n\t * @return 包含字典数据的响应对象，字典数据以Map列表形式返回\n\t */\n\t@GetExchange(\"/dict/remote/type/{type}\")\n\tR<List<Map<String, Object>>> getDictByType(@PathVariable String type);\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictDataProvider.java",
    "content": "package com.pig4cloud.pig.common.excel.provider;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.map.MapUtil;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.plugin.excel.handler.DictDataProvider;\nimport com.pig4cloud.plugin.excel.vo.DictEnum;\nimport lombok.RequiredArgsConstructor;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 远程字典数据提供程序实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class RemoteDictDataProvider implements DictDataProvider {\n\n\tprivate final RemoteDictApiService remoteDictApiService;\n\n\t/**\n\t * 根据类型获取字典枚举数组\n\t * @param type 字典类型\n\t * @return 字典枚举数组，无数据时返回空数组\n\t */\n\t@Override\n\tpublic DictEnum[] getDict(String type) {\n\t\tR<List<Map<String, Object>>> dictDataListR = remoteDictApiService.getDictByType(type);\n\t\tList<Map<String, Object>> dictDataList = dictDataListR.getData();\n\t\tif (CollUtil.isEmpty(dictDataList)) {\n\t\t\treturn new DictEnum[0];\n\t\t}\n\n\t\t// 构建 DictEnum 数组\n\t\tDictEnum.Builder dictEnumBuilder = DictEnum.builder();\n\t\tfor (Map<String, Object> dictData : dictDataList) {\n\t\t\tString value = MapUtil.getStr(dictData, \"value\");\n\t\t\tString label = MapUtil.getStr(dictData, \"label\");\n\t\t\tdictEnumBuilder.add(value, label);\n\t\t}\n\n\t\treturn dictEnumBuilder.build();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": " com.pig4cloud.pig.common.excel.ExcelAutoConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-feign/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n    <packaging>jar</packaging>\n    <artifactId>pig-common-feign</artifactId>\n    <description>feign-sentinel服务降级熔断、限流组件</description>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>\n        </dependency>\n        <!--feign 依赖-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-openfeign</artifactId>\n        </dependency>\n        <!-- okhttp 扩展 -->\n        <dependency>\n            <groupId>io.github.openfeign</groupId>\n            <artifactId>feign-okhttp</artifactId>\n        </dependency>\n        <!-- LB 扩展 -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-loadbalancer</artifactId>\n        </dependency>\n        <!--caffeine 替换LB 默认缓存实现-->\n        <dependency>\n            <groupId>com.github.ben-manes.caffeine</groupId>\n            <artifactId>caffeine</artifactId>\n        </dependency>\n        <!--oauth server 依赖-->\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-core</artifactId>\n        </dependency>\n        <!-- 异常枚举 -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/PigFeignAutoConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign;\n\nimport com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;\nimport com.pig4cloud.pig.common.feign.core.PigFeignInnerRequestInterceptor;\nimport com.pig4cloud.pig.common.feign.core.PigFeignRequestCloseInterceptor;\nimport com.pig4cloud.pig.common.feign.sentinel.ext.PigSentinelFeign;\nimport feign.Feign;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.cloud.openfeign.PigFeignClientsRegistrar;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.context.annotation.Scope;\n\n/**\n * Sentinel Feign 自动配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Configuration(proxyBeanMethods = false)\n@Import(PigFeignClientsRegistrar.class)\n@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)\npublic class PigFeignAutoConfiguration {\n\n\t/**\n\t * 创建Feign.Builder实例，支持Sentinel功能\n\t * @return Feign.Builder实例\n\t * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建\n\t * @ConditionalOnProperty 当配置feign.sentinel.enabled为true时生效\n\t * @Scope 指定bean作用域为prototype\n\t */\n\t@Bean\n\t@Scope(\"prototype\")\n\t@ConditionalOnMissingBean\n\t@ConditionalOnProperty(name = \"feign.sentinel.enabled\")\n\tpublic Feign.Builder feignSentinelBuilder() {\n\t\treturn PigSentinelFeign.builder();\n\t}\n\n\t/**\n\t * 创建并返回PigFeignRequestCloseInterceptor实例\n\t * @return PigFeignRequestCloseInterceptor实例\n\t */\n\t@Bean\n\tpublic PigFeignRequestCloseInterceptor pigFeignRequestCloseInterceptor() {\n\t\treturn new PigFeignRequestCloseInterceptor();\n\t}\n\n\t/**\n\t * 创建并返回PigFeignInnerRequestInterceptor实例\n\t * @return PigFeignInnerRequestInterceptor 内部请求拦截器实例\n\t */\n\t@Bean\n\tpublic PigFeignInnerRequestInterceptor pigFeignInnerRequestInterceptor() {\n\t\treturn new PigFeignInnerRequestInterceptor();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/EnablePigFeignClients.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign.annotation;\n\nimport org.springframework.cloud.openfeign.EnableFeignClients;\nimport org.springframework.core.annotation.AliasFor;\n\nimport java.lang.annotation.*;\n\n/**\n * 启用Pig Feign客户端注解\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@EnableFeignClients\npublic @interface EnablePigFeignClients {\n\n\t/**\n\t * {@link #basePackages()}属性的别名。允许更简洁的注解声明\n\t * @return 'basePackages'数组\n\t */\n\tString[] value() default {};\n\n\t/**\n\t * 扫描注解组件的基础包路径\n\t * <p>\n\t * 与{@link #value()}互为别名且互斥\n\t * <p>\n\t * 对于基于字符串的包名，可使用{@link #basePackageClasses()}作为类型安全的替代方案\n\t * @return 基础包路径数组\n\t */\n\t@AliasFor(annotation = EnableFeignClients.class, attribute = \"basePackages\")\n\tString[] basePackages() default { \"com.pig4cloud.pig\" };\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/NoToken.java",
    "content": "package com.pig4cloud.pig.common.feign.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 服务无token调用声明注解\n * <p>\n * 只有发起方没有 token 时候才需要添加此注解， @NoToken + @Inner\n * <p>\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface NoToken {\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignInnerRequestInterceptor.java",
    "content": "package com.pig4cloud.pig.common.feign.core;\n\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport feign.RequestInterceptor;\nimport feign.RequestTemplate;\nimport org.springframework.core.Ordered;\n\nimport java.lang.reflect.Method;\n\n/**\n * PigFeign 内部请求拦截器，用于处理 Feign 请求的 Token 校验\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PigFeignInnerRequestInterceptor implements RequestInterceptor, Ordered {\n\n\t/**\n\t * 为每个请求调用，使用提供的{@link RequestTemplate}方法添加数据\n\t * @param template 请求模板\n\t */\n\t@Override\n\tpublic void apply(RequestTemplate template) {\n\t\tMethod method = template.methodMetadata().method();\n\t\tNoToken noToken = method.getAnnotation(NoToken.class);\n\t\tif (noToken != null) {\n\t\t\ttemplate.header(SecurityConstants.FROM, SecurityConstants.FROM_IN);\n\t\t}\n\t}\n\n\t@Override\n\tpublic int getOrder() {\n\t\treturn Integer.MIN_VALUE;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignRequestCloseInterceptor.java",
    "content": "package com.pig4cloud.pig.common.feign.core;\n\nimport feign.RequestInterceptor;\nimport org.springframework.http.HttpHeaders;\n\n/**\n * Feign请求连接关闭拦截器\n * <p>\n * 用于设置HTTP连接为关闭状态\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PigFeignRequestCloseInterceptor implements RequestInterceptor {\n\n\t/**\n\t * 设置连接关闭\n\t * @param template 请求模板\n\t */\n\t@Override\n\tpublic void apply(feign.RequestTemplate template) {\n\t\ttemplate.header(HttpHeaders.CONNECTION, \"close\");\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/SentinelAutoConfiguration.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.feign.sentinel;\n\nimport com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;\nimport com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;\nimport com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.common.feign.sentinel.ext.PigSentinelFeign;\nimport com.pig4cloud.pig.common.feign.sentinel.handle.PigUrlBlockHandler;\nimport com.pig4cloud.pig.common.feign.sentinel.parser.PigHeaderRequestOriginParser;\nimport feign.Feign;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Scope;\n\n/**\n * Sentinel 自动配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Configuration(proxyBeanMethods = false)\n@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)\npublic class SentinelAutoConfiguration {\n\n\t/**\n\t * 创建Feign Sentinel构建器\n\t * @return Feign.Builder实例\n\t * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建\n\t * @ConditionalOnProperty 当配置项spring.cloud.openfeign.sentinel.enabled为true时生效\n\t * @Scope 指定bean作用域为prototype\n\t */\n\t@Bean\n\t@Scope(\"prototype\")\n\t@ConditionalOnMissingBean\n\t@ConditionalOnProperty(name = \"spring.cloud.openfeign.sentinel.enabled\")\n\tpublic Feign.Builder feignSentinelBuilder() {\n\t\treturn PigSentinelFeign.builder();\n\t}\n\n\t/**\n\t * 创建默认的BlockExceptionHandler bean\n\t * @param objectMapper 对象映射器\n\t * @return PigUrlBlockHandler实例\n\t * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic BlockExceptionHandler blockExceptionHandler(ObjectMapper objectMapper) {\n\t\treturn new PigUrlBlockHandler(objectMapper);\n\t}\n\n\t/**\n\t * 创建并返回一个RequestOriginParser bean，当容器中不存在该类型的bean时生效\n\t * @return 默认的PigHeaderRequestOriginParser实例\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic RequestOriginParser requestOriginParser() {\n\t\treturn new PigHeaderRequestOriginParser();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelFeign.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign.sentinel.ext;\n\nimport com.alibaba.cloud.sentinel.feign.SentinelContractHolder;\nimport feign.Contract;\nimport feign.Feign;\nimport feign.InvocationHandlerFactory;\nimport feign.Target;\nimport org.springframework.beans.BeansException;\nimport org.springframework.cloud.openfeign.FallbackFactory;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.cloud.openfeign.FeignClientFactory;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.util.StringUtils;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\nimport java.util.Map;\n\n/**\n * 支持自动降级注入的Feign构建器，重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic final class PigSentinelFeign {\n\n\tprivate PigSentinelFeign() {\n\n\t}\n\n\tpublic static PigSentinelFeign.Builder builder() {\n\t\treturn new PigSentinelFeign.Builder();\n\t}\n\n\tpublic static final class Builder extends Feign.Builder implements ApplicationContextAware {\n\n\t\tprivate Contract contract = new Contract.Default();\n\n\t\tprivate ApplicationContext applicationContext;\n\n\t\tprivate FeignClientFactory feignClientFactory;\n\n\t\t@Override\n\t\tpublic Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {\n\t\t\tthrow new UnsupportedOperationException();\n\t\t}\n\n\t\t@Override\n\t\tpublic PigSentinelFeign.Builder contract(Contract contract) {\n\t\t\tthis.contract = contract;\n\t\t\treturn this;\n\t\t}\n\n\t\t@Override\n\t\tpublic Feign internalBuild() {\n\t\t\tsuper.invocationHandlerFactory(new InvocationHandlerFactory() {\n\t\t\t\t@Override\n\t\t\t\tpublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {\n\n\t\t\t\t\t// 查找 FeignClient 上的 降级策略\n\t\t\t\t\tFeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);\n\t\t\t\t\tClass<?> fallback = feignClient.fallback();\n\t\t\t\t\tClass<?> fallbackFactory = feignClient.fallbackFactory();\n\n\t\t\t\t\tString beanName = feignClient.contextId();\n\t\t\t\t\tif (!StringUtils.hasText(beanName)) {\n\t\t\t\t\t\tbeanName = feignClient.name();\n\t\t\t\t\t}\n\n\t\t\t\t\tObject fallbackInstance;\n\t\t\t\t\tFallbackFactory<?> fallbackFactoryInstance;\n\t\t\t\t\tif (void.class != fallback) {\n\t\t\t\t\t\tfallbackInstance = getFromContext(beanName, \"fallback\", fallback, target.type());\n\t\t\t\t\t\treturn new PigSentinelInvocationHandler(target, dispatch,\n\t\t\t\t\t\t\t\tnew FallbackFactory.Default(fallbackInstance));\n\t\t\t\t\t}\n\n\t\t\t\t\tif (void.class != fallbackFactory) {\n\t\t\t\t\t\tfallbackFactoryInstance = (FallbackFactory<?>) getFromContext(beanName, \"fallbackFactory\",\n\t\t\t\t\t\t\t\tfallbackFactory, FallbackFactory.class);\n\t\t\t\t\t\treturn new PigSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);\n\t\t\t\t\t}\n\t\t\t\t\treturn new PigSentinelInvocationHandler(target, dispatch);\n\t\t\t\t}\n\n\t\t\t\tprivate Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {\n\t\t\t\t\tObject fallbackInstance = feignClientFactory.getInstance(name, fallbackType);\n\t\t\t\t\tif (fallbackInstance == null) {\n\t\t\t\t\t\tthrow new IllegalStateException(String\n\t\t\t\t\t\t\t.format(\"No %s instance of type %s found for feign client %s\", type, fallbackType, name));\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!targetType.isAssignableFrom(fallbackType)) {\n\t\t\t\t\t\tthrow new IllegalStateException(String.format(\n\t\t\t\t\t\t\t\t\"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s\",\n\t\t\t\t\t\t\t\ttype, fallbackType, targetType, name));\n\t\t\t\t\t}\n\t\t\t\t\treturn fallbackInstance;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tsuper.contract(new SentinelContractHolder(contract));\n\t\t\treturn super.internalBuild();\n\t\t}\n\n\t\t/**\n\t\t * private Object getFieldValue(Object instance, String fieldName) { Field field =\n\t\t * ReflectionUtils.findField(instance.getClass(), fieldName);\n\t\t * field.setAccessible(true); try { return field.get(instance); } catch\n\t\t * (IllegalAccessException e) { // ignore } return null; }\n\t\t **/\n\n\t\t@Override\n\t\tpublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n\t\t\tthis.applicationContext = applicationContext;\n\t\t\tthis.feignClientFactory = this.applicationContext.getBean(FeignClientFactory.class);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelInvocationHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign.sentinel.ext;\n\nimport com.alibaba.cloud.sentinel.feign.SentinelContractHolder;\nimport com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler;\nimport com.alibaba.csp.sentinel.Entry;\nimport com.alibaba.csp.sentinel.EntryType;\nimport com.alibaba.csp.sentinel.SphU;\nimport com.alibaba.csp.sentinel.Tracer;\nimport com.alibaba.csp.sentinel.context.ContextUtil;\nimport com.alibaba.csp.sentinel.slots.block.BlockException;\nimport com.pig4cloud.pig.common.core.util.R;\nimport feign.Feign;\nimport feign.InvocationHandlerFactory;\nimport feign.MethodMetadata;\nimport feign.Target;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cloud.openfeign.FallbackFactory;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport static feign.Util.checkNotNull;\n\n/**\n * 支持自动降级注入的Sentinel调用处理器，重写{@link SentinelInvocationHandler}\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\npublic class PigSentinelInvocationHandler implements InvocationHandler {\n\n\tpublic static final String EQUALS = \"equals\";\n\n\tpublic static final String HASH_CODE = \"hashCode\";\n\n\tpublic static final String TO_STRING = \"toString\";\n\n\tprivate final Target<?> target;\n\n\tprivate final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;\n\n\tprivate FallbackFactory<?> fallbackFactory;\n\n\tprivate Map<Method, Method> fallbackMethodMap;\n\n\tPigSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,\n\t\t\tFallbackFactory<?> fallbackFactory) {\n\t\tthis.target = checkNotNull(target, \"target\");\n\t\tthis.dispatch = checkNotNull(dispatch, \"dispatch\");\n\t\tthis.fallbackFactory = fallbackFactory;\n\t\tthis.fallbackMethodMap = toFallbackMethod(dispatch);\n\t}\n\n\tPigSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {\n\t\tthis.target = checkNotNull(target, \"target\");\n\t\tthis.dispatch = checkNotNull(dispatch, \"dispatch\");\n\t}\n\n\t@Override\n\tpublic Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {\n\t\tif (EQUALS.equals(method.getName())) {\n\t\t\ttry {\n\t\t\t\tObject otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;\n\t\t\t\treturn equals(otherHandler);\n\t\t\t}\n\t\t\tcatch (IllegalArgumentException e) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (HASH_CODE.equals(method.getName())) {\n\t\t\treturn hashCode();\n\t\t}\n\t\telse if (TO_STRING.equals(method.getName())) {\n\t\t\treturn toString();\n\t\t}\n\n\t\tObject result;\n\t\tInvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);\n\t\t// only handle by HardCodedTarget\n\t\tif (target instanceof Target.HardCodedTarget) {\n\t\t\tTarget.HardCodedTarget<?> hardCodedTarget = (Target.HardCodedTarget) target;\n\t\t\tMethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP\n\t\t\t\t.get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method));\n\t\t\t// resource default is HttpMethod:protocol://url\n\t\t\tif (methodMetadata == null) {\n\t\t\t\tresult = methodHandler.invoke(args);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tString resourceName = methodMetadata.template().method().toUpperCase() + ':' + hardCodedTarget.url()\n\t\t\t\t\t\t+ methodMetadata.template().path();\n\t\t\t\tEntry entry = null;\n\t\t\t\ttry {\n\t\t\t\t\tContextUtil.enter(resourceName);\n\t\t\t\t\tentry = SphU.entry(resourceName, EntryType.OUT, 1, args);\n\t\t\t\t\tresult = methodHandler.invoke(args);\n\t\t\t\t}\n\t\t\t\tcatch (Throwable ex) {\n\t\t\t\t\t// fallback handle\n\t\t\t\t\tif (!BlockException.isBlockException(ex)) {\n\t\t\t\t\t\tTracer.trace(ex);\n\t\t\t\t\t}\n\t\t\t\t\tif (fallbackFactory != null) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\treturn fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex), args);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (IllegalAccessException e) {\n\t\t\t\t\t\t\t// shouldn't happen as method is public due to being an\n\t\t\t\t\t\t\t// interface\n\t\t\t\t\t\t\tthrow new AssertionError(e);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (InvocationTargetException e) {\n\t\t\t\t\t\t\tthrow new AssertionError(e.getCause());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t// 若是R类型 执行自动降级返回R\n\t\t\t\t\t\tif (R.class == method.getReturnType()) {\n\t\t\t\t\t\t\tlog.error(\"feign 服务间调用异常\", ex);\n\t\t\t\t\t\t\treturn R.failed(ex.getLocalizedMessage());\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfinally {\n\t\t\t\t\tif (entry != null) {\n\t\t\t\t\t\tentry.exit(1, args);\n\t\t\t\t\t}\n\t\t\t\t\tContextUtil.exit();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// other target type using default strategy\n\t\t\tresult = methodHandler.invoke(args);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (obj instanceof SentinelInvocationHandler) {\n\t\t\tPigSentinelInvocationHandler other = (PigSentinelInvocationHandler) obj;\n\t\t\treturn target.equals(other.target);\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn target.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn target.toString();\n\t}\n\n\tstatic Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {\n\t\tMap<Method, Method> result = new LinkedHashMap<>();\n\t\tfor (Method method : dispatch.keySet()) {\n\t\t\tmethod.setAccessible(true);\n\t\t\tresult.put(method, method);\n\t\t}\n\t\treturn result;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/GlobalBizExceptionHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign.sentinel.handle;\n\nimport com.alibaba.csp.sentinel.Tracer;\nimport com.pig4cloud.pig.common.core.util.R;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.security.access.AccessDeniedException;\nimport org.springframework.security.core.SpringSecurityMessageSource;\nimport org.springframework.util.Assert;\nimport org.springframework.validation.BindException;\nimport org.springframework.validation.FieldError;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport org.springframework.web.servlet.resource.NoResourceFoundException;\n\nimport java.util.List;\n\n/**\n * 全局业务异常处理器，结合Sentinel处理系统异常\n * <p>\n * 注意：全局异常处理器不能作用在OAuth Server\n * </p>\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Order(10000)\n@RestControllerAdvice\n@ConditionalOnExpression(\"!'${security.oauth2.client.clientId}'.isEmpty()\")\npublic class GlobalBizExceptionHandler {\n\n\t/**\n\t * 全局异常.\n\t * @param e the e\n\t * @return R\n\t */\n\t@ExceptionHandler(Exception.class)\n\t@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n\tpublic R handleGlobalException(Exception e) {\n\t\tlog.error(\"全局异常信息 ex={}\", e.getMessage(), e);\n\n\t\t// 业务异常交由 sentinel 记录\n\t\tTracer.trace(e);\n\t\treturn R.failed(e.getLocalizedMessage());\n\t}\n\n\t/**\n\t * 处理业务校验过程中碰到的非法参数异常 该异常基本由{@link org.springframework.util.Assert}抛出\n\t * @param exception 参数校验异常\n\t * @return API返回结果对象包装后的错误输出结果\n\t * @see Assert#hasLength(String, String)\n\t * @see Assert#hasText(String, String)\n\t * @see Assert#isTrue(boolean, String)\n\t * @see Assert#isNull(Object, String)\n\t * @see Assert#notNull(Object, String)\n\t */\n\t@ExceptionHandler(IllegalArgumentException.class)\n\t@ResponseStatus(HttpStatus.OK)\n\tpublic R handleIllegalArgumentException(IllegalArgumentException exception) {\n\t\tlog.error(\"非法参数,ex = {}\", exception.getMessage(), exception);\n\t\treturn R.failed(exception.getMessage());\n\t}\n\n\t/**\n\t * AccessDeniedException\n\t * @param e the e\n\t * @return R\n\t */\n\t@ExceptionHandler(AccessDeniedException.class)\n\t@ResponseStatus(HttpStatus.FORBIDDEN)\n\tpublic R handleAccessDeniedException(AccessDeniedException e) {\n\t\tString msg = SpringSecurityMessageSource.getAccessor()\n\t\t\t.getMessage(\"AbstractAccessDecisionManager.accessDenied\", e.getMessage());\n\t\tlog.warn(\"拒绝授权异常信息 ex={}\", msg);\n\t\treturn R.failed(msg);\n\t}\n\n\t/**\n\t * validation Exception\n\t * @param exception\n\t * @return R\n\t */\n\t@ExceptionHandler({ MethodArgumentNotValidException.class })\n\t@ResponseStatus(HttpStatus.BAD_REQUEST)\n\tpublic R handleBodyValidException(MethodArgumentNotValidException exception) {\n\t\tList<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();\n\t\tlog.warn(\"参数绑定异常,ex = {}\", fieldErrors.get(0).getDefaultMessage());\n\t\treturn R.failed(String.format(\"%s %s\", fieldErrors.get(0).getField(), fieldErrors.get(0).getDefaultMessage()));\n\t}\n\n\t/**\n\t * validation Exception (以form-data形式传参)\n\t * @param exception\n\t * @return R\n\t */\n\t@ExceptionHandler({ BindException.class })\n\t@ResponseStatus(HttpStatus.BAD_REQUEST)\n\tpublic R bindExceptionHandler(BindException exception) {\n\t\tList<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();\n\t\tlog.warn(\"参数绑定异常,ex = {}\", fieldErrors.get(0).getDefaultMessage());\n\t\treturn R.failed(fieldErrors.get(0).getDefaultMessage());\n\t}\n\n\t/**\n\t * 保持和低版本请求路径不存在的行为一致\n\t * <p>\n\t * <a href=\"https://github.com/spring-projects/spring-boot/issues/38733\">[Spring Boot\n\t * 3.2.0] 404 Not Found behavior #38733</a>\n\t * @param exception\n\t * @return R\n\t */\n\t@ExceptionHandler({ NoResourceFoundException.class })\n\t@ResponseStatus(HttpStatus.NOT_FOUND)\n\tpublic R notFoundExceptionHandler(NoResourceFoundException exception) {\n\t\tlog.debug(\"请求路径 404 {}\", exception.getMessage());\n\t\treturn R.failed(exception.getMessage());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/PigUrlBlockHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign.sentinel.handle;\n\nimport com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;\nimport com.alibaba.csp.sentinel.slots.block.BlockException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.common.core.util.R;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\n\n/**\n * Sentinel统一降级限流策略处理器\n * <p>\n * 实现BlockExceptionHandler接口，处理Sentinel限流降级异常\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class PigUrlBlockHandler implements BlockExceptionHandler {\n\n\tprivate final ObjectMapper objectMapper;\n\n\t@Override\n\tpublic void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e)\n\t\t\tthrows Exception {\n\t\tlog.error(\"sentinel 降级 资源名称{}\", resourceName, e);\n\n\t\tresponse.setContentType(MediaType.APPLICATION_JSON.getType());\n\t\tresponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());\n\t\tresponse.getWriter().print(objectMapper.writeValueAsString(R.failed(e.getMessage())));\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/parser/PigHeaderRequestOriginParser.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.feign.sentinel.parser;\n\nimport com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser;\nimport jakarta.servlet.http.HttpServletRequest;\n\n/**\n * Sentinel 请求头解析判断实现类，用于从HTTP请求头中获取Allow字段值\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PigHeaderRequestOriginParser implements RequestOriginParser {\n\n\t/**\n\t * 请求头获取allow\n\t */\n\tprivate static final String ALLOW = \"Allow\";\n\n\t/**\n\t * 解析HTTP请求中的来源信息\n\t * @param request HTTP请求对象\n\t * @return 解析出的来源信息\n\t */\n\t@Override\n\tpublic String parseOrigin(HttpServletRequest request) {\n\t\treturn request.getHeader(ALLOW);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/java/org/springframework/cloud/openfeign/PigFeignClientsRegistrar.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage org.springframework.cloud.openfeign;\n\nimport lombok.Getter;\nimport org.springframework.beans.factory.BeanClassLoaderAware;\nimport org.springframework.beans.factory.config.BeanDefinitionHolder;\nimport org.springframework.beans.factory.support.AbstractBeanDefinition;\nimport org.springframework.beans.factory.support.BeanDefinitionBuilder;\nimport org.springframework.beans.factory.support.BeanDefinitionReaderUtils;\nimport org.springframework.beans.factory.support.BeanDefinitionRegistry;\nimport org.springframework.boot.context.annotation.ImportCandidates;\nimport org.springframework.context.EnvironmentAware;\nimport org.springframework.context.annotation.ImportBeanDefinitionRegistrar;\nimport org.springframework.core.annotation.AnnotatedElementUtils;\nimport org.springframework.core.annotation.AnnotationAttributes;\nimport org.springframework.core.env.Environment;\nimport org.springframework.core.type.AnnotationMetadata;\nimport org.springframework.lang.Nullable;\nimport org.springframework.util.ClassUtils;\nimport org.springframework.util.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Feign客户端注册器，用于自动配置Feign客户端\n *\n * @author lengleng\n * @date 2025/05/31\n * @see ImportBeanDefinitionRegistrar\n * @see BeanClassLoaderAware\n * @see EnvironmentAware\n */\npublic class PigFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware {\n\n\tprivate final static String BASE_URL = \"http://127.0.0.1:${server.port}${server.servlet.context-path}\";\n\n\t@Getter\n\tprivate ClassLoader beanClassLoader;\n\n\t@Getter\n\tprivate Environment environment;\n\n\t/**\n\t * 注册Bean定义\n\t * @param metadata 注解元数据\n\t * @param registry Bean定义注册器\n\t */\n\t@Override\n\tpublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {\n\t\tregisterFeignClients(registry);\n\t}\n\n\t/**\n\t * 设置Bean类加载器\n\t * @param classLoader 要设置的类加载器\n\t */\n\t@Override\n\tpublic void setBeanClassLoader(ClassLoader classLoader) {\n\t\tthis.beanClassLoader = classLoader;\n\t}\n\n\t/**\n\t * 注册Feign客户端到Spring容器\n\t * @param registry Bean定义注册器，用于注册Bean定义\n\t */\n\tprivate void registerFeignClients(BeanDefinitionRegistry registry) {\n\t\tList<String> feignClients = new ArrayList<>();\n\n\t\t// 支持 springboot 2.7 + 最新版本的配置方式\n\t\tImportCandidates.load(FeignClient.class, getBeanClassLoader()).forEach(feignClients::add);\n\n\t\t// 如果 spring.factories 里为空\n\t\tif (feignClients.isEmpty()) {\n\t\t\treturn;\n\t\t}\n\t\tfor (String className : feignClients) {\n\t\t\ttry {\n\t\t\t\tClass<?> clazz = beanClassLoader.loadClass(className);\n\t\t\t\tAnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz,\n\t\t\t\t\t\tFeignClient.class);\n\t\t\t\tif (attributes == null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// 如果是单体项目自动注入 & url 为空\n\t\t\t\tBoolean isMicro = environment.getProperty(\"spring.cloud.nacos.discovery.enabled\", Boolean.class, true);\n\t\t\t\t// 如果已经存在该 bean，支持原生的 Feign\n\t\t\t\tif (registry.containsBeanDefinition(className) && isMicro) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tregisterClientConfiguration(registry, getClientName(attributes), className,\n\t\t\t\t\t\tattributes.get(\"configuration\"));\n\n\t\t\t\tvalidate(attributes);\n\t\t\t\tBeanDefinitionBuilder definition = BeanDefinitionBuilder\n\t\t\t\t\t.genericBeanDefinition(FeignClientFactoryBean.class);\n\t\t\t\tdefinition.addPropertyValue(\"url\", getUrl(attributes));\n\t\t\t\tdefinition.addPropertyValue(\"path\", getPath(attributes));\n\t\t\t\tString name = getName(attributes);\n\t\t\t\tdefinition.addPropertyValue(\"name\", name);\n\n\t\t\t\t// 兼容最新版本的 spring-cloud-openfeign，尚未发布\n\t\t\t\tStringBuilder aliasBuilder = new StringBuilder(18);\n\t\t\t\tif (attributes.containsKey(\"contextId\")) {\n\t\t\t\t\tString contextId = getContextId(attributes);\n\t\t\t\t\taliasBuilder.append(contextId);\n\t\t\t\t\tdefinition.addPropertyValue(\"contextId\", contextId);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\taliasBuilder.append(name);\n\t\t\t\t}\n\n\t\t\t\tdefinition.addPropertyValue(\"type\", className);\n\t\t\t\tdefinition.addPropertyValue(\"dismiss404\",\n\t\t\t\t\t\tBoolean.parseBoolean(String.valueOf(attributes.get(\"dismiss404\"))));\n\t\t\t\tObject fallbackFactory = attributes.get(\"fallbackFactory\");\n\t\t\t\tif (fallbackFactory != null) {\n\t\t\t\t\tdefinition.addPropertyValue(\"fallbackFactory\", fallbackFactory instanceof Class ? fallbackFactory\n\t\t\t\t\t\t\t: ClassUtils.resolveClassName(fallbackFactory.toString(), null));\n\t\t\t\t}\n\t\t\t\tdefinition.addPropertyValue(\"fallbackFactory\", attributes.get(\"fallbackFactory\"));\n\t\t\t\tdefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);\n\n\t\t\t\tAbstractBeanDefinition beanDefinition = definition.getBeanDefinition();\n\n\t\t\t\t// alias\n\t\t\t\tString alias = aliasBuilder.append(\"FeignClient\").toString();\n\n\t\t\t\t// has a default, won't be null\n\t\t\t\tboolean primary = (Boolean) attributes.get(\"primary\");\n\n\t\t\t\tbeanDefinition.setPrimary(primary);\n\n\t\t\t\tString qualifier = getQualifier(attributes);\n\t\t\t\tif (StringUtils.hasText(qualifier)) {\n\t\t\t\t\talias = qualifier;\n\t\t\t\t}\n\n\t\t\t\tBeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,\n\t\t\t\t\t\tnew String[] { alias });\n\t\t\t\tBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);\n\n\t\t\t}\n\t\t\tcatch (ClassNotFoundException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 验证Feign客户端配置属性\n\t * @param attributes 配置属性Map\n\t */\n\tprivate void validate(Map<String, Object> attributes) {\n\t\tAnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);\n\t\t// This blows up if an aliased property is overspecified\n\t\tFeignClientsRegistrar.validateFallback(annotation.getClass(\"fallback\"));\n\t\tFeignClientsRegistrar.validateFallbackFactory(annotation.getClass(\"fallbackFactory\"));\n\t}\n\n\t/**\n\t * 从属性Map中获取名称\n\t * @param attributes 属性Map\n\t * @return 解析后的名称\n\t */\n\tprivate String getName(Map<String, Object> attributes) {\n\t\tString name = (String) attributes.get(\"serviceId\");\n\t\tif (!StringUtils.hasText(name)) {\n\t\t\tname = (String) attributes.get(\"name\");\n\t\t}\n\t\tif (!StringUtils.hasText(name)) {\n\t\t\tname = (String) attributes.get(\"value\");\n\t\t}\n\t\tname = resolve(name);\n\t\treturn FeignClientsRegistrar.getName(name);\n\t}\n\n\t/**\n\t * 获取上下文ID\n\t * @param attributes 属性Map\n\t * @return 解析后的上下文ID，如果contextId为空则返回名称\n\t */\n\tprivate String getContextId(Map<String, Object> attributes) {\n\t\tString contextId = (String) attributes.get(\"contextId\");\n\t\tif (!StringUtils.hasText(contextId)) {\n\t\t\treturn getName(attributes);\n\t\t}\n\n\t\tcontextId = resolve(contextId);\n\t\treturn FeignClientsRegistrar.getName(contextId);\n\t}\n\n\t/**\n\t * 解析字符串中的占位符\n\t * @param value 待解析的字符串\n\t * @return 解析后的字符串，若输入为空则返回原值\n\t */\n\tprivate String resolve(String value) {\n\t\tif (StringUtils.hasText(value)) {\n\t\t\treturn this.environment.resolvePlaceholders(value);\n\t\t}\n\t\treturn value;\n\t}\n\n\t/**\n\t * 获取URL地址\n\t * @param attributes 属性集合\n\t * @return 解析后的URL地址，微服务环境下返回null\n\t */\n\tprivate String getUrl(Map<String, Object> attributes) {\n\n\t\t// 如果是单体项目自动注入 & url 为空\n\t\tBoolean isMicro = environment.getProperty(\"spring.cloud.nacos.discovery.enabled\", Boolean.class, true);\n\n\t\tif (isMicro) {\n\t\t\treturn null;\n\t\t}\n\n\t\tObject objUrl = attributes.get(\"url\");\n\n\t\tString url = \"\";\n\t\tif (StringUtils.hasText(objUrl.toString())) {\n\t\t\turl = resolve(objUrl.toString());\n\t\t}\n\t\telse {\n\t\t\turl = resolve(BASE_URL);\n\t\t}\n\n\t\treturn FeignClientsRegistrar.getUrl(url);\n\t}\n\n\t/**\n\t * 获取路径\n\t * @param attributes 属性Map，包含路径信息\n\t * @return 解析后的路径\n\t */\n\tprivate String getPath(Map<String, Object> attributes) {\n\t\tString path = resolve((String) attributes.get(\"path\"));\n\t\treturn FeignClientsRegistrar.getPath(path);\n\t}\n\n\t/**\n\t * 从客户端信息中获取qualifier字段值\n\t * @param client 客户端信息映射表，可能为null\n\t * @return qualifier字段值，若无有效值则返回null\n\t */\n\t@Nullable\n\tprivate String getQualifier(@Nullable Map<String, Object> client) {\n\t\tif (client == null) {\n\t\t\treturn null;\n\t\t}\n\t\tString qualifier = (String) client.get(\"qualifier\");\n\t\tif (StringUtils.hasText(qualifier)) {\n\t\t\treturn qualifier;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * 获取客户端名称，依次从contextId、value、name、serviceId字段中获取\n\t * @param client 客户端信息Map，可为null\n\t * @return 客户端名称，可能为null\n\t * @throws IllegalStateException 当无法从client中获取到有效名称时抛出\n\t */\n\t@Nullable\n\tprivate String getClientName(@Nullable Map<String, Object> client) {\n\t\tif (client == null) {\n\t\t\treturn null;\n\t\t}\n\t\tString value = (String) client.get(\"contextId\");\n\t\tif (!StringUtils.hasText(value)) {\n\t\t\tvalue = (String) client.get(\"value\");\n\t\t}\n\t\tif (!StringUtils.hasText(value)) {\n\t\t\tvalue = (String) client.get(\"name\");\n\t\t}\n\t\tif (!StringUtils.hasText(value)) {\n\t\t\tvalue = (String) client.get(\"serviceId\");\n\t\t}\n\t\tif (StringUtils.hasText(value)) {\n\t\t\treturn value;\n\t\t}\n\n\t\tthrow new IllegalStateException(\n\t\t\t\t\"Either 'name' or 'value' must be provided in @\" + FeignClient.class.getSimpleName());\n\t}\n\n\t/**\n\t * 注册客户端配置到BeanDefinitionRegistry\n\t * @param registry Bean定义注册器\n\t * @param name 客户端名称\n\t * @param className 类名\n\t * @param configuration 配置对象\n\t */\n\tprivate void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,\n\t\t\tObject configuration) {\n\t\tBeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);\n\t\tbuilder.addConstructorArgValue(name);\n\t\tbuilder.addConstructorArgValue(className);\n\t\tbuilder.addConstructorArgValue(configuration);\n\t\tregistry.registerBeanDefinition(name + \".\" + FeignClientSpecification.class.getSimpleName(),\n\t\t\t\tbuilder.getBeanDefinition());\n\t}\n\n\t/**\n\t * 设置环境变量\n\t * @param environment 要设置的环境变量对象\n\t */\n\t@Override\n\tpublic void setEnvironment(Environment environment) {\n\t\tthis.environment = environment;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-feign/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.feign.PigFeignAutoConfiguration\ncom.pig4cloud.pig.common.feign.sentinel.SentinelAutoConfiguration\ncom.pig4cloud.pig.common.feign.sentinel.handle.GlobalBizExceptionHandler\n"
  },
  {
    "path": "pig-common/pig-common-log/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-log</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 日志服务</description>\n\n\n    <dependencies>\n        <!--工具类核心包-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-extra</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-http</artifactId>\n        </dependency>\n        <!--UPMS接口模块-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-upms-api</artifactId>\n        </dependency>\n        <!--安全依赖获取上下文信息-->\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-oauth2-core</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/LogAutoConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log;\n\nimport com.pig4cloud.pig.admin.api.feign.RemoteLogService;\nimport com.pig4cloud.pig.common.log.aspect.SysLogAspect;\nimport com.pig4cloud.pig.common.log.config.PigLogProperties;\nimport com.pig4cloud.pig.common.log.event.SysLogListener;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.EnableAsync;\n\n/**\n * 日志自动配置类，用于配置系统日志相关功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@EnableAsync\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(PigLogProperties.class)\n@ConditionalOnProperty(value = \"security.log.enabled\", matchIfMissing = true)\npublic class LogAutoConfiguration {\n\n\t/**\n\t * 创建并返回SysLogListener的Bean实例\n\t * @param logProperties 日志属性配置\n\t * @param remoteLogService 远程日志服务\n\t * @return SysLogListener实例\n\t */\n\t@Bean\n\tpublic SysLogListener sysLogListener(PigLogProperties logProperties, RemoteLogService remoteLogService) {\n\t\treturn new SysLogListener(remoteLogService, logProperties);\n\t}\n\n\t/**\n\t * 创建并返回SysLogAspect的Bean实例\n\t * @return SysLogAspect实例\n\t */\n\t@Bean\n\tpublic SysLogAspect sysLogAspect() {\n\t\treturn new SysLogAspect();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/annotation/SysLog.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 系统日志注解：用于标记需要记录操作日志的方法\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface SysLog {\n\n\t/**\n\t * 描述\n\t * @return {String}\n\t */\n\tString value() default \"\";\n\n\t/**\n\t * spel 表达式\n\t * @return 日志描述\n\t */\n\tString expression() default \"\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/aspect/SysLogAspect.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.aspect;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.log.event.SysLogEvent;\nimport com.pig4cloud.pig.common.log.event.SysLogEventSource;\nimport com.pig4cloud.pig.common.log.util.LogTypeEnum;\nimport com.pig4cloud.pig.common.log.util.SysLogUtils;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.expression.EvaluationContext;\n\n/**\n * 系统日志切面类，通过Spring AOP实现操作日志的异步记录\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Aspect\n@Slf4j\n@RequiredArgsConstructor\npublic class SysLogAspect {\n\n\t/**\n\t * 环绕通知方法，用于处理系统日志记录\n\t * @param point 连接点对象\n\t * @param sysLog 系统日志注解\n\t * @return 目标方法执行结果\n\t * @throws Throwable 目标方法执行可能抛出的异常\n\t */\n\t@Around(\"@annotation(sysLog)\")\n\t@SneakyThrows\n\tpublic Object around(ProceedingJoinPoint point, com.pig4cloud.pig.common.log.annotation.SysLog sysLog) {\n\t\tString strClassName = point.getTarget().getClass().getName();\n\t\tString strMethodName = point.getSignature().getName();\n\t\tlog.debug(\"[类名]:{},[方法]:{}\", strClassName, strMethodName);\n\n\t\tString value = sysLog.value();\n\t\tString expression = sysLog.expression();\n\t\t// 当前表达式存在 SPEL，会覆盖 value 的值\n\t\tif (StrUtil.isNotBlank(expression)) {\n\t\t\t// 解析SPEL\n\t\t\tMethodSignature signature = (MethodSignature) point.getSignature();\n\t\t\tEvaluationContext context = SysLogUtils.getContext(point.getArgs(), signature.getMethod());\n\t\t\ttry {\n\t\t\t\tvalue = SysLogUtils.getValue(context, expression, String.class);\n\t\t\t}\n\t\t\tcatch (Exception e) {\n\t\t\t\t// SPEL 表达式异常，获取 value 的值\n\t\t\t\tlog.error(\"@SysLog 解析SPEL {} 异常\", expression);\n\t\t\t}\n\t\t}\n\n\t\tSysLogEventSource logVo = SysLogUtils.getSysLog();\n\t\tlogVo.setTitle(value);\n\t\t// 获取请求body参数\n\t\tif (StrUtil.isBlank(logVo.getParams())) {\n\t\t\tlogVo.setBody(point.getArgs());\n\t\t}\n\t\t// 发送异步日志事件\n\t\tLong startTime = System.currentTimeMillis();\n\t\tObject obj;\n\n\t\ttry {\n\t\t\tobj = point.proceed();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlogVo.setLogType(LogTypeEnum.ERROR.getType());\n\t\t\tlogVo.setException(e.getMessage());\n\t\t\tthrow e;\n\t\t}\n\t\tfinally {\n\t\t\tLong endTime = System.currentTimeMillis();\n\t\t\tlogVo.setTime(endTime - startTime);\n\t\t\tSpringContextHolder.publishEvent(new SysLogEvent(logVo));\n\t\t}\n\n\t\treturn obj;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/config/PigLogProperties.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.config;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\nimport java.util.List;\n\n/**\n * 日志配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\n@Setter\n@ConfigurationProperties(PigLogProperties.PREFIX)\npublic class PigLogProperties {\n\n\tpublic static final String PREFIX = \"security.log\";\n\n\t/**\n\t * 开启日志记录\n\t */\n\tprivate boolean enabled = true;\n\n\t/**\n\t * 放行字段，password,mobile,idcard,phone\n\t */\n\t@Value(\"${security.log.exclude-fields:password,mobile,idcard,phone}\")\n\tprivate List<String> excludeFields;\n\n\t/**\n\t * 请求报文最大存储长度\n\t */\n\tprivate Integer maxLength = 2000;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/event/SysLogEvent.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.event;\n\nimport java.io.Serial;\nimport org.springframework.context.ApplicationEvent;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\n\n/**\n * 系统日志事件类\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class SysLogEvent extends ApplicationEvent {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 构造方法，根据源SysLog对象创建SysLogEvent\n\t * @param source 源SysLog对象\n\t */\n\tpublic SysLogEvent(SysLog source) {\n\t\tsuper(source);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/event/SysLogEventSource.java",
    "content": "package com.pig4cloud.pig.common.log.event;\n\nimport java.io.Serial;\n\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\n\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 系统日志事件源类，继承自SysLog\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\npublic class SysLogEventSource extends SysLog {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 参数重写成object\n\t */\n\tprivate Object body;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/event/SysLogListener.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.event;\n\nimport java.util.Objects;\n\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.scheduling.annotation.Async;\n\nimport com.fasterxml.jackson.annotation.JsonFilter;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.ser.FilterProvider;\nimport com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;\nimport com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.admin.api.feign.RemoteLogService;\nimport com.pig4cloud.pig.common.core.jackson.PigJavaTimeModule;\nimport com.pig4cloud.pig.common.log.config.PigLogProperties;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\n/**\n * 系统日志监听器：异步处理系统日志事件\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class SysLogListener implements InitializingBean {\n\n\t// new 一个 避免日志脱敏策略影响全局ObjectMapper\n\tprivate final static ObjectMapper objectMapper = new ObjectMapper();\n\n\tprivate final RemoteLogService remoteLogService;\n\n\tprivate final PigLogProperties logProperties;\n\n\t/**\n\t * 异步保存系统日志\n\t * @param event 系统日志事件\n\t */\n\t@SneakyThrows\n\t@Async\n\t@Order\n\t@EventListener(SysLogEvent.class)\n\tpublic void saveSysLog(SysLogEvent event) {\n\t\tSysLogEventSource source = (SysLogEventSource) event.getSource();\n\t\tSysLog sysLog = new SysLog();\n\t\tBeanUtils.copyProperties(source, sysLog);\n\n\t\t// json 格式刷参数放在异步中处理，提升性能\n\t\tif (Objects.nonNull(source.getBody())) {\n\t\t\tString params = objectMapper.writeValueAsString(source.getBody());\n\t\t\tsysLog.setParams(StrUtil.subPre(params, logProperties.getMaxLength()));\n\t\t}\n\n\t\tremoteLogService.saveLog(sysLog);\n\t}\n\n\t@Override\n\tpublic void afterPropertiesSet() {\n\t\tobjectMapper.addMixIn(Object.class, PropertyFilterMixIn.class);\n\t\tString[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]);\n\n\t\tFilterProvider filters = new SimpleFilterProvider().addFilter(\"filter properties by name\",\n\t\t\t\tSimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames));\n\t\tobjectMapper.setFilterProvider(filters);\n\t\tobjectMapper.registerModule(new PigJavaTimeModule());\n\t}\n\n\t/**\n\t * 属性过滤混合类：用于通过名称过滤属性\n\t *\n\t * @author lengleng\n\t * @date 2025/05/31\n\t */\n\t@JsonFilter(\"filter properties by name\")\n\tclass PropertyFilterMixIn {\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/init/ApplicationLoggerInitializer.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.init;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.env.EnvironmentPostProcessor;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.env.ConfigurableEnvironment;\n\n/**\n * 应用日志初始化类：通过环境变量注入 logging.file 自动维护 Spring Boot Admin Logger Viewer\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class ApplicationLoggerInitializer implements EnvironmentPostProcessor, Ordered {\n\n\t/**\n\t * 后处理环境配置，设置日志路径和相关系统属性\n\t * @param environment 可配置的环境对象\n\t * @param application Spring应用对象\n\t */\n\t@Override\n\tpublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {\n\t\tString appName = environment.getProperty(\"spring.application.name\");\n\t\tString logBase = environment.getProperty(\"LOGGING_PATH\", \"logs\");\n\n\t\t// spring boot admin 直接加载日志\n\t\tSystem.setProperty(\"logging.file.name\", String.format(\"%s/%s/debug.log\", logBase, appName));\n\n\t\t// 避免各种依赖的地方组件造成 BeanPostProcessorChecker 警告\n\t\tSystem.setProperty(\"logging.level.org.springframework.context.support.PostProcessorRegistrationDelegate\",\n\t\t\t\t\"ERROR\");\n\n\t\t// 避免 sentinel 1.8.4+ 心跳日志过大\n\t\tSystem.setProperty(\"csp.sentinel.log.level\", \"OFF\");\n\n\t\t// 避免 sentinel 健康检查 server\n\t\tSystem.setProperty(\"management.health.sentinel.enabled\", \"false\");\n\t}\n\n\t@Override\n\tpublic int getOrder() {\n\t\treturn Ordered.LOWEST_PRECEDENCE;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/util/LogTypeEnum.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.util;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 日志类型枚举\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\n@RequiredArgsConstructor\npublic enum LogTypeEnum {\n\n\t/**\n\t * 正常日志类型\n\t */\n\tNORMAL(\"0\", \"正常日志\"),\n\n\t/**\n\t * 错误日志类型\n\t */\n\tERROR(\"9\", \"错误日志\");\n\n\t/**\n\t * 类型\n\t */\n\tprivate final String type;\n\n\t/**\n\t * 描述\n\t */\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/util/SysLogUtils.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.log.util;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.URLUtil;\nimport cn.hutool.extra.servlet.JakartaServletUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.log.config.PigLogProperties;\nimport com.pig4cloud.pig.common.log.event.SysLogEventSource;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.experimental.UtilityClass;\nimport org.springframework.core.StandardReflectionParameterNameDiscoverer;\nimport org.springframework.expression.EvaluationContext;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 系统日志工具类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@UtilityClass\npublic class SysLogUtils {\n\n\t/**\n\t * 获取系统日志事件源\n\t * @return 系统日志事件源对象\n\t */\n\tpublic SysLogEventSource getSysLog() {\n\t\tHttpServletRequest request = ((ServletRequestAttributes) Objects\n\t\t\t.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();\n\t\tSysLogEventSource sysLog = new SysLogEventSource();\n\t\tsysLog.setLogType(LogTypeEnum.NORMAL.getType());\n\t\tsysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));\n\t\tsysLog.setMethod(request.getMethod());\n\t\tsysLog.setRemoteAddr(JakartaServletUtil.getClientIP(request));\n\t\tsysLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));\n\t\tsysLog.setCreateBy(getUsername());\n\t\tsysLog.setServiceId(SpringUtil.getProperty(\"spring.application.name\"));\n\n\t\t// get 参数脱敏\n\t\tPigLogProperties logProperties = SpringContextHolder.getBean(PigLogProperties.class);\n\t\tMap<String, String[]> paramsMap = MapUtil.removeAny(new HashMap<>(request.getParameterMap()),\n\t\t\t\tArrayUtil.toArray(logProperties.getExcludeFields(), String.class));\n\t\tsysLog.setParams(HttpUtil.toParams(paramsMap));\n\t\treturn sysLog;\n\t}\n\n\t/**\n\t * 获取用户名称\n\t * @return username\n\t */\n\tprivate String getUsername() {\n\t\tAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();\n\t\tif (authentication == null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn authentication.getName();\n\t}\n\n\t/**\n\t * 获取spel 定义的参数值\n\t * @param context 参数容器\n\t * @param key key\n\t * @param clazz 需要返回的类型\n\t * @param <T> 返回泛型\n\t * @return 参数值\n\t */\n\tpublic <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {\n\t\tSpelExpressionParser spelExpressionParser = new SpelExpressionParser();\n\t\tExpression expression = spelExpressionParser.parseExpression(key);\n\t\treturn expression.getValue(context, clazz);\n\t}\n\n\t/**\n\t * 获取参数容器\n\t * @param arguments 方法的参数列表\n\t * @param signatureMethod 被执行的方法体\n\t * @return 装载参数的容器\n\t */\n\tpublic EvaluationContext getContext(Object[] arguments, Method signatureMethod) {\n\t\tString[] parameterNames = new StandardReflectionParameterNameDiscoverer().getParameterNames(signatureMethod);\n\t\tEvaluationContext context = new StandardEvaluationContext();\n\t\tif (parameterNames == null) {\n\t\t\treturn context;\n\t\t}\n\t\tfor (int i = 0; i < arguments.length; i++) {\n\t\t\tcontext.setVariable(parameterNames[i], arguments[i]);\n\t\t}\n\t\treturn context;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.log.LogAutoConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/resources/META-INF/spring-configuration-metadata.json",
    "content": "{\n  \"groups\": [\n    {\n      \"name\": \"security.log\",\n      \"type\": \"com.pig4cloud.pig.common.log.config.PigLogProperties\",\n      \"sourceType\": \"com.pig4cloud.pig.common.log.config.PigLogProperties\"\n    }\n  ],\n  \"properties\": [\n    {\n      \"name\": \"security.log.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"开启日志记录\",\n      \"sourceType\": \"com.pig4cloud.pig.common.log.config.PigLogProperties\"\n    },\n    {\n      \"name\": \"security.log.exclude-fields\",\n      \"type\": \"java.util.List<java.lang.String>\",\n      \"description\": \"放行字段，password,mobile,idcard,phone\",\n      \"sourceType\": \"com.pig4cloud.pig.common.log.config.PigLogProperties\"\n    },\n    {\n      \"name\": \"security.log.max-length\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"请求报文最大存储长度\",\n      \"sourceType\": \"com.pig4cloud.pig.common.log.config.PigLogProperties\"\n    }\n  ],\n  \"hints\": []\n}\n"
  },
  {
    "path": "pig-common/pig-common-log/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.env.EnvironmentPostProcessor=\\\n    com.pig4cloud.pig.common.log.init.ApplicationLoggerInitializer\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-mybatis</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig mybatis 封装</description>\n\n\n    <dependencies>\n        <!--hutool-->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-core</artifactId>\n        </dependency>\n        <!--mybatis-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-spring</artifactId>\n        </dependency>\n        <!-- mybatis-plus 3.5.9 需要单独引入此依赖-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-jsqlparser</artifactId>\n        </dependency>\n        <!--swagger  注解-->\n        <dependency>\n            <groupId>io.swagger.core.v3</groupId>\n            <artifactId>swagger-annotations-jakarta</artifactId>\n        </dependency>\n        <!--server-api-->\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!--security 依赖-->\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-core</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/MybatisAutoConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.mybatis;\n\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport com.pig4cloud.pig.common.mybatis.config.MybatisPlusMetaObjectHandler;\nimport com.pig4cloud.pig.common.mybatis.plugins.PigPaginationInnerInterceptor;\nimport com.pig4cloud.pig.common.mybatis.resolver.SqlFilterArgumentResolver;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport java.util.List;\n\n/**\n * MyBatis Plus 统一自动配置类\n * <p>\n * 提供SQL过滤器、分页插件及审计字段自动填充等配置\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Configuration(proxyBeanMethods = false)\npublic class MybatisAutoConfiguration implements WebMvcConfigurer {\n\n\t/**\n\t * 添加SQL过滤器参数解析器，避免SQL注入\n\t * @param argumentResolvers 方法参数解析器列表\n\t */\n\t@Override\n\tpublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {\n\t\targumentResolvers.add(new SqlFilterArgumentResolver());\n\t}\n\n\t/**\n\t * 创建并配置MybatisPlus分页拦截器\n\t * @return 配置好的MybatisPlus拦截器实例\n\t */\n\t@Bean\n\tpublic MybatisPlusInterceptor mybatisPlusInterceptor() {\n\t\tMybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n\t\tinterceptor.addInnerInterceptor(new PigPaginationInnerInterceptor());\n\t\treturn interceptor;\n\t}\n\n\t/**\n\t * 创建并返回MybatisPlusMetaObjectHandler实例，用于审计字段自动填充\n\t * @return MybatisPlusMetaObjectHandler实例\n\t */\n\t@Bean\n\tpublic MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() {\n\t\treturn new MybatisPlusMetaObjectHandler();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/base/BaseEntity.java",
    "content": "package com.pig4cloud.pig.common.mybatis.base;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.TableField;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Getter;\nimport lombok.Setter;\n\n/**\n * 基础实体抽象类，包含通用实体字段\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\n@Setter\npublic class BaseEntity implements Serializable {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 创建者\n\t */\n\t@Schema(description = \"创建人\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate String createBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新者\n\t */\n\t@Schema(description = \"更新人\")\n\t@TableField(fill = FieldFill.INSERT_UPDATE)\n\tprivate String updateBy;\n\n\t/**\n\t * 更新时间\n\t */\n\t@Schema(description = \"更新时间\")\n\t@TableField(fill = FieldFill.INSERT_UPDATE)\n\tprivate LocalDateTime updateTime;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/config/MybatisPlusMetaObjectHandler.java",
    "content": "package com.pig4cloud.pig.common.mybatis.config;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.ibatis.reflection.MetaObject;\nimport org.springframework.security.authentication.AnonymousAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.util.ClassUtils;\n\nimport java.nio.charset.Charset;\nimport java.time.LocalDateTime;\nimport java.util.Optional;\n\n/**\n * MybatisPlus 自动填充处理器，用于实体类字段的自动填充\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\npublic class MybatisPlusMetaObjectHandler implements MetaObjectHandler {\n\n\t/**\n\t * 插入时自动填充字段\n\t * @param metaObject 元对象，用于操作实体类属性\n\t */\n\t@Override\n\tpublic void insertFill(MetaObject metaObject) {\n\t\tlog.debug(\"mybatis plus start insert fill ....\");\n\t\tLocalDateTime now = LocalDateTime.now();\n\n\t\tfillValIfNullByName(\"createTime\", now, metaObject, true);\n\t\tfillValIfNullByName(\"updateTime\", now, metaObject, true);\n\t\tfillValIfNullByName(\"createBy\", getUserName(), metaObject, true);\n\t\tfillValIfNullByName(\"updateBy\", getUserName(), metaObject, true);\n\n\t\t// 删除标记自动填充\n\t\tfillValIfNullByName(\"delFlag\", CommonConstants.STATUS_NORMAL, metaObject, true);\n\t}\n\n\t/**\n\t * 更新时自动填充字段\n\t * @param metaObject 元对象\n\t */\n\t@Override\n\tpublic void updateFill(MetaObject metaObject) {\n\t\tlog.debug(\"mybatis plus start update fill ....\");\n\t\tfillValIfNullByName(\"updateTime\", LocalDateTime.now(), metaObject, true);\n\t\tfillValIfNullByName(\"updateBy\", getUserName(), metaObject, true);\n\t}\n\n\t/**\n\t * 填充值，先判断是否有手动设置，优先手动设置的值，例如：job必须手动设置\n\t * @param fieldName 属性名\n\t * @param fieldVal 属性值\n\t * @param metaObject MetaObject\n\t * @param isCover 是否覆盖原有值,避免更新操作手动入参\n\t */\n\tprivate static void fillValIfNullByName(String fieldName, Object fieldVal, MetaObject metaObject, boolean isCover) {\n\t\t// 0. 如果填充值为空\n\t\tif (fieldVal == null) {\n\t\t\treturn;\n\t\t}\n\n\t\t// 1. 没有 set 方法\n\t\tif (!metaObject.hasSetter(fieldName)) {\n\t\t\treturn;\n\t\t}\n\t\t// 2. 如果用户有手动设置的值\n\t\tObject userSetValue = metaObject.getValue(fieldName);\n\t\tString setValueStr = StrUtil.str(userSetValue, Charset.defaultCharset());\n\t\tif (StrUtil.isNotBlank(setValueStr) && !isCover) {\n\t\t\treturn;\n\t\t}\n\t\t// 3. field 类型相同时设置\n\t\tClass<?> getterType = metaObject.getGetterType(fieldName);\n\t\tif (ClassUtils.isAssignableValue(getterType, fieldVal)) {\n\t\t\tmetaObject.setValue(fieldName, fieldVal);\n\t\t}\n\t}\n\n\t/**\n\t * 获取 spring security 当前的用户名\n\t * @return 当前用户名\n\t */\n\tprivate String getUserName() {\n\t\tAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();\n\t\t// 匿名接口直接返回\n\t\tif (authentication instanceof AnonymousAuthenticationToken) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (Optional.ofNullable(authentication).isPresent()) {\n\t\t\treturn authentication.getName();\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/handler/JsonLongArrayTypeHandler.java",
    "content": "package com.pig4cloud.pig.common.mybatis.handler;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.StrUtil;\nimport lombok.SneakyThrows;\nimport org.apache.ibatis.type.BaseTypeHandler;\nimport org.apache.ibatis.type.JdbcType;\nimport org.apache.ibatis.type.MappedJdbcTypes;\nimport org.apache.ibatis.type.MappedTypes;\n\nimport java.sql.CallableStatement;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n/**\n * MyBatis 长整型数组与字符串类型转换处理器\n * <p>\n * 实现数据库VARCHAR类型与Java Long数组类型的相互转换\n *\n * @author lengleng\n * @date 2025/05/31\n * @see MappedTypes 映射的Java类型为Long[]\n * @see MappedJdbcTypes 映射的JDBC类型为VARCHAR\n */\n@MappedTypes(value = { Long[].class })\n@MappedJdbcTypes(value = JdbcType.VARCHAR)\npublic class JsonLongArrayTypeHandler extends BaseTypeHandler<Long[]> {\n\n\t/**\n\t * 设置非空参数到PreparedStatement中\n\t * @param ps PreparedStatement对象\n\t * @param i 参数位置\n\t * @param parameter 长整型数组参数\n\t * @param jdbcType JDBC类型\n\t * @throws SQLException 数据库操作异常\n\t */\n\t@Override\n\tpublic void setNonNullParameter(PreparedStatement ps, int i, Long[] parameter, JdbcType jdbcType)\n\t\t\tthrows SQLException {\n\t\tps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA));\n\t}\n\n\t/**\n\t * 从ResultSet中获取指定列名的长整型数组结果\n\t * @param rs 结果集\n\t * @param columnName 列名\n\t * @return 转换后的长整型数组，可能为null\n\t * @throws SQLException 数据库访问错误时抛出\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic Long[] getNullableResult(ResultSet rs, String columnName) {\n\t\tString reString = rs.getString(columnName);\n\t\treturn Convert.toLongArray(reString);\n\t}\n\n\t/**\n\t * 从ResultSet中获取指定列的长整型数组\n\t * @param rs 结果集\n\t * @param columnIndex 列索引\n\t * @return 长整型数组，可能为null\n\t * @throws SQLException 数据库访问错误时抛出\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic Long[] getNullableResult(ResultSet rs, int columnIndex) {\n\t\tString reString = rs.getString(columnIndex);\n\t\treturn Convert.toLongArray(reString);\n\t}\n\n\t/**\n\t * 从CallableStatement中获取指定列的长整型数组\n\t * @param cs CallableStatement对象\n\t * @param columnIndex 列索引\n\t * @return 转换后的长整型数组，可能为null\n\t * @throws Exception 数据库访问出错或转换异常时抛出\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic Long[] getNullableResult(CallableStatement cs, int columnIndex) {\n\t\tString reString = cs.getString(columnIndex);\n\t\treturn Convert.toLongArray(reString);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/handler/JsonStringArrayTypeHandler.java",
    "content": "package com.pig4cloud.pig.common.mybatis.handler;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.StrUtil;\nimport lombok.SneakyThrows;\nimport org.apache.ibatis.type.BaseTypeHandler;\nimport org.apache.ibatis.type.JdbcType;\nimport org.apache.ibatis.type.MappedJdbcTypes;\nimport org.apache.ibatis.type.MappedTypes;\n\nimport java.sql.CallableStatement;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n/**\n * MyBatis 字符串数组类型处理器，用于数据库VARCHAR类型与Java字符串数组的相互转换\n *\n * @author lengleng\n * @date 2025/05/31\n * @see MappedTypes 指定处理的Java类型\n * @see MappedJdbcTypes 指定处理的JDBC类型\n */\n@MappedTypes(value = { String[].class })\n@MappedJdbcTypes(value = JdbcType.VARCHAR)\npublic class JsonStringArrayTypeHandler extends BaseTypeHandler<String[]> {\n\n\t/**\n\t * 设置非空参数到PreparedStatement\n\t * @param ps PreparedStatement对象\n\t * @param i 参数位置\n\t * @param parameter 字符串数组参数\n\t * @param jdbcType JDBC类型\n\t * @throws SQLException 数据库操作异常\n\t */\n\t@Override\n\tpublic void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType)\n\t\t\tthrows SQLException {\n\t\tps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA));\n\t}\n\n\t/**\n\t * 从ResultSet中获取指定列名的字符串数组结果，允许为null\n\t * @param rs 结果集\n\t * @param columnName 列名\n\t * @return 转换后的字符串数组，可能为null\n\t * @throws SQLException 数据库访问错误时抛出\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic String[] getNullableResult(ResultSet rs, String columnName) {\n\t\tString reString = rs.getString(columnName);\n\t\treturn Convert.toStrArray(reString);\n\t}\n\n\t/**\n\t * 从ResultSet中获取指定列的可空字符串数组结果\n\t * @param rs 结果集\n\t * @param columnIndex 列索引\n\t * @return 转换后的字符串数组，可能为null\n\t * @throws SQLException 数据库访问错误时抛出\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic String[] getNullableResult(ResultSet rs, int columnIndex) {\n\t\tString reString = rs.getString(columnIndex);\n\t\treturn Convert.toStrArray(reString);\n\t}\n\n\t/**\n\t * 从CallableStatement中获取指定列的可空字符串结果并转换为字符串数组\n\t * @param cs CallableStatement对象\n\t * @param columnIndex 列索引\n\t * @return 转换后的字符串数组\n\t * @throws Exception 如果操作过程中发生错误\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic String[] getNullableResult(CallableStatement cs, int columnIndex) {\n\t\tString reString = cs.getString(columnIndex);\n\t\treturn Convert.toStrArray(reString);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/plugins/PigPaginationInnerInterceptor.java",
    "content": "package com.pig4cloud.pig.common.mybatis.plugins;\n\nimport org.apache.ibatis.executor.Executor;\nimport org.apache.ibatis.mapping.BoundSql;\nimport org.apache.ibatis.mapping.MappedStatement;\nimport org.apache.ibatis.session.ResultHandler;\nimport org.apache.ibatis.session.RowBounds;\n\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.ParameterUtils;\nimport com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect;\n\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\n\n/**\n * 分页拦截器实现类，用于处理分页查询逻辑\n * <p>\n * 当分页大小小于0时自动设置为0，防止全表查询\n *\n * @author lengleng\n * @date 2025/05/31\n * @since 2021年10月11日\n */\n@Data\n@NoArgsConstructor\n@EqualsAndHashCode(callSuper = false)\npublic class PigPaginationInnerInterceptor extends PaginationInnerInterceptor {\n\n\t/**\n\t * 数据库类型\n\t * <p>\n\t * 查看 {@link #findIDialect(Executor)} 逻辑\n\t */\n\tprivate DbType dbType;\n\n\t/**\n\t * 方言实现类\n\t * <p>\n\t * 查看 {@link #findIDialect(Executor)} 逻辑\n\t */\n\tprivate IDialect dialect;\n\n\tpublic PigPaginationInnerInterceptor(DbType dbType) {\n\t\tthis.dbType = dbType;\n\t}\n\n\tpublic PigPaginationInnerInterceptor(IDialect dialect) {\n\t\tthis.dialect = dialect;\n\t}\n\n\t/**\n\t * 在执行查询前处理分页参数\n\t * @param executor 执行器\n\t * @param ms 映射语句\n\t * @param parameter 参数对象\n\t * @param rowBounds 行边界\n\t * @param resultHandler 结果处理器\n\t * @param boundSql 绑定SQL\n\t */\n\t@Override\n\tpublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,\n\t\t\tResultHandler resultHandler, BoundSql boundSql) {\n\t\tIPage<?> page = ParameterUtils.findPage(parameter).orElse(null);\n\t\t// size 小于 0 直接设置为 0 , 即不查询任何数据\n\t\tif (null != page && page.getSize() < 0) {\n\t\t\tpage.setSize(0);\n\t\t}\n\t\tsuper.beforeQuery(executor, ms, page, rowBounds, resultHandler, boundSql);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/resolver/SqlFilterArgumentResolver.java",
    "content": "/*\n *\n *  *  Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).\n *  *  <p>\n *  *  Licensed under the GNU Lesser General Public License 3.0 (the \"License\");\n *  *  you may not use this file except in compliance with the License.\n *  *  You may obtain a copy of the License at\n *  *  <p>\n *  * https://www.gnu.org/licenses/lgpl.html\n *  *  <p>\n *  * Unless required by applicable law or agreed to in writing, software\n *  * distributed under the License is distributed on an \"AS IS\" BASIS,\n *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  * See the License for the specific language governing permissions and\n *  * limitations under the License.\n *\n */\n\npackage com.pig4cloud.pig.common.mybatis.resolver;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.metadata.OrderItem;\nimport com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.web.bind.support.WebDataBinderFactory;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.ModelAndViewContainer;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\n\n/**\n * Mybatis Plus Order By SQL注入问题解决类\n *\n * @author lengleng\n * @date 2019-06-24\n */\npublic class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver {\n\n\t/**\n\t * 判断Controller方法参数是否为Page类型\n\t * @param parameter 方法参数\n\t * @return 如果参数类型是Page则返回true，否则返回false\n\t */\n\t@Override\n\tpublic boolean supportsParameter(MethodParameter parameter) {\n\t\treturn parameter.getParameterType().equals(Page.class);\n\t}\n\n\t/**\n\t * 解析分页参数并构建Page对象\n\t * @param parameter 方法参数信息\n\t * @param mavContainer 模型和视图容器\n\t * @param webRequest web请求对象\n\t * @param binderFactory 数据绑定工厂\n\t * @return 包含分页和排序信息的Page对象\n\t * @throws NumberFormatException 当分页参数转换失败时抛出\n\t */\n\t@Override\n\tpublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,\n\t\t\tNativeWebRequest webRequest, WebDataBinderFactory binderFactory) {\n\n\t\tHttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);\n\n\t\tString[] ascs = request.getParameterValues(\"ascs\");\n\t\tString[] descs = request.getParameterValues(\"descs\");\n\t\tString current = request.getParameter(\"current\");\n\t\tString size = request.getParameter(\"size\");\n\n\t\tPage<?> page = new Page<>();\n\t\tif (StrUtil.isNotBlank(current)) {\n\t\t\tpage.setCurrent(Convert.toLong(current, 0L));\n\t\t}\n\n\t\tif (StrUtil.isNotBlank(size)) {\n\t\t\tpage.setSize(Convert.toLong(size, 10L));\n\t\t}\n\n\t\tList<OrderItem> orderItemList = new ArrayList<>();\n\t\tOptional.ofNullable(ascs)\n\t\t\t.ifPresent(s -> orderItemList\n\t\t\t\t.addAll(Arrays.stream(s).filter(asc -> !SqlInjectionUtils.check(asc)).map(OrderItem::asc).toList()));\n\t\tOptional.ofNullable(descs)\n\t\t\t.ifPresent(s -> orderItemList\n\t\t\t\t.addAll(Arrays.stream(s).filter(desc -> !SqlInjectionUtils.check(desc)).map(OrderItem::desc).toList()));\n\t\tpage.addOrder(orderItemList);\n\n\t\treturn page;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.mybatis.MybatisAutoConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-oss/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-oss</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 文件系统依赖</description>\n\n    <dependencies>\n        <dependency>\n            <groupId>software.amazon.awssdk</groupId>\n            <artifactId>s3</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-core</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/FileAutoConfiguration.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file;\n\nimport com.pig4cloud.pig.common.file.core.FileProperties;\nimport com.pig4cloud.pig.common.file.local.LocalFileAutoConfiguration;\nimport com.pig4cloud.pig.common.file.oss.OssAutoConfiguration;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Import;\n\n/**\n * AWS 自动配置类\n *\n * @author lengleng\n * @author 858695266\n * @date 2025/05/31\n */\n@Import({ LocalFileAutoConfiguration.class, OssAutoConfiguration.class })\n@EnableConfigurationProperties({ FileProperties.class })\npublic class FileAutoConfiguration {\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/core/FileProperties.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.core;\n\nimport com.pig4cloud.pig.common.file.local.LocalFileProperties;\nimport com.pig4cloud.pig.common.file.oss.OssProperties;\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.NestedConfigurationProperty;\n\n/**\n * 文件 配置信息\n *\n * @author lengleng\n * <p>\n * bucket 设置公共读权限\n */\n@Data\n@ConfigurationProperties(prefix = \"file\")\npublic class FileProperties {\n\n\t/**\n\t * 默认的存储桶名称\n\t */\n\tprivate String bucketName = \"local\";\n\n\t/**\n\t * 本地文件配置信息\n\t */\n\t@NestedConfigurationProperty\n\tprivate LocalFileProperties local;\n\n\t/**\n\t * oss 文件配置信息\n\t */\n\t@NestedConfigurationProperty\n\tprivate OssProperties oss;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/core/FileTemplate.java",
    "content": "package com.pig4cloud.pig.common.file.core;\n\nimport org.springframework.beans.factory.InitializingBean;\n\nimport java.io.InputStream;\nimport java.util.List;\n\n/**\n * 文件操作模板\n *\n * @author lengleng\n * @date 2022/4/19\n */\npublic interface FileTemplate extends InitializingBean {\n\n\t/**\n\t * 创建bucket\n\t * @param bucketName bucket名称\n\t */\n\tvoid createBucket(String bucketName);\n\n\t/**\n\t * 获取全部bucket\n\t * <p>\n\t *\n\t * API Documentation</a>\n\t */\n\tList<? extends Object> getAllBuckets();\n\n\t/**\n\t * @param bucketName bucket名称\n\t * @see <a href= Documentation</a>\n\t */\n\tvoid removeBucket(String bucketName);\n\n\t/**\n\t * 上传文件\n\t * @param bucketName bucket名称\n\t * @param objectName 文件名称\n\t * @param stream 文件流\n\t * @param contextType 文件类型\n\t * @throws Exception\n\t */\n\tvoid putObject(String bucketName, String objectName, InputStream stream, String contextType) throws Exception;\n\n\t/**\n\t * 上传文件\n\t * @param bucketName bucket名称\n\t * @param objectName 文件名称\n\t * @param stream 文件流\n\t * @param contextType 文件类型\n\t * @throws Exception\n\t */\n\tvoid putObject(String bucketName, String objectName, InputStream stream) throws Exception;\n\n\t/**\n\t * 获取文件\n\t * @param bucketName bucket名称\n\t * @param objectName 文件名称\n\t * @return 文件对象 API Documentation</a>\n\t */\n\tObject getObject(String bucketName, String objectName);\n\n\tvoid removeObject(String bucketName, String objectName) throws Exception;\n\n\t/**\n\t * @throws Exception\n\t */\n\t@Override\n\tdefault void afterPropertiesSet() throws Exception {\n\t}\n\n\t/**\n\t * 根据文件前置查询文件\n\t * @param bucketName bucket名称\n\t * @param prefix 前缀\n\t * @param recursive 是否递归查询\n\t * @return 文件对象列表\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects\">AWS\n\t * API Documentation</a>\n\t */\n\tList<? extends Object> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive);\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/local/LocalFileAutoConfiguration.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.local;\n\nimport com.pig4cloud.pig.common.file.core.FileProperties;\nimport com.pig4cloud.pig.common.file.core.FileTemplate;\nimport lombok.AllArgsConstructor;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\n\n/**\n * aws 自动配置类\n *\n * @author lengleng\n * @author 858695266\n */\n@AllArgsConstructor\npublic class LocalFileAutoConfiguration {\n\n\tprivate final FileProperties properties;\n\n\t@Bean\n\t@ConditionalOnMissingBean(LocalFileTemplate.class)\n\t@ConditionalOnProperty(name = \"file.local.enable\", havingValue = \"true\", matchIfMissing = true)\n\tpublic FileTemplate localFileTemplate() {\n\t\treturn new LocalFileTemplate(properties);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/local/LocalFileProperties.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.local;\n\nimport lombok.Data;\n\n/**\n * 本地文件 配置信息\n *\n * @author lengleng\n * <p>\n * bucket 设置公共读权限\n */\n@Data\npublic class LocalFileProperties {\n\n\t/**\n\t * 是否开启\n\t */\n\tprivate boolean enable;\n\n\t/**\n\t * 默认路径\n\t */\n\tprivate String basePath;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/local/LocalFileTemplate.java",
    "content": "package com.pig4cloud.pig.common.file.local;\n\nimport cn.hutool.core.io.FileUtil;\nimport com.pig4cloud.pig.common.file.core.FileProperties;\nimport com.pig4cloud.pig.common.file.core.FileTemplate;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * 本地文件读取模式\n *\n * @author lengleng\n * @date 2022/4/19\n */\n@RequiredArgsConstructor\npublic class LocalFileTemplate implements FileTemplate {\n\n\tprivate final FileProperties properties;\n\n\t/**\n\t * 简单的 Bucket 数据对象\n\t */\n\tpublic record SimpleBucket(String name) {\n\n\t}\n\n\t/**\n\t * 简单的 ObjectSummary 数据对象\n\t */\n\tpublic record SimpleObjectSummary(String key) {\n\n\t}\n\n\t/**\n\t * 创建bucket\n\t * @param bucketName bucket名称\n\t */\n\t@Override\n\tpublic void createBucket(String bucketName) {\n\t\tFileUtil.mkdir(properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName);\n\t}\n\n\t/**\n\t * 获取全部bucket\n\t * <p>\n\t * <p>\n\t * API Documentation</a>\n\t */\n\t@Override\n\tpublic List<SimpleBucket> getAllBuckets() {\n\t\treturn Arrays.stream(FileUtil.ls(properties.getLocal().getBasePath()))\n\t\t\t.filter(FileUtil::isDirectory)\n\t\t\t.map(dir -> new SimpleBucket(dir.getName()))\n\t\t\t.toList();\n\t}\n\n\t/**\n\t * @param bucketName bucket名称\n\t * @see <a href= Documentation</a>\n\t */\n\t@Override\n\tpublic void removeBucket(String bucketName) {\n\t\tFileUtil.del(properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName);\n\t}\n\n\t/**\n\t * 上传文件\n\t * @param bucketName bucket名称\n\t * @param objectName 文件名称\n\t * @param stream 文件流\n\t * @param contextType 文件类型\n\t */\n\t@Override\n\tpublic void putObject(String bucketName, String objectName, InputStream stream, String contextType) {\n\t\t// 当 Bucket 不存在时创建\n\t\tString dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName;\n\t\tif (!FileUtil.isDirectory(properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName)) {\n\t\t\tcreateBucket(bucketName);\n\t\t}\n\n\t\t// 写入文件\n\t\tFile file = FileUtil.file(dir + FileUtil.FILE_SEPARATOR + objectName);\n\t\tFileUtil.writeFromStream(stream, file);\n\t}\n\n\t/**\n\t * 获取文件\n\t * @param bucketName bucket名称\n\t * @param objectName 文件名称\n\t * @return 文件输入流\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic InputStream getObject(String bucketName, String objectName) {\n\t\tString dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName;\n\t\treturn FileUtil.getInputStream(dir + FileUtil.FILE_SEPARATOR + objectName);\n\t}\n\n\t/**\n\t * 删除指定存储桶中的对象\n\t * @param bucketName 存储桶名称\n\t * @param objectName 对象名称\n\t * @throws Exception 删除过程中可能抛出的异常\n\t */\n\t@Override\n\tpublic void removeObject(String bucketName, String objectName) throws Exception {\n\t\tString dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName;\n\t\tFileUtil.del(dir + FileUtil.FILE_SEPARATOR + objectName);\n\t}\n\n\t/**\n\t * 上传文件到指定存储桶\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件名称\n\t * @param stream 文件输入流\n\t * @throws Exception 上传过程中可能发生的异常\n\t */\n\t@Override\n\tpublic void putObject(String bucketName, String objectName, InputStream stream) throws Exception {\n\t\tputObject(bucketName, objectName, stream, null);\n\t}\n\n\t/**\n\t * 根据文件前置查询文件\n\t * @param bucketName bucket名称\n\t * @param prefix 前缀\n\t * @param recursive 是否递归查询\n\t * @return 文件对象摘要列表\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects\">AWS\n\t * API Documentation</a>\n\t */\n\t@Override\n\tpublic List<SimpleObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {\n\t\tString dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName;\n\n\t\treturn Arrays.stream(FileUtil.ls(dir))\n\t\t\t.filter(file -> file.getName().startsWith(prefix))\n\t\t\t.map(file -> new SimpleObjectSummary(file.getName()))\n\t\t\t.toList();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/OssAutoConfiguration.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.oss;\n\nimport com.pig4cloud.pig.common.file.core.FileProperties;\nimport com.pig4cloud.pig.common.file.core.FileTemplate;\nimport com.pig4cloud.pig.common.file.oss.http.OssEndpoint;\nimport com.pig4cloud.pig.common.file.oss.service.OssTemplate;\nimport lombok.AllArgsConstructor;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\n\n/**\n * AWS 对象存储自动配置类\n *\n * @author lengleng\n * @author 858695266\n * @date 2025/05/31\n */\n@AllArgsConstructor\npublic class OssAutoConfiguration {\n\n\tprivate final FileProperties properties;\n\n\t/**\n\t * 创建OssTemplate Bean\n\t * @return 文件模板实例\n\t * @ConditionalOnMissingBean 当容器中不存在OssTemplate Bean时创建\n\t * @ConditionalOnProperty 当配置项file.oss.enable为true时生效\n\t */\n\t@Bean\n\t@Primary\n\t@ConditionalOnMissingBean(OssTemplate.class)\n\t@ConditionalOnProperty(name = \"file.oss.enable\", havingValue = \"true\")\n\tpublic FileTemplate ossTemplate() {\n\t\treturn new OssTemplate(properties);\n\t}\n\n\t/**\n\t * 创建OssEndpoint Bean\n\t * @param template OssTemplate实例\n\t * @return OssEndpoint实例\n\t * @ConditionalOnMissingBean 当容器中不存在该类型Bean时创建\n\t * @ConditionalOnProperty 当配置项file.oss.info为true时生效\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\t@ConditionalOnProperty(name = \"file.oss.info\", havingValue = \"true\")\n\tpublic OssEndpoint ossEndpoint(OssTemplate template) {\n\t\treturn new OssEndpoint(template);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/OssProperties.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.oss;\n\nimport lombok.Data;\n\n/**\n * aws 配置信息\n *\n * @author lengleng\n * @author 858695266 配置文件添加： oss: enable: true endpoint: http://127.0.0.1:9000 #\n * pathStyleAccess 采用nginx反向代理或者AWS S3 配置成true，支持第三方云存储配置成false pathStyleAccess: false\n * access-key: lengleng secret-key: lengleng bucket-name: lengleng region: custom-domain:\n * https://oss.xxx.com/lengleng\n * <p>\n * bucket 设置公共读权限\n */\n@Data\npublic class OssProperties {\n\n\t/**\n\t * 对象存储服务的URL\n\t */\n\tprivate String endpoint;\n\n\t/**\n\t * 自定义域名\n\t */\n\tprivate String customDomain;\n\n\t/**\n\t * true path-style nginx 反向代理和S3默认支持 pathStyle {http://endpoint/bucketname} false\n\t * supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style\n\t * 模式{http://bucketname.endpoint}\n\t */\n\tprivate Boolean pathStyleAccess = true;\n\n\t/**\n\t * 应用ID\n\t */\n\tprivate String appId;\n\n\t/**\n\t * 区域\n\t */\n\tprivate String region;\n\n\t/**\n\t * Access key就像用户ID，可以唯一标识你的账户\n\t */\n\tprivate String accessKey;\n\n\t/**\n\t * Secret key是你账户的密码\n\t */\n\tprivate String secretKey;\n\n\t/**\n\t * 最大线程数，默认： 100\n\t */\n\tprivate Integer maxConnections = 100;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/http/OssEndpoint.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.oss.http;\n\nimport com.pig4cloud.pig.common.file.oss.service.OssTemplate;\nimport lombok.AllArgsConstructor;\nimport lombok.Cleanup;\nimport lombok.SneakyThrows;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\nimport software.amazon.awssdk.services.s3.model.Bucket;\nimport software.amazon.awssdk.services.s3.model.S3Object;\n\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * AWS 对象存储服务端点\n *\n * @author lengleng\n * @author 858695266\n * @date 2025/05/31\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/oss\")\n@ConditionalOnProperty(name = \"file.oss.info\", havingValue = \"true\")\npublic class OssEndpoint {\n\n\tprivate final OssTemplate template;\n\n\t/**\n\t * 创建指定名称的存储桶\n\t * @param bucketName 存储桶名称\n\t * @return 创建的存储桶对象\n\t * @throws Exception 创建过程中可能抛出的异常\n\t */\n\t@SneakyThrows\n\t@PostMapping(\"/bucket/{bucketName}\")\n\tpublic Bucket createBucket(@PathVariable String bucketName) {\n\n\t\ttemplate.createBucket(bucketName);\n\t\treturn template.getBucket(bucketName).get();\n\n\t}\n\n\t/**\n\t * 获取所有存储桶列表\n\t * @return 存储桶列表\n\t * @throws Exception 获取过程中可能抛出的异常\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/bucket\")\n\tpublic List<Bucket> getBuckets() {\n\t\treturn template.getAllBuckets();\n\t}\n\n\t/**\n\t * 根据桶名称获取桶信息\n\t * @param bucketName 桶名称\n\t * @return 对应的桶对象\n\t * @throws IllegalArgumentException 当桶不存在时抛出异常\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/bucket/{bucketName}\")\n\tpublic Bucket getBucket(@PathVariable String bucketName) {\n\t\treturn template.getBucket(bucketName).orElseThrow(() -> new IllegalArgumentException(\"Bucket Name not found!\"));\n\t}\n\n\t/**\n\t * 删除指定名称的存储桶\n\t * @param bucketName 要删除的存储桶名称\n\t * @throws Exception 删除过程中可能抛出的异常\n\t */\n\t@SneakyThrows\n\t@DeleteMapping(\"/bucket/{bucketName}\")\n\t@ResponseStatus(HttpStatus.ACCEPTED)\n\tpublic void deleteBucket(@PathVariable String bucketName) {\n\t\ttemplate.removeBucket(bucketName);\n\t}\n\n\t/**\n\t * 创建对象到指定存储桶\n\t * @param object 要上传的文件对象\n\t * @param bucketName 目标存储桶名称\n\t * @return 上传后的对象信息响应\n\t * @throws IOException 文件操作异常\n\t */\n\t@SneakyThrows\n\t@PostMapping(\"/object/{bucketName}\")\n\tpublic Map<String, Object> createObject(@RequestBody MultipartFile object, @PathVariable String bucketName) {\n\t\tString name = object.getOriginalFilename();\n\t\t@Cleanup\n\t\tInputStream inputStream = object.getInputStream();\n\t\ttemplate.putObject(bucketName, name, inputStream, object.getSize(), object.getContentType());\n\n\t\tMap<String, Object> result = new HashMap<>();\n\t\tresult.put(\"bucket\", bucketName);\n\t\tresult.put(\"object\", name);\n\t\tresult.put(\"size\", object.getSize());\n\t\tresult.put(\"contentType\", object.getContentType());\n\t\treturn result;\n\t}\n\n\t/**\n\t * 创建对象到指定存储桶\n\t * @param object 上传的文件对象\n\t * @param bucketName 存储桶名称\n\t * @param objectName 对象名称\n\t * @return 创建成功的对象信息\n\t * @throws Exception 当文件上传或获取对象信息失败时抛出异常\n\t */\n\t@SneakyThrows\n\t@PostMapping(\"/object/{bucketName}/{objectName}\")\n\tpublic Map<String, Object> createObject(@RequestBody MultipartFile object, @PathVariable String bucketName,\n\t\t\t@PathVariable String objectName) {\n\t\t@Cleanup\n\t\tInputStream inputStream = object.getInputStream();\n\t\ttemplate.putObject(bucketName, objectName, inputStream, object.getSize(), object.getContentType());\n\n\t\tMap<String, Object> result = new HashMap<>();\n\t\tresult.put(\"bucket\", bucketName);\n\t\tresult.put(\"object\", objectName);\n\t\tresult.put(\"size\", object.getSize());\n\t\tresult.put(\"contentType\", object.getContentType());\n\t\treturn result;\n\t}\n\n\t/**\n\t * 根据对象名前缀过滤对象列表\n\t * @param bucketName 存储桶名称\n\t * @param objectName 对象名前缀\n\t * @return 匹配前缀的S3对象列表\n\t * @throws Exception 操作执行过程中可能抛出的异常\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/object/{bucketName}/{objectName}\")\n\tpublic List<S3Object> filterObject(@PathVariable String bucketName, @PathVariable String objectName) {\n\n\t\treturn template.getAllObjectsByPrefix(bucketName, objectName, true);\n\n\t}\n\n\t/**\n\t * 获取对象信息及访问URL\n\t * @param bucketName 存储桶名称\n\t * @param objectName 对象名称\n\t * @param expires URL过期时间(秒)\n\t * @return 包含存储桶、对象、URL和过期时间的Map\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/object/{bucketName}/{objectName}/{expires}\")\n\tpublic Map<String, Object> getObject(@PathVariable String bucketName, @PathVariable String objectName,\n\t\t\t@PathVariable Integer expires) {\n\t\tMap<String, Object> responseBody = new HashMap<>(8);\n\t\t// Put Object info\n\t\tresponseBody.put(\"bucket\", bucketName);\n\t\tresponseBody.put(\"object\", objectName);\n\t\tresponseBody.put(\"url\", template.getObjectURL(bucketName, objectName, expires));\n\t\tresponseBody.put(\"expires\", expires);\n\t\treturn responseBody;\n\t}\n\n\t/**\n\t * 删除指定存储桶中的对象\n\t * @param bucketName 存储桶名称\n\t * @param objectName 对象名称\n\t * @throws Exception 删除对象时可能抛出的异常\n\t */\n\t@SneakyThrows\n\t@ResponseStatus(HttpStatus.ACCEPTED)\n\t@DeleteMapping(\"/object/{bucketName}/{objectName}/\")\n\tpublic void deleteObject(@PathVariable String bucketName, @PathVariable String objectName) {\n\n\t\ttemplate.removeObject(bucketName, objectName);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/service/OssTemplate.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.common.file.oss.service;\n\nimport com.pig4cloud.pig.common.file.core.FileProperties;\nimport com.pig4cloud.pig.common.file.core.FileTemplate;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springframework.beans.factory.InitializingBean;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.core.sync.RequestBody;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.s3.S3Client;\nimport software.amazon.awssdk.services.s3.S3Configuration;\nimport software.amazon.awssdk.services.s3.model.*;\nimport software.amazon.awssdk.services.s3.presigner.S3Presigner;\nimport software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;\n\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Optional;\n\n/**\n * AWS S3通用存储操作模板类\n *\n * <p>\n * 支持所有兼容S3协议的云存储服务，包括AWS S3、MinIO、阿里云OSS、腾讯云COS等\n * </p>\n * <p>\n * 提供存储桶管理、文件对象管理、预签名URL生成等功能\n * </p>\n *\n * @author lengleng\n * @author 858695266\n * @date 2025/05/31\n * @since 1.0\n */\n@RequiredArgsConstructor\npublic class OssTemplate implements InitializingBean, FileTemplate {\n\n\t/**\n\t * 文件存储配置属性\n\t */\n\tprivate final FileProperties properties;\n\n\t/**\n\t * S3客户端实例，用于执行S3 API操作\n\t */\n\tprivate S3Client s3Client;\n\n\t/**\n\t * S3预签名器，用于生成预签名URL\n\t */\n\tprivate S3Presigner s3Presigner;\n\n\t/**\n\t * 创建存储桶\n\t * @param bucketName 存储桶名称，必须全局唯一且符合DNS命名规范\n\t * @throws Exception 创建失败时抛出异常\n\t */\n\t@SneakyThrows\n\tpublic void createBucket(String bucketName) {\n\t\t// 检查存储桶是否已存在，避免重复创建\n\t\tif (!doesBucketExist(bucketName)) {\n\t\t\tCreateBucketRequest request = CreateBucketRequest.builder().bucket(bucketName).build();\n\t\t\ts3Client.createBucket(request);\n\t\t}\n\t}\n\n\t/**\n\t * 检查存储桶是否存在\n\t * @param bucketName 存储桶名称\n\t * @return 存在返回true，否则返回false\n\t */\n\tprivate boolean doesBucketExist(String bucketName) {\n\t\ttry {\n\t\t\tHeadBucketRequest request = HeadBucketRequest.builder().bucket(bucketName).build();\n\t\t\ts3Client.headBucket(request);\n\t\t\treturn true;\n\t\t}\n\t\tcatch (NoSuchBucketException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * 获取所有存储桶列表\n\t * @return 存储桶列表\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets\">AWS\n\t * API Documentation</a>\n\t */\n\t@SneakyThrows\n\tpublic List<Bucket> getAllBuckets() {\n\t\tListBucketsResponse response = s3Client.listBuckets();\n\t\treturn response.buckets();\n\t}\n\n\t/**\n\t * 根据名称查找特定存储桶\n\t * @param bucketName 存储桶名称\n\t * @return Optional包装的Bucket对象\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets\">AWS\n\t * API Documentation</a>\n\t */\n\t@SneakyThrows\n\tpublic Optional<Bucket> getBucket(String bucketName) {\n\t\treturn getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();\n\t}\n\n\t/**\n\t * 删除存储桶\n\t *\n\t * <p>\n\t * 注意：存储桶必须为空才能删除，删除操作不可逆\n\t * </p>\n\t * @param bucketName 存储桶名称\n\t * @throws Exception 删除失败时抛出异常\n\t * @see <a href=\n\t * \"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteBucket\">AWS API\n\t * Documentation</a>\n\t */\n\t@SneakyThrows\n\tpublic void removeBucket(String bucketName) {\n\t\tDeleteBucketRequest request = DeleteBucketRequest.builder().bucket(bucketName).build();\n\t\ts3Client.deleteBucket(request);\n\t}\n\n\t/**\n\t * 根据前缀查询文件对象\n\t * @param bucketName 存储桶名称\n\t * @param prefix 文件名前缀，可为null或空字符串\n\t * @param recursive 是否递归查询子目录\n\t * @return S3Object列表\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects\">AWS\n\t * API Documentation</a>\n\t */\n\t@SneakyThrows\n\tpublic List<S3Object> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {\n\t\tListObjectsV2Request.Builder requestBuilder = ListObjectsV2Request.builder().bucket(bucketName);\n\n\t\t// 设置前缀过滤条件\n\t\tif (prefix != null && !prefix.isEmpty()) {\n\t\t\trequestBuilder.prefix(prefix);\n\t\t}\n\n\t\t// 非递归查询时设置分隔符\n\t\tif (!recursive) {\n\t\t\trequestBuilder.delimiter(\"/\");\n\t\t}\n\n\t\tListObjectsV2Response response = s3Client.listObjectsV2(requestBuilder.build());\n\t\treturn response.contents();\n\t}\n\n\t/**\n\t * 生成文件的预签名访问URL\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @param expires 过期时间（天数）\n\t * @return 预签名的访问URL\n\t * @throws Exception 生成失败时抛出异常\n\t */\n\t@SneakyThrows\n\tpublic String getObjectURL(String bucketName, String objectName, Integer expires) {\n\t\tGetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(objectName).build();\n\n\t\tGetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()\n\t\t\t.signatureDuration(Duration.ofDays(expires))\n\t\t\t.getObjectRequest(getObjectRequest)\n\t\t\t.build();\n\n\t\treturn s3Presigner.presignGetObject(presignRequest).url().toString();\n\t}\n\n\t/**\n\t * 获取文件对象\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @return S3响应对象，包含文件流和元数据\n\t * @throws Exception 获取失败时抛出异常\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject\">AWS\n\t * API Documentation</a>\n\t */\n\t@SneakyThrows\n\tpublic Object getObject(String bucketName, String objectName) {\n\t\tGetObjectRequest request = GetObjectRequest.builder().bucket(bucketName).key(objectName).build();\n\t\treturn s3Client.getObject(request);\n\t}\n\n\t/**\n\t * 上传文件（使用默认内容类型）\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @param stream 文件输入流\n\t * @throws Exception 上传失败时抛出异常\n\t */\n\tpublic void putObject(String bucketName, String objectName, InputStream stream) throws Exception {\n\t\tputObject(bucketName, objectName, stream, \"application/octet-stream\");\n\t}\n\n\t/**\n\t * 上传文件（指定内容类型）\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @param stream 文件输入流\n\t * @param contextType 文件MIME类型\n\t * @throws Exception 上传失败时抛出异常\n\t */\n\tpublic void putObject(String bucketName, String objectName, InputStream stream, String contextType)\n\t\t\tthrows Exception {\n\t\tPutObjectRequest request = PutObjectRequest.builder()\n\t\t\t.bucket(bucketName)\n\t\t\t.key(objectName)\n\t\t\t.contentType(contextType)\n\t\t\t.build();\n\n\t\ts3Client.putObject(request, RequestBody.fromInputStream(stream, stream.available()));\n\t}\n\n\t/**\n\t * 上传文件（指定文件大小）\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @param stream 文件输入流\n\t * @param size 文件大小（字节数）\n\t * @param contextType 文件MIME类型\n\t * @return PutObjectResponse 上传响应对象\n\t * @throws Exception 上传失败时抛出异常\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject\">AWS\n\t * API Documentation</a>\n\t */\n\tpublic PutObjectResponse putObject(String bucketName, String objectName, InputStream stream, long size,\n\t\t\tString contextType) throws Exception {\n\t\tPutObjectRequest request = PutObjectRequest.builder()\n\t\t\t.bucket(bucketName)\n\t\t\t.key(objectName)\n\t\t\t.contentType(contextType)\n\t\t\t.contentLength(size)\n\t\t\t.build();\n\n\t\treturn s3Client.putObject(request, RequestBody.fromInputStream(stream, size));\n\t}\n\n\t/**\n\t * 获取文件元数据信息\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @return HeadObjectResponse 文件元数据响应对象\n\t * @throws Exception 获取失败时抛出异常\n\t * @see <a href=\"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject\">AWS\n\t * API Documentation</a>\n\t */\n\tpublic HeadObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {\n\t\tHeadObjectRequest request = HeadObjectRequest.builder().bucket(bucketName).key(objectName).build();\n\t\treturn s3Client.headObject(request);\n\t}\n\n\t/**\n\t * 删除文件对象\n\t *\n\t * <p>\n\t * 注意：删除操作不可逆，删除不存在的文件不会报错\n\t * </p>\n\t * @param bucketName 存储桶名称\n\t * @param objectName 文件对象名称\n\t * @throws Exception 删除失败时抛出异常\n\t * @see <a href=\n\t * \"http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject\">AWS API\n\t * Documentation</a>\n\t */\n\tpublic void removeObject(String bucketName, String objectName) throws Exception {\n\t\tDeleteObjectRequest request = DeleteObjectRequest.builder().bucket(bucketName).key(objectName).build();\n\t\ts3Client.deleteObject(request);\n\t}\n\n\t/**\n\t * 初始化S3客户端和预签名器实例\n\t *\n\t * <p>\n\t * 在Spring Bean属性设置完成后自动调用，配置端点地址、区域、访问凭证等\n\t * </p>\n\t * @throws Exception 初始化失败时抛出异常\n\t */\n\t@Override\n\tpublic void afterPropertiesSet() {\n\t\t// 创建认证凭据\n\t\tAwsBasicCredentials awsCredentials = AwsBasicCredentials.create(properties.getOss().getAccessKey(),\n\t\t\t\tproperties.getOss().getSecretKey());\n\n\t\t// 构建S3配置\n\t\tS3Configuration.Builder s3ConfigBuilder = S3Configuration.builder()\n\t\t\t.pathStyleAccessEnabled(properties.getOss().getPathStyleAccess());\n\n\t\t// 创建S3客户端\n\t\tthis.s3Client = S3Client.builder()\n\t\t\t.endpointOverride(URI.create(properties.getOss().getEndpoint()))\n\t\t\t.region(Region.of(properties.getOss().getRegion() != null ? properties.getOss().getRegion() : \"us-east-1\"))\n\t\t\t.credentialsProvider(StaticCredentialsProvider.create(awsCredentials))\n\t\t\t.serviceConfiguration(s3ConfigBuilder.build())\n\t\t\t.build();\n\n\t\t// 创建S3预签名器\n\t\tthis.s3Presigner = S3Presigner.builder()\n\t\t\t.endpointOverride(URI.create(properties.getOss().getEndpoint()))\n\t\t\t.region(Region.of(properties.getOss().getRegion() != null ? properties.getOss().getRegion() : \"us-east-1\"))\n\t\t\t.credentialsProvider(StaticCredentialsProvider.create(awsCredentials))\n\t\t\t.serviceConfiguration(s3ConfigBuilder.build())\n\t\t\t.build();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-oss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.file.FileAutoConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-seata/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ /*\n  ~  *  Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).\n  ~  *  <p>\n  ~  *  Licensed under the GNU Lesser General Public License 3.0 (the \"License\");\n  ~  *  you may not use this file except in compliance with the License.\n  ~  *  You may obtain a copy of the License at\n  ~  *  <p>\n  ~  * https://www.gnu.org/licenses/lgpl.html\n  ~  *  <p>\n  ~  * Unless required by applicable law or agreed to in writing, software\n  ~  * distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~  * See the License for the specific language governing permissions and\n  ~  * limitations under the License.\n  ~  */\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-seata</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 分布式事务处理模块</description>\n\n    <dependencies>\n        <!--核心依赖-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <!-- seata-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-seata/src/main/java/com/pig4cloud/pig/common/seata/config/SeataAutoConfiguration.java",
    "content": "package com.pig4cloud.pig.common.seata.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.PropertySource;\n\nimport com.pig4cloud.pig.common.core.factory.YamlPropertySourceFactory;\n\n/**\n * Seata 自动配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@PropertySource(value = \"classpath:seata-config.yml\", factory = YamlPropertySourceFactory.class)\n@Configuration(proxyBeanMethods = false)\npublic class SeataAutoConfiguration {\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": " com.pig4cloud.pig.common.seata.config.SeataAutoConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-seata/src/main/resources/seata-config.yml",
    "content": "seata:\n  enabled: true\n  tx-service-group: pig_tx_group # 事务群组（可以每个应用独立取名，也可以使用相同的名字）\n  client:\n    rm-report-success-enable: true\n    rm-table-meta-check-enable: false # 自动刷新缓存中的表结构（默认false）\n    rm-report-retry-count: 5 # 一阶段结果上报TC重试次数（默认5）\n    rm-async-commit-buffer-limit: 10000 # 异步提交缓存队列长度（默认10000）\n    rm:\n      lock:\n        lock-retry-internal: 10 # 校验或占用全局锁重试间隔（默认10ms）\n        lock-retry-times: 30 # 校验或占用全局锁重试次数（默认30）\n        lock-retry-policy-branch-rollback-on-conflict: true # 分支事务与其它全局回滚事务冲突时锁策略（优先释放本地锁让回滚成功）\n    tm-commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数（默认1次，建议大于1）\n    tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数（默认1次，建议大于1）\n    undo:\n      data-validation: true # 二阶段回滚镜像校验（默认true开启）\n      log-serialization: jackson # undo序列化方式（默认jackson 不支持 LocalDateTime）\n      log-table: undo_log  # 自定义undo表名（默认undo_log）\n    log:\n      exceptionRate: 100 # 日志异常输出概率（默认100）\n    support:\n      spring:\n        datasource-autoproxy: true\n  service:\n    vgroup-mapping:\n      pig_tx_group: default # TC 集群（必须与seata-server保持一致）\n    enable-degrade: false # 降级开关\n    disable-global-transaction: false # 禁用全局事务（默认false）\n    grouplist:\n      default: pig-seata:8091\n  transport:\n    shutdown:\n      wait: 3\n    thread-factory:\n      boss-thread-prefix: NettyBoss\n      worker-thread-prefix: NettyServerNIOWorker\n      server-executor-thread-prefix: NettyServerBizHandler\n      share-boss-worker: false\n      client-selector-thread-prefix: NettyClientSelector\n      client-selector-thread-size: 1\n      client-worker-thread-prefix: NettyClientWorkerThread\n    type: TCP\n    server: NIO\n    heartbeat: true\n    serialization: seata\n    compressor: none\n    enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送（默认true）\n  registry:\n    file:\n      name: file.conf\n    type: file\n  config:\n    file:\n      name: file.conf\n    type: file\n"
  },
  {
    "path": "pig-common/pig-common-security/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-security</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 安全工具类</description>\n\n\n    <dependencies>\n        <!--工具类核心包-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-extra</artifactId>\n        </dependency>\n        <!--UPMS API-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-upms-api</artifactId>\n        </dependency>\n        <!--common utils-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-commons</artifactId>\n        </dependency>\n        <!--feign 工具类-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-openfeign</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-oauth2-jose</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-oauth2-authorization-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/EnablePigResourceServer.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.annotation;\n\nimport com.pig4cloud.pig.common.security.component.PigResourceServerAutoConfiguration;\nimport com.pig4cloud.pig.common.security.component.PigResourceServerConfiguration;\nimport com.pig4cloud.pig.common.security.feign.PigFeignClientConfiguration;\nimport org.springframework.context.annotation.Import;\n\nimport java.lang.annotation.*;\n\n/**\n * 启用Pig资源服务器注解\n * <p>\n * 通过导入相关配置类启用Pig资源服务器功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Documented\n@Inherited\n@Target({ ElementType.TYPE })\n@Retention(RetentionPolicy.RUNTIME)\n@Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class,\n\t\tPigFeignClientConfiguration.class })\npublic @interface EnablePigResourceServer {\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/HasPermission.java",
    "content": "package com.pig4cloud.pig.common.security.annotation;\n\nimport org.springframework.security.access.prepost.PreAuthorize;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 权限注解：用于方法级别的权限控制\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Target({ ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@PreAuthorize(\"@pms.hasPermission('{value}'.split(','))\")\npublic @interface HasPermission {\n\n\t/**\n\t * 权限字符串\n\t * @return {@link String[] }\n\t */\n\tString[] value();\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/Inner.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 内部注解：用于标记方法或类型是否需要AOP统一处理\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Target({ ElementType.METHOD, ElementType.TYPE })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Inner {\n\n\t/**\n\t * 是否AOP统一处理\n\t * @return false, true\n\t */\n\tboolean value() default true;\n\n\t/**\n\t * 需要特殊判空的字段(预留)\n\t * @return {}\n\t */\n\tString[] field() default {};\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PermissionService.java",
    "content": "\n/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.util.PatternMatchUtils;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Collection;\n\n/**\n * 接口权限判断工具类\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PermissionService {\n\n\t/**\n\t * 判断接口是否有任意xxx，xxx权限\n\t * @param permissions 权限\n\t * @return {boolean}\n\t */\n\tpublic boolean hasPermission(String... permissions) {\n\t\tif (ArrayUtil.isEmpty(permissions)) {\n\t\t\treturn false;\n\t\t}\n\t\tAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();\n\t\tif (authentication == null) {\n\t\t\treturn false;\n\t\t}\n\t\tCollection<? extends GrantedAuthority> authorities = authentication.getAuthorities();\n\t\treturn authorities.stream()\n\t\t\t.map(GrantedAuthority::getAuthority)\n\t\t\t.filter(StringUtils::hasText)\n\t\t\t.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PermitAllUrlProperties.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\n\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.mvc.method.RequestMappingInfo;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\n\nimport com.pig4cloud.pig.common.security.annotation.Inner;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport lombok.Getter;\nimport lombok.Setter;\n\n/**\n * 资源服务器对外直接暴露URL配置类\n * <p>\n * 用于配置不需要认证即可访问的URL路径，支持路径变量替换\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@ConfigurationProperties(prefix = \"security.oauth2.ignore\")\npublic class PermitAllUrlProperties implements InitializingBean {\n\n\tprivate static final Pattern PATTERN = Pattern.compile(\"\\\\{(.*?)\\\\}\");\n\n\tprivate static final String[] DEFAULT_IGNORE_URLS = new String[] { \"/actuator/**\", \"/error\", \"/v3/api-docs\" };\n\n\t@Getter\n\t@Setter\n\tprivate List<String> urls = new ArrayList<>();\n\n\t/**\n\t * 初始化方法，在属性设置完成后执行 收集带有@Inner注解的Controller方法路径，并将路径中的变量替换为*\n\t */\n\t@Override\n\tpublic void afterPropertiesSet() {\n\t\turls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS));\n\t\tRequestMappingHandlerMapping mapping = SpringUtil.getBean(\"requestMappingHandlerMapping\");\n\t\tMap<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();\n\n\t\tmap.keySet().forEach(info -> {\n\t\t\tHandlerMethod handlerMethod = map.get(info);\n\n\t\t\t// 获取方法上边的注解 替代path variable 为 *\n\t\t\tInner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);\n\t\t\tOptional.ofNullable(method)\n\t\t\t\t.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())\n\t\t\t\t\t.getPatternValues()\n\t\t\t\t\t.forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, \"*\"))));\n\n\t\t\t// 获取类上边的注解, 替代path variable 为 *\n\t\t\tInner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);\n\t\t\tOptional.ofNullable(controller)\n\t\t\t\t.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())\n\t\t\t\t\t.getPatternValues()\n\t\t\t\t\t.forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, \"*\"))));\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigBearerTokenExtractor.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.server.resource.BearerTokenError;\nimport org.springframework.security.oauth2.server.resource.BearerTokenErrors;\nimport org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;\nimport org.springframework.util.AntPathMatcher;\nimport org.springframework.util.PathMatcher;\nimport org.springframework.util.StringUtils;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author caiqy\n * @date 2020.05.15\n */\npublic class PigBearerTokenExtractor implements BearerTokenResolver {\n\n\tprivate static final Pattern authorizationPattern = Pattern.compile(\"^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$\",\n\t\t\tPattern.CASE_INSENSITIVE);\n\n\tprivate boolean allowFormEncodedBodyParameter = false;\n\n\tprivate boolean allowUriQueryParameter = true;\n\n\tprivate String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;\n\n\tprivate final PathMatcher pathMatcher = new AntPathMatcher();\n\n\tprivate final PermitAllUrlProperties urlProperties;\n\n\tpublic PigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {\n\t\tthis.urlProperties = urlProperties;\n\t}\n\n\t@Override\n\tpublic String resolve(HttpServletRequest request) {\n\t\tboolean match = urlProperties.getUrls()\n\t\t\t.stream()\n\t\t\t.anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));\n\n\t\tif (match) {\n\t\t\treturn null;\n\t\t}\n\n\t\tfinal String authorizationHeaderToken = resolveFromAuthorizationHeader(request);\n\t\tfinal String parameterToken = isParameterTokenSupportedForRequest(request)\n\t\t\t\t? resolveFromRequestParameters(request) : null;\n\t\tif (authorizationHeaderToken != null) {\n\t\t\tif (parameterToken != null) {\n\t\t\t\tfinal BearerTokenError error = BearerTokenErrors\n\t\t\t\t\t.invalidRequest(\"Found multiple bearer tokens in the request\");\n\t\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t\t}\n\t\t\treturn authorizationHeaderToken;\n\t\t}\n\t\tif (parameterToken != null && isParameterTokenEnabledForRequest(request)) {\n\t\t\treturn parameterToken;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate String resolveFromAuthorizationHeader(HttpServletRequest request) {\n\t\tString authorization = request.getHeader(this.bearerTokenHeaderName);\n\t\tif (!StringUtils.startsWithIgnoreCase(authorization, \"bearer\")) {\n\t\t\treturn null;\n\t\t}\n\t\tMatcher matcher = authorizationPattern.matcher(authorization);\n\t\tif (!matcher.matches()) {\n\t\t\tBearerTokenError error = BearerTokenErrors.invalidToken(\"Bearer token is malformed\");\n\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t}\n\t\treturn matcher.group(\"token\");\n\t}\n\n\tprivate static String resolveFromRequestParameters(HttpServletRequest request) {\n\t\tString[] values = request.getParameterValues(\"access_token\");\n\t\tif (values == null || values.length == 0) {\n\t\t\treturn null;\n\t\t}\n\t\tif (values.length == 1) {\n\t\t\treturn values[0];\n\t\t}\n\t\tBearerTokenError error = BearerTokenErrors.invalidRequest(\"Found multiple bearer tokens in the request\");\n\t\tthrow new OAuth2AuthenticationException(error);\n\t}\n\n\tprivate boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {\n\t\treturn ((\"POST\".equals(request.getMethod())\n\t\t\t\t&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))\n\t\t\t\t|| \"GET\".equals(request.getMethod()));\n\t}\n\n\tprivate boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) {\n\t\treturn ((this.allowFormEncodedBodyParameter && \"POST\".equals(request.getMethod())\n\t\t\t\t&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))\n\t\t\t\t|| (this.allowUriQueryParameter && \"GET\".equals(request.getMethod())));\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigBootCorsProperties.java",
    "content": "package com.pig4cloud.pig.common.security.component;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author lengleng\n * @date 2025-11-12\n * <p>\n * CORS 跨域资源共享配置属性\n */\n@Data\n@ConfigurationProperties(prefix = \"security.cors\")\npublic class PigBootCorsProperties {\n\n\t/**\n\t * 是否启用CORS跨域支持\n\t */\n\tprivate Boolean enabled = false;\n\n\t/**\n\t * 允许的源模式列表，支持通配符 例如：http://localhost:*, https://*.example.com\n\t */\n\tprivate List<String> allowedOriginPatterns = new ArrayList<>(List.of(\"*\"));\n\n\t/**\n\t * 允许的请求头列表 默认允许所有请求头\n\t */\n\tprivate List<String> allowedHeaders = new ArrayList<>(List.of(\"*\"));\n\n\t/**\n\t * 允许的HTTP方法列表 默认允许常用的HTTP方法\n\t */\n\tprivate List<String> allowedMethods = new ArrayList<>(List.of(\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"));\n\n\t/**\n\t * 是否允许携带凭证（如Cookie） 当设置为true时，allowedOriginPatterns不能使用通配符*\n\t */\n\tprivate Boolean allowCredentials = true;\n\n\t/**\n\t * 应用CORS配置的路径模式 默认应用到所有路径\n\t */\n\tprivate String pathPattern = \"/**\";\n\n}"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigClientCredentialsOAuth2AuthenticatedPrincipal.java",
    "content": "package com.pig4cloud.pig.common.security.component;\n\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;\n\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * 客户端模式凭证认证主体实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class PigClientCredentialsOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal {\n\n\tprivate final Map<String, Object> attributes;\n\n\tprivate final Collection<GrantedAuthority> authorities;\n\n\tprivate final String name;\n\n\t/**\n\t * 获取属性集合\n\t * @return 属性键值对集合\n\t */\n\t@Override\n\tpublic Map<String, Object> getAttributes() {\n\t\treturn this.attributes;\n\t}\n\n\t/**\n\t * 获取用户权限集合\n\t * @return 用户权限集合\n\t */\n\t@Override\n\tpublic Collection<? extends GrantedAuthority> getAuthorities() {\n\t\treturn this.authorities;\n\t}\n\n\t/**\n\t * 获取名称\n\t * @return 当前对象的名称\n\t */\n\t@Override\n\tpublic String getName() {\n\t\treturn this.name;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigCustomOAuth2AccessTokenResponseHttpMessageConverter.java",
    "content": "package com.pig4cloud.pig.common.security.component;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.core.convert.converter.Converter;\nimport org.springframework.http.HttpOutputMessage;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.converter.GenericHttpMessageConverter;\nimport org.springframework.http.converter.HttpMessageNotWritableException;\nimport org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;\nimport org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;\nimport org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;\n\nimport java.util.Map;\n\n/**\n * 扩展OAuth2AccessTokenResponseHttpMessageConverter，支持Long类型转String的Token响应转换\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PigCustomOAuth2AccessTokenResponseHttpMessageConverter\n\t\textends OAuth2AccessTokenResponseHttpMessageConverter {\n\n\t/**\n\t * 字符串到对象的映射类型引用\n\t */\n\tprivate static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {\n\t};\n\n\t/**\n\t * OAuth2访问令牌响应参数转换器，用于将OAuth2AccessTokenResponse转换为Map<String, Object>\n\t */\n\tprivate Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();\n\n\t/**\n\t * 将OAuth2访问令牌响应写入HTTP输出消息\n\t * @param tokenResponse OAuth2访问令牌响应\n\t * @param outputMessage HTTP输出消息\n\t * @throws HttpMessageNotWritableException 写入响应时发生错误抛出异常\n\t */\n\t@Override\n\tprotected void writeInternal(OAuth2AccessTokenResponse tokenResponse, HttpOutputMessage outputMessage)\n\t\t\tthrows HttpMessageNotWritableException {\n\t\ttry {\n\t\t\tMap<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter\n\t\t\t\t.convert(tokenResponse);\n\n\t\t\tObjectMapper objectMapper = SpringContextHolder.getBean(ObjectMapper.class);\n\t\t\tGenericHttpMessageConverter<Object> jsonMessageConverter = new MappingJackson2HttpMessageConverter(\n\t\t\t\t\tobjectMapper);\n\t\t\tjsonMessageConverter.write(tokenResponseParameters, STRING_OBJECT_MAP.getType(), MediaType.APPLICATION_JSON,\n\t\t\t\t\toutputMessage);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthrow new HttpMessageNotWritableException(\n\t\t\t\t\t\"An error occurred writing the OAuth 2.0 Access Token Response: \" + ex.getMessage(), ex);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigCustomOpaqueTokenIntrospector.java",
    "content": "package com.pig4cloud.pig.common.security.component;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.security.service.PigUser;\nimport com.pig4cloud.pig.common.security.service.PigUserDetailsService;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.core.Ordered;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.authority.AuthorityUtils;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;\nimport org.springframework.security.oauth2.server.authorization.OAuth2Authorization;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.OAuth2TokenType;\nimport org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;\nimport org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;\n\nimport java.security.Principal;\nimport java.util.Comparator;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * 自定义不透明令牌内省器，用于处理OAuth2不透明令牌的验证和用户信息获取\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {\n\n\t/**\n\t * OAuth2授权服务\n\t */\n\tprivate final OAuth2AuthorizationService authorizationService;\n\n\t/**\n\t * 根据token内省获取认证主体信息\n\t * @param token 访问令牌\n\t * @return OAuth2认证主体信息\n\t * @throws InvalidBearerTokenException 当token对应的授权信息不存在时抛出\n\t * @throws UsernameNotFoundException 当用户不存在时抛出\n\t */\n\t@Override\n\tpublic OAuth2AuthenticatedPrincipal introspect(String token) {\n\t\tOAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);\n\t\tif (Objects.isNull(oldAuthorization)) {\n\t\t\tthrow new InvalidBearerTokenException(token);\n\t\t}\n\n\t\t// 客户端模式默认返回\n\t\tif (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) {\n\t\t\treturn new DefaultOAuth2AuthenticatedPrincipal(oldAuthorization.getPrincipalName(),\n\t\t\t\t\tObjects.requireNonNull(oldAuthorization.getAccessToken().getClaims()),\n\t\t\t\t\tAuthorityUtils.NO_AUTHORITIES);\n\t\t}\n\n\t\tMap<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil\n\t\t\t.getBeansOfType(PigUserDetailsService.class);\n\n\t\tOptional<PigUserDetailsService> optional = userDetailsServiceMap.values()\n\t\t\t.stream()\n\t\t\t.filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),\n\t\t\t\t\toldAuthorization.getAuthorizationGrantType().getValue()))\n\t\t\t.max(Comparator.comparingInt(Ordered::getOrder));\n\n\t\tUserDetails userDetails = null;\n\t\ttry {\n\t\t\tObject principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName());\n\t\t\tUsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;\n\t\t\tObject tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();\n\t\t\tuserDetails = optional.get().loadUserByUser((PigUser) tokenPrincipal);\n\t\t}\n\t\tcatch (UsernameNotFoundException notFoundException) {\n\t\t\tlog.warn(\"用户不不存在 {}\", notFoundException.getLocalizedMessage());\n\t\t\tthrow notFoundException;\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlog.error(\"资源服务器 introspect Token error {}\", ex.getLocalizedMessage());\n\t\t}\n\n\t\t// 注入客户端信息，方便上下文中获取\n\t\tPigUser pigUser = (PigUser) userDetails;\n\t\tObjects.requireNonNull(pigUser)\n\t\t\t.getAttributes()\n\t\t\t.put(SecurityConstants.CLIENT_ID, oldAuthorization.getRegisteredClientId());\n\t\treturn pigUser;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigResourceServerAutoConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 资源服务器自动配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\n@EnableConfigurationProperties(PermitAllUrlProperties.class)\npublic class PigResourceServerAutoConfiguration {\n\n\t/**\n\t * 鉴权具体的实现逻辑\n\t * @return （#pms.xxx）\n\t */\n\t@Bean(\"pms\")\n\tpublic PermissionService permissionService() {\n\t\treturn new PermissionService();\n\t}\n\n\t/**\n\t * 请求令牌的抽取逻辑\n\t * @param urlProperties 对外暴露的接口列表\n\t * @return BearerTokenExtractor\n\t */\n\t@Bean\n\tpublic PigBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {\n\t\treturn new PigBearerTokenExtractor(urlProperties);\n\t}\n\n\t/**\n\t * 资源服务器异常处理\n\t * @param objectMapper jackson 输出对象\n\t * @param securityMessageSource 自定义国际化处理器\n\t * @return ResourceAuthExceptionEntryPoint\n\t */\n\t@Bean\n\tpublic ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper,\n\t\t\tMessageSource securityMessageSource) {\n\t\treturn new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource);\n\t}\n\n\t/**\n\t * 资源服务器toke内省处理器\n\t * @param authorizationService token 存储实现\n\t * @return TokenIntrospector\n\t */\n\t@Bean\n\tpublic OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {\n\t\treturn new PigCustomOpaqueTokenIntrospector(authorizationService);\n\t}\n\n\t/**\n\t * 支持自定义权限表达式\n\t * @return {@link PrePostTemplateDefaults }\n\t */\n\t@Bean\n\tAnnotationTemplateExpressionDefaults prePostTemplateDefaults() {\n\t\treturn new AnnotationTemplateExpressionDefaults();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigResourceServerConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;\nimport org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;\nimport org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\nimport org.springframework.web.cors.CorsConfiguration;\nimport org.springframework.web.cors.UrlBasedCorsConfigurationSource;\n\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 资源服务器认证授权配置\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@EnableWebSecurity\n@EnableMethodSecurity\n@RequiredArgsConstructor\npublic class PigResourceServerConfiguration {\n\n\t/**\n\t * 资源认证异常处理入口点\n\t */\n\tprotected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;\n\n\t/**\n\t * 允许所有URL的配置属性\n\t */\n\tprivate final PermitAllUrlProperties permitAllUrl;\n\n\t/**\n\t * PigBearerToken提取器\n\t */\n\tprivate final PigBearerTokenExtractor pigBearerTokenExtractor;\n\n\t/**\n\t * 自定义不透明令牌解析器\n\t */\n\tprivate final OpaqueTokenIntrospector customOpaqueTokenIntrospector;\n\n\t/**\n\t * CORS跨域资源共享配置属性\n\t */\n\tprivate final PigBootCorsProperties pigBootCorsProperties;\n\n\t/**\n\t * 资源服务器安全配置\n\t * @param http http\n\t * @return {@link SecurityFilterChain }\n\t * @throws Exception 异常\n\t */\n\t@Bean\n\tSecurityFilterChain resourceServer(HttpSecurity http) throws Exception {\n\t\t/**\n\t\t * AntPathRequestMatcher[] permitMatchers = permitAllUrl.getUrls() .stream()\n\t\t * .map(AntPathRequestMatcher::new) .toList() .toArray(new AntPathRequestMatcher[]\n\t\t * {});\n\t\t **/\n\t\tPathPatternRequestMatcher[] permitMatchers = permitAllUrl.getUrls()\n\t\t\t.stream()\n\t\t\t.map(url -> PathPatternRequestMatcher.withDefaults().matcher(url))\n\t\t\t.toList()\n\t\t\t.toArray(new PathPatternRequestMatcher[] {});\n\n\t\thttp.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(permitMatchers)\n\t\t\t.permitAll()\n\t\t\t.anyRequest()\n\t\t\t.authenticated())\n\t\t\t.oauth2ResourceServer(\n\t\t\t\t\toauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector))\n\t\t\t\t\t\t.authenticationEntryPoint(resourceAuthExceptionEntryPoint)\n\t\t\t\t\t\t.bearerTokenResolver(pigBearerTokenExtractor))\n\t\t\t.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))\n\t\t\t.csrf(AbstractHttpConfigurer::disable);\n\n\t\t// 配置 CORS 跨域资源共享\n\t\tif (Boolean.TRUE.equals(pigBootCorsProperties.getEnabled())) {\n\t\t\thttp.cors(cors -> cors.configurationSource(corsConfigurationSource()));\n\t\t}\n\n\t\treturn http.build();\n\t}\n\n\t/**\n\t * 配置 CORS 跨域资源共享\n\t * @return UrlBasedCorsConfigurationSource CORS配置源\n\t */\n\tprivate UrlBasedCorsConfigurationSource corsConfigurationSource() {\n\t\tUrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n\t\tCorsConfiguration corsConfiguration = new CorsConfiguration();\n\n\t\t// 从配置文件读取允许的源模式\n\t\tpigBootCorsProperties.getAllowedOriginPatterns().forEach(corsConfiguration::addAllowedOriginPattern);\n\t\t// 从配置文件读取允许的请求头\n\t\tpigBootCorsProperties.getAllowedHeaders().forEach(corsConfiguration::addAllowedHeader);\n\t\t// 从配置文件读取允许的HTTP方法\n\t\tpigBootCorsProperties.getAllowedMethods().forEach(corsConfiguration::addAllowedMethod);\n\t\t// 从配置文件读取是否允许携带凭证\n\t\tcorsConfiguration.setAllowCredentials(pigBootCorsProperties.getAllowCredentials());\n\n\t\t// 注册CORS配置到指定路径\n\t\tsource.registerCorsConfiguration(pigBootCorsProperties.getPathPattern(), corsConfiguration);\n\n\t\treturn source;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigSecurityInnerAspect.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.security.access.AccessDeniedException;\n\n/**\n * 服务间接口不鉴权处理切面\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Aspect\n@RequiredArgsConstructor\npublic class PigSecurityInnerAspect implements Ordered {\n\n\tprivate final HttpServletRequest request;\n\n\t/**\n\t * 环绕通知，用于检查内部调用权限\n\t * @param point 切点对象\n\t * @param inner 内部调用注解\n\t * @throws AccessDeniedException 当无权限访问时抛出异常\n\t */\n\t@SneakyThrows\n\t@Before(\"@within(inner) || @annotation(inner)\")\n\tpublic void around(JoinPoint point, Inner inner) {\n\t\t// 实际注入的inner实体由表达式后一个注解决定，即是方法上的@Inner注解实体，若方法上无@Inner注解，则获取类上的\n\t\tif (inner == null) {\n\t\t\tClass<?> clazz = point.getTarget().getClass();\n\t\t\tinner = AnnotationUtils.findAnnotation(clazz, Inner.class);\n\t\t}\n\t\tString header = request.getHeader(SecurityConstants.FROM);\n\t\tif (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) {\n\t\t\tlog.warn(\"访问接口 {} 没有权限\", point.getSignature().getName());\n\t\t\tthrow new AccessDeniedException(\"Access is denied\");\n\t\t}\n\t}\n\n\t@Override\n\tpublic int getOrder() {\n\t\treturn Ordered.HIGHEST_PRECEDENCE + 1;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigSecurityMessageSourceConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.support.ReloadableResourceBundleMessageSource;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport java.util.Locale;\n\nimport static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;\n\n/**\n * 创建并配置安全相关的消息源\n *\n * @return 配置好的ReloadableResourceBundleMessageSource实例\n */\n@ConditionalOnWebApplication(type = SERVLET)\npublic class PigSecurityMessageSourceConfiguration implements WebMvcConfigurer {\n\n\t/**\n\t * 创建并配置安全相关的消息源\n\t * @return 配置好的ReloadableResourceBundleMessageSource实例\n\t */\n\t@Bean\n\tpublic MessageSource securityMessageSource() {\n\t\tReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();\n\t\tmessageSource.addBasenames(\"classpath:i18n/errors/messages\");\n\t\tmessageSource.setDefaultLocale(Locale.CHINA);\n\t\treturn messageSource;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/ResourceAuthExceptionEntryPoint.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.component;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.i18n.LocaleContextHolder;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.security.authentication.InsufficientAuthenticationException;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;\nimport org.springframework.security.web.AuthenticationEntryPoint;\n\nimport java.io.PrintWriter;\n\n/**\n * 资源认证异常处理入口点，用于处理客户端认证异常\n *\n * @author lengleng\n * @date 2019/2/1\n */\n@RequiredArgsConstructor\npublic class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {\n\n\tprivate final ObjectMapper objectMapper;\n\n\tprivate final MessageSource messageSource;\n\n\t/**\n\t * 处理认证失败的响应\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param authException 认证异常\n\t * @throws Exception 写入响应时可能抛出异常\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic void commence(HttpServletRequest request, HttpServletResponse response,\n\t\t\tAuthenticationException authException) {\n\t\tresponse.setCharacterEncoding(CommonConstants.UTF8);\n\t\tresponse.setContentType(CommonConstants.CONTENT_TYPE);\n\t\tR<String> result = new R<>();\n\t\tresult.setCode(CommonConstants.FAIL);\n\t\tresponse.setStatus(HttpStatus.UNAUTHORIZED.value());\n\t\tif (authException != null) {\n\t\t\tresult.setMsg(\"error\");\n\t\t\tresult.setData(authException.getMessage());\n\t\t}\n\n\t\t// 针对令牌过期返回特殊的 424\n\t\tif (authException instanceof InvalidBearerTokenException\n\t\t\t\t|| authException instanceof InsufficientAuthenticationException) {\n\t\t\tresponse.setStatus(org.springframework.http.HttpStatus.FAILED_DEPENDENCY.value());\n\t\t\tresult.setMsg(this.messageSource.getMessage(\"OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired\",\n\t\t\t\t\tnull, LocaleContextHolder.getLocale()));\n\t\t}\n\t\tPrintWriter printWriter = response.getWriter();\n\t\tprintWriter.append(objectMapper.writeValueAsString(result));\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/feign/PigFeignClientConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.feign;\n\nimport feign.RequestInterceptor;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;\n\n/**\n * Pig Feign 客户端配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PigFeignClientConfiguration {\n\n\t/**\n\t * 注入 oauth2 feign token 增强\n\t * @param tokenResolver token获取处理器\n\t * @return 拦截器\n\t */\n\t@Bean\n\tpublic RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) {\n\t\treturn new PigOAuthRequestInterceptor(tokenResolver);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/feign/PigOAuthRequestInterceptor.java",
    "content": "package com.pig4cloud.pig.common.security.feign;\n\nimport java.util.Collection;\n\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.security.oauth2.core.OAuth2AccessToken;\nimport org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;\n\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.WebUtils;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport feign.RequestInterceptor;\nimport feign.RequestTemplate;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * oauth2 feign token传递\n *\n * 重新 OAuth2FeignRequestInterceptor ，官方实现部分常见不适用\n *\n * @author lengleng\n * @date 2022/5/29\n */\n@RequiredArgsConstructor\npublic class PigOAuthRequestInterceptor implements RequestInterceptor {\n\n\t/**\n\t * 用于解析Bearer令牌的解析器\n\t */\n\tprivate final BearerTokenResolver tokenResolver;\n\n\t/**\n\t * Create a template with the header of provided name and extracted extract </br>\n\t *\n\t * 1. 如果使用 非web 请求，header 区别 </br>\n\t *\n\t * 2. 根据authentication 还原请求token\n\t * @param template\n\t */\n\t@Override\n\tpublic void apply(RequestTemplate template) {\n\t\tCollection<String> fromHeader = template.headers().get(SecurityConstants.FROM);\n\t\t// 带from 请求直接跳过\n\t\tif (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// 非web 请求直接跳过\n\t\tif (!WebUtils.getRequest().isPresent()) {\n\t\t\treturn;\n\t\t}\n\t\tHttpServletRequest request = WebUtils.getRequest().get();\n\t\t// 避免请求参数的 query token 无法传递\n\t\tString token = tokenResolver.resolve(request);\n\t\tif (StrUtil.isBlank(token)) {\n\t\t\treturn;\n\t\t}\n\t\ttemplate.header(HttpHeaders.AUTHORIZATION,\n\t\t\t\tString.format(\"%s %s\", OAuth2AccessToken.TokenType.BEARER.getValue(), token));\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigAppUserDetailsServiceImpl.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.service;\n\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.security.core.userdetails.UserDetails;\n\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.admin.api.feign.RemoteUserService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.R;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\n/**\n * 用户详细信息服务实现类，提供基于手机号的用户信息加载功能\n *\n * @author lengleng hccake\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class PigAppUserDetailsServiceImpl implements PigUserDetailsService {\n\n\tprivate final RemoteUserService remoteUserService;\n\n\tprivate final CacheManager cacheManager;\n\n\t/**\n\t * 根据手机号加载用户信息\n\t * @param phone 用户手机号\n\t * @return 用户详细信息\n\t * @throws Exception 获取用户信息过程中可能抛出的异常\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic UserDetails loadUserByUsername(String phone) {\n\t\tCache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);\n\t\tif (cache != null && cache.get(phone) != null) {\n\t\t\treturn (PigUser) cache.get(phone).get();\n\t\t}\n\n\t\tUserDTO userDTO = new UserDTO();\n\t\tuserDTO.setPhone(phone);\n\t\tR<UserInfo> result = remoteUserService.info(userDTO);\n\n\t\tUserDetails userDetails = getUserDetails(result);\n\t\tif (cache != null) {\n\t\t\tcache.put(phone, userDetails);\n\t\t}\n\t\treturn userDetails;\n\t}\n\n\t/**\n\t * 根据用户信息加载用户详情\n\t * @param pigUser 用户信息对象\n\t * @return 用户详情\n\t */\n\t@Override\n\tpublic UserDetails loadUserByUser(PigUser pigUser) {\n\t\treturn this.loadUserByUsername(pigUser.getPhone());\n\t}\n\n\t/**\n\t * 是否支持此客户端校验\n\t * @param clientId 目标客户端\n\t * @return true/false\n\t */\n\t@Override\n\tpublic boolean support(String clientId, String grantType) {\n\t\treturn SecurityConstants.MOBILE.equals(grantType);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigRedisOAuth2AuthorizationConsentService.java",
    "content": "package com.pig4cloud.pig.common.security.service;\n\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;\nimport org.springframework.util.Assert;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 基于Redis实现的OAuth2授权同意服务\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class PigRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {\n\n\tprivate final static Long TIMEOUT = 10L;\n\n\t/**\n\t * 保存OAuth2授权同意信息\n\t * @param authorizationConsent 授权同意信息，不能为null\n\t * @throws IllegalArgumentException 当authorizationConsent为null时抛出\n\t */\n\t@Override\n\tpublic void save(OAuth2AuthorizationConsent authorizationConsent) {\n\t\tAssert.notNull(authorizationConsent, \"authorizationConsent cannot be null\");\n\n\t\tRedisUtils.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT, TimeUnit.MINUTES);\n\t}\n\n\t/**\n\t * 移除OAuth2授权同意信息\n\t * @param authorizationConsent 授权同意信息，不能为null\n\t * @throws IllegalArgumentException 当authorizationConsent为null时抛出\n\t */\n\t@Override\n\tpublic void remove(OAuth2AuthorizationConsent authorizationConsent) {\n\t\tAssert.notNull(authorizationConsent, \"authorizationConsent cannot be null\");\n\t\tRedisUtils.delete(buildKey(authorizationConsent));\n\t}\n\n\t/**\n\t * 根据注册客户端ID和主体名称查找OAuth2授权同意信息\n\t * @param registeredClientId 注册客户端ID，不能为空\n\t * @param principalName 主体名称，不能为空\n\t * @return 查找到的OAuth2授权同意信息，可能为null\n\t */\n\t@Override\n\tpublic OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {\n\t\tAssert.hasText(registeredClientId, \"registeredClientId cannot be empty\");\n\t\tAssert.hasText(principalName, \"principalName cannot be empty\");\n\t\treturn RedisUtils.get(buildKey(registeredClientId, principalName));\n\t}\n\n\t/**\n\t * 构建授权确认信息的key\n\t * @param registeredClientId 注册客户端ID\n\t * @param principalName 主体名称\n\t * @return 拼接后的key字符串\n\t */\n\tprivate static String buildKey(String registeredClientId, String principalName) {\n\t\treturn \"token:consent:\" + registeredClientId + \":\" + principalName;\n\t}\n\n\t/**\n\t * 构建授权同意的键值\n\t * @param authorizationConsent 授权同意对象\n\t * @return 构建的键值字符串\n\t */\n\tprivate static String buildKey(OAuth2AuthorizationConsent authorizationConsent) {\n\t\treturn buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigRedisOAuth2AuthorizationService.java",
    "content": "package com.pig4cloud.pig.common.security.service;\n\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.lang.Nullable;\nimport org.springframework.security.oauth2.core.OAuth2AccessToken;\nimport org.springframework.security.oauth2.core.OAuth2RefreshToken;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.oauth2.server.authorization.OAuth2Authorization;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;\nimport org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;\nimport org.springframework.security.oauth2.server.authorization.OAuth2TokenType;\nimport org.springframework.util.Assert;\n\nimport java.time.temporal.ChronoUnit;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 基于Redis实现的OAuth2授权服务类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class PigRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {\n\n\tprivate final static Long TIMEOUT = 10L;\n\n\tprivate static final String AUTHORIZATION = \"token\";\n\n\t/**\n\t * 保存OAuth2授权信息到Redis\n\t * @param authorization 授权信息对象，不能为null\n\t * @throws IllegalArgumentException 当authorization为null时抛出异常\n\t */\n\t@Override\n\tpublic void save(OAuth2Authorization authorization) {\n\t\tAssert.notNull(authorization, \"authorization cannot be null\");\n\n\t\tif (isState(authorization)) {\n\t\t\tString token = authorization.getAttribute(\"state\");\n\t\t\tRedisUtils.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES);\n\t\t}\n\n\t\tif (isCode(authorization)) {\n\t\t\tOAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization\n\t\t\t\t.getToken(OAuth2AuthorizationCode.class);\n\t\t\tOAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();\n\t\t\tlong between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),\n\t\t\t\t\tauthorizationCodeToken.getExpiresAt());\n\t\t\tRedisUtils.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization,\n\t\t\t\t\tbetween, TimeUnit.MINUTES);\n\t\t}\n\n\t\tif (isRefreshToken(authorization)) {\n\t\t\tOAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();\n\t\t\tlong between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());\n\t\t\tRedisUtils.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization,\n\t\t\t\t\tbetween, TimeUnit.SECONDS);\n\t\t}\n\n\t\tif (isAccessToken(authorization)) {\n\t\t\tOAuth2AccessToken accessToken = authorization.getAccessToken().getToken();\n\t\t\tlong between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());\n\t\t\tRedisUtils.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization,\n\t\t\t\t\tbetween, TimeUnit.SECONDS);\n\t\t}\n\t}\n\n\t/**\n\t * 移除OAuth2授权信息\n\t * @param authorization 要移除的授权信息，不能为null\n\t * @throws IllegalArgumentException 当authorization为null时抛出\n\t */\n\t@Override\n\tpublic void remove(OAuth2Authorization authorization) {\n\t\tAssert.notNull(authorization, \"authorization cannot be null\");\n\n\t\tList<String> keys = new ArrayList<>();\n\t\tif (isState(authorization)) {\n\t\t\tString token = authorization.getAttribute(\"state\");\n\t\t\tkeys.add(buildKey(OAuth2ParameterNames.STATE, token));\n\t\t}\n\n\t\tif (isCode(authorization)) {\n\t\t\tOAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization\n\t\t\t\t.getToken(OAuth2AuthorizationCode.class);\n\t\t\tOAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();\n\t\t\tkeys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));\n\t\t}\n\n\t\tif (isRefreshToken(authorization)) {\n\t\t\tOAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();\n\t\t\tkeys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()));\n\t\t}\n\n\t\tif (isAccessToken(authorization)) {\n\t\t\tOAuth2AccessToken accessToken = authorization.getAccessToken().getToken();\n\t\t\tkeys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));\n\t\t}\n\t\tRedisUtils.delete(keys.toArray(String[]::new));\n\t}\n\n\t/**\n\t * 根据ID查询OAuth2授权信息\n\t * @param id 授权ID\n\t * @return 授权信息，可能为null\n\t * @throws UnsupportedOperationException 当前不支持此操作\n\t */\n\t@Override\n\t@Nullable\n\tpublic OAuth2Authorization findById(String id) {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t/**\n\t * 根据token和token类型查询OAuth2授权信息\n\t * @param token token值\n\t * @param tokenType token类型，不能为null\n\t * @return 授权信息对象，可能为null\n\t * @throws IllegalArgumentException 当token为空或tokenType为null时抛出\n\t */\n\t@Override\n\t@Nullable\n\tpublic OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {\n\t\tAssert.hasText(token, \"token cannot be empty\");\n\t\tAssert.notNull(tokenType, \"tokenType cannot be empty\");\n\t\treturn RedisUtils.get(buildKey(tokenType.getValue(), token));\n\t}\n\n\t/**\n\t * 构建key\n\t * @param type 类型\n\t * @param id ID\n\t * @return 拼接后的key字符串\n\t */\n\tprivate String buildKey(String type, String id) {\n\t\treturn String.format(\"%s::%s::%s\", AUTHORIZATION, type, id);\n\t}\n\n\t/**\n\t * 检查授权对象是否包含state属性\n\t * @param authorization 授权对象\n\t * @return 如果包含state属性返回true，否则返回false\n\t */\n\tprivate static boolean isState(OAuth2Authorization authorization) {\n\t\treturn Objects.nonNull(authorization.getAttribute(\"state\"));\n\t}\n\n\t/**\n\t * 检查授权对象是否包含授权码\n\t * @param authorization 授权对象\n\t * @return 如果包含授权码返回true，否则返回false\n\t */\n\tprivate static boolean isCode(OAuth2Authorization authorization) {\n\t\tOAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization\n\t\t\t.getToken(OAuth2AuthorizationCode.class);\n\t\treturn Objects.nonNull(authorizationCode);\n\t}\n\n\t/**\n\t * 判断授权是否包含刷新令牌\n\t * @param authorization 授权信息\n\t * @return 如果包含刷新令牌返回true，否则返回false\n\t */\n\tprivate static boolean isRefreshToken(OAuth2Authorization authorization) {\n\t\treturn Objects.nonNull(authorization.getRefreshToken());\n\t}\n\n\t/**\n\t * 判断授权对象是否包含访问令牌\n\t * @param authorization 授权对象\n\t * @return 如果包含访问令牌返回true，否则返回false\n\t */\n\tprivate static boolean isAccessToken(OAuth2Authorization authorization) {\n\t\treturn Objects.nonNull(authorization.getAccessToken());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigRemoteRegisteredClientRepository.java",
    "content": "package com.pig4cloud.pig.common.security.service;\n\nimport cn.hutool.core.util.BooleanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.RetOps;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.security.oauth2.core.AuthorizationGrantType;\nimport org.springframework.security.oauth2.core.ClientAuthenticationMethod;\nimport org.springframework.security.oauth2.core.OAuth2Error;\nimport org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;\nimport org.springframework.security.oauth2.server.authorization.client.RegisteredClient;\nimport org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;\nimport org.springframework.security.oauth2.server.authorization.settings.ClientSettings;\nimport org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;\nimport org.springframework.security.oauth2.server.authorization.settings.TokenSettings;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Optional;\n\n/**\n * 查询客户端相关信息实现类，支持Redis缓存\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\npublic class PigRemoteRegisteredClientRepository implements RegisteredClientRepository {\n\n\t/**\n\t * 刷新令牌有效期默认 30 天\n\t */\n\tprivate final static int refreshTokenValiditySeconds = 60 * 60 * 24 * 30;\n\n\t/**\n\t * 请求令牌有效期默认 12 小时\n\t */\n\tprivate final static int accessTokenValiditySeconds = 60 * 60 * 12;\n\n\t/**\n\t * 远程客户端详情服务\n\t */\n\tprivate final RemoteClientDetailsService clientDetailsService;\n\n\t/**\n\t * 保存注册的客户端\n\t *\n\t * <p>\n\t * 重要提示：敏感信息应在实现外部进行编码，例如 {@link RegisteredClient#getClientSecret()}\n\t * </p>\n\t * @param registeredClient 要保存的注册客户端\n\t */\n\t@Override\n\tpublic void save(RegisteredClient registeredClient) {\n\t}\n\n\t/**\n\t * 根据ID查找已注册的客户端\n\t * @param id 注册标识符\n\t * @return 找到的{@link RegisteredClient}，未找到则返回{@code null}\n\t */\n\t@Override\n\tpublic RegisteredClient findById(String id) {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t/**\n\t * 根据客户端ID查询注册客户端信息，支持Redis缓存\n\t * @param clientId 客户端ID\n\t * @return 注册客户端信息\n\t * @throws OAuth2AuthorizationCodeRequestAuthenticationException 客户端查询异常时抛出\n\t */\n\t@Override\n\t@SneakyThrows\n\t@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = \"#clientId\", unless = \"#result == null\")\n\tpublic RegisteredClient findByClientId(String clientId) {\n\n\t\tSysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId))\n\t\t\t.getData()\n\t\t\t.orElseThrow(() -> new OAuth2AuthorizationCodeRequestAuthenticationException(\n\t\t\t\t\tnew OAuth2Error(\"客户端查询异常，请检查数据库链接\"), null));\n\n\t\tRegisteredClient.Builder builder = RegisteredClient.withId(clientDetails.getClientId())\n\t\t\t.clientId(clientDetails.getClientId())\n\t\t\t.clientSecret(SecurityConstants.NOOP + clientDetails.getClientSecret())\n\t\t\t.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);\n\n\t\tfor (String authorizedGrantType : clientDetails.getAuthorizedGrantTypes()) {\n\t\t\tbuilder.authorizationGrantType(new AuthorizationGrantType(authorizedGrantType));\n\n\t\t}\n\t\t// 回调地址\n\t\tOptional.ofNullable(clientDetails.getWebServerRedirectUri())\n\t\t\t.ifPresent(redirectUri -> Arrays.stream(redirectUri.split(StrUtil.COMMA))\n\t\t\t\t.filter(StrUtil::isNotBlank)\n\t\t\t\t.forEach(builder::redirectUri));\n\n\t\t// scope\n\t\tOptional.ofNullable(clientDetails.getScope())\n\t\t\t.ifPresent(scope -> Arrays.stream(scope.split(StrUtil.COMMA))\n\t\t\t\t.filter(StrUtil::isNotBlank)\n\t\t\t\t.forEach(builder::scope));\n\n\t\treturn builder\n\t\t\t.tokenSettings(TokenSettings.builder()\n\t\t\t\t.accessTokenFormat(OAuth2TokenFormat.REFERENCE)\n\t\t\t\t.accessTokenTimeToLive(Duration.ofSeconds(\n\t\t\t\t\t\tOptional.ofNullable(clientDetails.getAccessTokenValidity()).orElse(accessTokenValiditySeconds)))\n\t\t\t\t.refreshTokenTimeToLive(Duration.ofSeconds(Optional.ofNullable(clientDetails.getRefreshTokenValidity())\n\t\t\t\t\t.orElse(refreshTokenValiditySeconds)))\n\t\t\t\t.build())\n\t\t\t.clientSettings(ClientSettings.builder()\n\t\t\t\t.requireAuthorizationConsent(!BooleanUtil.toBoolean(clientDetails.getAutoapprove()))\n\t\t\t\t.build())\n\t\t\t.build();\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUser.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.service;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport lombok.Getter;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.core.SpringSecurityCoreVersion;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;\n\nimport java.io.Serial;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 扩展用户信息类，继承自User并实现OAuth2AuthenticatedPrincipal接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class PigUser extends User implements OAuth2AuthenticatedPrincipal {\n\n\t@Serial\n\tprivate static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;\n\n\t/**\n\t * 扩展属性，方便存放oauth 上下文相关信息\n\t */\n\tprivate final Map<String, Object> attributes = new HashMap<>();\n\n\t/**\n\t * 用户ID\n\t */\n\t@Getter\n\t@JsonSerialize(using = ToStringSerializer.class)\n\tprivate final Long id;\n\n\t/**\n\t * 部门ID\n\t */\n\t@Getter\n\t@JsonSerialize(using = ToStringSerializer.class)\n\tprivate final Long deptId;\n\n\t/**\n\t * 手机号\n\t */\n\t@Getter\n\tprivate final String phone;\n\n\tpublic PigUser(Long id, Long deptId, String username, String password, String phone, boolean enabled,\n\t\t\tboolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked,\n\t\t\tCollection<? extends GrantedAuthority> authorities) {\n\t\tsuper(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);\n\t\tthis.id = id;\n\t\tthis.deptId = deptId;\n\t\tthis.phone = phone;\n\t}\n\n\t/**\n\t * 获取OAuth 2.0令牌属性\n\t * @return OAuth 2.0令牌属性Map\n\t */\n\t@Override\n\tpublic Map<String, Object> getAttributes() {\n\t\treturn this.attributes;\n\t}\n\n\t/**\n\t * 获取用户名称\n\t * @return 用户名称\n\t */\n\t@Override\n\tpublic String getName() {\n\t\treturn this.getUsername();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsService.java",
    "content": "package com.pig4cloud.pig.common.security.service;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.core.util.RetOps;\nimport org.springframework.core.Ordered;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.core.authority.AuthorityUtils;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\n\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * 用户详情服务接口，扩展了Spring Security的UserDetailsService和Ordered接口 提供用户详情加载、客户端支持校验及排序功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface PigUserDetailsService extends UserDetailsService, Ordered {\n\n\t/**\n\t * 是否支持此客户端校验\n\t * @param clientId 目标客户端\n\t * @return true/false\n\t */\n\tdefault boolean support(String clientId, String grantType) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * 排序值 默认取最大的\n\t * @return 排序值\n\t */\n\tdefault int getOrder() {\n\t\treturn 0;\n\t}\n\n\t/**\n\t * 根据用户信息构建UserDetails对象\n\t * @param result 包含用户信息的R对象\n\t * @return 构建好的UserDetails对象\n\t * @throws UsernameNotFoundException 当用户信息不存在时抛出异常\n\t */\n\tdefault UserDetails getUserDetails(R<UserInfo> result) {\n\t\tUserInfo info = RetOps.of(result).getData().orElseThrow(() -> new UsernameNotFoundException(\"用户不存在\"));\n\t\tSet<String> dbAuthsSet = new HashSet<>();\n\n\t\t// 维护角色列表\n\t\tinfo.getRoleList().forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role.getRoleId()));\n\n\t\t// 维护权限列表\n\t\tdbAuthsSet.addAll(info.getPermissions());\n\t\tCollection<GrantedAuthority> authorities = AuthorityUtils\n\t\t\t.createAuthorityList(dbAuthsSet.toArray(new String[0]));\n\n\t\t// 构造security用户\n\t\treturn new PigUser(info.getUserId(), info.getDept().getDeptId(), info.getUsername(),\n\t\t\t\tSecurityConstants.BCRYPT + info.getPassword(), info.getPhone(), true, true, true,\n\t\t\t\tStrUtil.equals(info.getLockFlag(), CommonConstants.STATUS_NORMAL), authorities);\n\t}\n\n\t/**\n\t * 通过用户实体查询用户详情\n\t * @param pigUser 用户实体对象\n\t * @return 用户详情信息\n\t */\n\tdefault UserDetails loadUserByUser(PigUser pigUser) {\n\t\treturn this.loadUserByUsername(pigUser.getUsername());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsServiceImpl.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.service;\n\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.security.core.userdetails.UserDetails;\n\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.admin.api.feign.RemoteUserService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.util.R;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\n/**\n * 用户详情服务实现类，提供基于用户名加载用户详情功能\n *\n * @author lengleng\n * @author hccake\n * @date 2025/05/31\n */\n@Primary\n@RequiredArgsConstructor\npublic class PigUserDetailsServiceImpl implements PigUserDetailsService {\n\n\tprivate final RemoteUserService remoteUserService;\n\n\tprivate final CacheManager cacheManager;\n\n\t/**\n\t * 根据用户名加载用户详情\n\t * @param username 用户名\n\t * @return 用户详情信息\n\t * @throws Exception 获取用户信息过程中可能抛出的异常\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic UserDetails loadUserByUsername(String username) {\n\t\tCache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);\n\t\tif (cache != null && cache.get(username) != null) {\n\t\t\treturn (PigUser) cache.get(username).get();\n\t\t}\n\n\t\tUserDTO userDTO = new UserDTO();\n\t\tuserDTO.setUsername(username);\n\t\tR<UserInfo> result = remoteUserService.info(userDTO);\n\t\tUserDetails userDetails = getUserDetails(result);\n\t\tif (cache != null) {\n\t\t\tcache.put(username, userDetails);\n\t\t}\n\t\treturn userDetails;\n\t}\n\n\t@Override\n\tpublic int getOrder() {\n\t\treturn Integer.MIN_VALUE;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/OAuth2EndpointUtils.java",
    "content": "package com.pig4cloud.pig.common.security.util;\n\nimport cn.hutool.core.map.MapUtil;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.experimental.UtilityClass;\nimport org.springframework.security.oauth2.core.*;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;\nimport org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;\nimport org.springframework.security.oauth2.core.endpoint.PkceParameterNames;\nimport org.springframework.security.oauth2.server.authorization.OAuth2Authorization;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\n\nimport java.time.temporal.ChronoUnit;\nimport java.util.Map;\n\n/**\n * OAuth2 端点工具类\n *\n * <p>\n * 提供OAuth2相关端点操作的实用方法\n * </p>\n *\n * @author lengleng\n * @author jumuning\n * @date 2025/05/31\n */\n@UtilityClass\npublic class OAuth2EndpointUtils {\n\n\tpublic final String ACCESS_TOKEN_REQUEST_ERROR_URI = \"https://datatracker.ietf.org/doc/html/rfc6749#section-5.2\";\n\n\t/**\n\t * 从HttpServletRequest中获取参数并转换为MultiValueMap\n\t * @param request HttpServletRequest对象\n\t * @return 包含所有参数的MultiValueMap，key为参数名，value为参数值列表\n\t */\n\tpublic MultiValueMap<String, String> getParameters(HttpServletRequest request) {\n\t\tMap<String, String[]> parameterMap = request.getParameterMap();\n\t\tMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());\n\t\tparameterMap.forEach((key, values) -> {\n\t\t\tfor (String value : values) {\n\t\t\t\tparameters.add(key, value);\n\t\t\t}\n\t\t});\n\t\treturn parameters;\n\t}\n\n\t/**\n\t * 检查请求是否符合PKCE令牌请求规范\n\t * @param request HTTP请求对象\n\t * @return 如果请求是授权码模式且包含code和code_verifier参数则返回true，否则返回false\n\t */\n\tpublic boolean matchesPkceTokenRequest(HttpServletRequest request) {\n\t\treturn AuthorizationGrantType.AUTHORIZATION_CODE.getValue()\n\t\t\t.equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE))\n\t\t\t\t&& request.getParameter(OAuth2ParameterNames.CODE) != null\n\t\t\t\t&& request.getParameter(PkceParameterNames.CODE_VERIFIER) != null;\n\t}\n\n\t/**\n\t * 抛出OAuth2认证异常\n\t * @param errorCode 错误码\n\t * @param parameterName 参数名称\n\t * @param errorUri 错误URI\n\t * @throws OAuth2AuthenticationException OAuth2认证异常\n\t */\n\tpublic void throwError(String errorCode, String parameterName, String errorUri) {\n\t\tOAuth2Error error = new OAuth2Error(errorCode, \"OAuth 2.0 Parameter: \" + parameterName, errorUri);\n\t\tthrow new OAuth2AuthenticationException(error);\n\t}\n\n\t/**\n\t * 发送OAuth2访问令牌响应\n\t * @param authentication 用户认证信息\n\t * @param claims 扩展信息\n\t * @return OAuth2访问令牌响应\n\t */\n\tpublic OAuth2AccessTokenResponse sendAccessTokenResponse(OAuth2Authorization authentication,\n\t\t\tMap<String, Object> claims) {\n\n\t\tOAuth2AccessToken accessToken = authentication.getAccessToken().getToken();\n\t\tOAuth2RefreshToken refreshToken = authentication.getRefreshToken().getToken();\n\n\t\tOAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())\n\t\t\t.tokenType(accessToken.getTokenType())\n\t\t\t.scopes(accessToken.getScopes());\n\t\tif (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {\n\t\t\tbuilder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));\n\t\t}\n\t\tif (refreshToken != null) {\n\t\t\tbuilder.refreshToken(refreshToken.getTokenValue());\n\t\t}\n\n\t\tif (MapUtil.isNotEmpty(claims)) {\n\t\t\tbuilder.additionalParameters(claims);\n\t\t}\n\t\treturn builder.build();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/OAuth2ErrorCodesExpand.java",
    "content": "package com.pig4cloud.pig.common.security.util;\n\n/**\n * OAuth2 异常信息常量接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface OAuth2ErrorCodesExpand {\n\n\t/** 用户名未找到 */\n\tString USERNAME_NOT_FOUND = \"username_not_found\";\n\n\t/** 错误凭证 */\n\tString BAD_CREDENTIALS = \"bad_credentials\";\n\n\t/** 用户被锁 */\n\tString USER_LOCKED = \"user_locked\";\n\n\t/** 用户禁用 */\n\tString USER_DISABLE = \"user_disable\";\n\n\t/** 用户过期 */\n\tString USER_EXPIRED = \"user_expired\";\n\n\t/** 证书过期 */\n\tString CREDENTIALS_EXPIRED = \"credentials_expired\";\n\n\t/** scope 为空异常 */\n\tString SCOPE_IS_EMPTY = \"scope_is_empty\";\n\n\t/**\n\t * 令牌不存在\n\t */\n\tString TOKEN_MISSING = \"token_missing\";\n\n\t/** 未知的登录异常 */\n\tString UN_KNOW_LOGIN_ERROR = \"un_know_login_error\";\n\n\t/**\n\t * 不合法的Token\n\t */\n\tString INVALID_BEARER_TOKEN = \"invalid_bearer_token\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/OAuthClientException.java",
    "content": "package com.pig4cloud.pig.common.security.util;\n\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.core.OAuth2Error;\n\nimport java.io.Serial;\n\n/**\n * OAuth客户端异常类\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class OAuthClientException extends OAuth2AuthenticationException {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 使用指定消息构造OAuthClientException\n\t * @param msg 详细消息\n\t */\n\tpublic OAuthClientException(String msg) {\n\t\tsuper(new OAuth2Error(msg), msg);\n\t}\n\n\t/**\n\t * 构造一个带有指定消息和根原因的OAuthClientException\n\t * @param msg 详细消息\n\t * @param cause 根原因\n\t */\n\tpublic OAuthClientException(String msg, Throwable cause) {\n\t\tsuper(new OAuth2Error(msg), cause);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/ScopeException.java",
    "content": "package com.pig4cloud.pig.common.security.util;\n\nimport java.io.Serial;\n\nimport org.springframework.security.oauth2.core.OAuth2AuthenticationException;\nimport org.springframework.security.oauth2.core.OAuth2Error;\n\n/**\n * ScopeException 异常类，用于处理OAuth2认证过程中的作用域异常\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class ScopeException extends OAuth2AuthenticationException {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 使用指定消息构造ScopeException\n\t * @param msg 详细消息\n\t */\n\tpublic ScopeException(String msg) {\n\t\tsuper(new OAuth2Error(msg), msg);\n\t}\n\n\t/**\n\t * 使用指定的错误信息和根异常构造ScopeException\n\t * @param msg 错误详细信息\n\t * @param cause 根异常\n\t */\n\tpublic ScopeException(String msg, Throwable cause) {\n\t\tsuper(new OAuth2Error(msg), cause);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/SecurityUtils.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.security.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.security.service.PigUser;\nimport lombok.experimental.UtilityClass;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.core.context.SecurityContextHolder;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 安全工具类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@UtilityClass\npublic class SecurityUtils {\n\n\t/**\n\t * 获取当前安全上下文的认证信息\n\t * @return 当前认证信息对象\n\t */\n\tpublic Authentication getAuthentication() {\n\t\treturn SecurityContextHolder.getContext().getAuthentication();\n\t}\n\n\t/**\n\t * 获取当前认证用户\n\t * @param authentication 认证信息\n\t * @return 用户对象，如果认证主体不是PigUser类型则返回null\n\t */\n\tpublic PigUser getUser(Authentication authentication) {\n\t\tObject principal = authentication.getPrincipal();\n\t\tif (principal instanceof PigUser) {\n\t\t\treturn (PigUser) principal;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * 获取当前认证用户\n\t * @return 当前认证用户对象，未认证时返回null\n\t */\n\tpublic PigUser getUser() {\n\t\tAuthentication authentication = getAuthentication();\n\t\tif (authentication == null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn getUser(authentication);\n\t}\n\n\t/**\n\t * 获取用户角色信息\n\t * @return 角色集合\n\t */\n\tpublic List<Long> getRoles() {\n\t\tAuthentication authentication = getAuthentication();\n\t\tCollection<? extends GrantedAuthority> authorities = authentication.getAuthorities();\n\n\t\tList<Long> roleIds = new ArrayList<>();\n\t\tauthorities.stream()\n\t\t\t.filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE))\n\t\t\t.forEach(granted -> {\n\t\t\t\tString id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE);\n\t\t\t\troleIds.add(Long.parseLong(id));\n\t\t\t});\n\t\treturn roleIds;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.security.service.PigUserDetailsServiceImpl\ncom.pig4cloud.pig.common.security.service.PigAppUserDetailsServiceImpl\ncom.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationService\ncom.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationConsentService\ncom.pig4cloud.pig.common.security.component.PigSecurityInnerAspect\ncom.pig4cloud.pig.common.security.component.PigSecurityMessageSourceConfiguration\ncom.pig4cloud.pig.common.security.component.PigBootCorsProperties\ncom.pig4cloud.pig.common.security.service.PigRemoteRegisteredClientRepository\n"
  },
  {
    "path": "pig-common/pig-common-security/src/main/resources/i18n/errors/messages_zh_CN.properties",
    "content": "AbstractAccessDecisionManager.accessDenied=\\u4E0D\\u5141\\u8BB8\\u8BBF\\u95EE\nAbstractLdapAuthenticationProvider.emptyPassword=\\u7528\\u6237\\u540D\\u6216\\u5BC6\\u7801\\u9519\\u8BEF\nAbstractSecurityInterceptor.authenticationNotFound=\\u672A\\u5728SecurityContext\\u4E2D\\u67E5\\u627E\\u5230\\u8BA4\\u8BC1\\u5BF9\\u8C61\nOAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired=\\u8BF7\\u6C42\\u4EE4\\u724C\\u5DF2\\u8FC7\\u671F\nAbstractUserDetailsAuthenticationProvider.smsBadCredentials=\\u624B\\u673A\\u53F7\\u4E0D\\u5B58\\u5728\\u767B\\u5F55\\u5931\\u8D25\nAbstractUserDetailsAuthenticationProvider.badCredentials=\\u7528\\u6237\\u540D\\u6216\\u5BC6\\u7801\\u9519\\u8BEF\nAbstractUserDetailsAuthenticationProvider.credentialsExpired=\\u7528\\u6237\\u51ED\\u8BC1\\u5DF2\\u8FC7\\u671F\nAbstractUserDetailsAuthenticationProvider.disabled=\\u7528\\u6237\\u5DF2\\u5931\\u6548\nAbstractUserDetailsAuthenticationProvider.expired=\\u7528\\u6237\\u5E10\\u53F7\\u5DF2\\u8FC7\\u671F\nAbstractUserDetailsAuthenticationProvider.locked=\\u7528\\u6237\\u5E10\\u53F7\\u5DF2\\u88AB\\u9501\\u5B9A\nAbstractUserDetailsAuthenticationProvider.onlySupports=\\u4EC5\\u4EC5\\u652F\\u6301UsernamePasswordAuthenticationToken\nAccountStatusUserDetailsChecker.credentialsExpired=\\u7528\\u6237\\u51ED\\u8BC1\\u5DF2\\u8FC7\\u671F\nAccountStatusUserDetailsChecker.disabled=\\u7528\\u6237\\u5DF2\\u5931\\u6548\nAccountStatusUserDetailsChecker.expired=\\u7528\\u6237\\u5E10\\u53F7\\u5DF2\\u8FC7\\u671F\nAccountStatusUserDetailsChecker.locked=\\u7528\\u6237\\u5E10\\u53F7\\u5DF2\\u88AB\\u9501\\u5B9A\nAclEntryAfterInvocationProvider.noPermission=\\u7ED9\\u5B9A\\u7684Authentication\\u5BF9\\u8C61({0})\\u6839\\u672C\\u65E0\\u6743\\u64CD\\u63A7\\u9886\\u57DF\\u5BF9\\u8C61({1})\nAnonymousAuthenticationProvider.incorrectKey=\\u5C55\\u793A\\u7684AnonymousAuthenticationToken\\u4E0D\\u542B\\u6709\\u9884\\u671F\\u7684key\nBindAuthenticator.badCredentials=\\u7528\\u6237\\u540D\\u6216\\u5BC6\\u7801\\u9519\\u8BEF\nBindAuthenticator.emptyPassword=\\u7528\\u6237\\u540D\\u6216\\u5BC6\\u7801\\u9519\\u8BEF\nCasAuthenticationProvider.incorrectKey=\\u5C55\\u793A\\u7684CasAuthenticationToken\\u4E0D\\u542B\\u6709\\u9884\\u671F\\u7684key\nCasAuthenticationProvider.noServiceTicket=\\u672A\\u80FD\\u591F\\u6B63\\u786E\\u63D0\\u4F9B\\u5F85\\u9A8C\\u8BC1\\u7684CAS\\u670D\\u52A1\\u7968\\u6839\nConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\\u5DF2\\u7ECF\\u8D85\\u8FC7\\u4E86\\u5F53\\u524D\\u4E3B\\u4F53({0})\\u88AB\\u5141\\u8BB8\\u7684\\u6700\\u5927\\u4F1A\\u8BDD\\u6570\\u91CF\nDigestAuthenticationFilter.incorrectRealm=\\u54CD\\u5E94\\u7ED3\\u679C\\u4E2D\\u7684Realm\\u540D\\u5B57({0})\\u540C\\u7CFB\\u7EDF\\u6307\\u5B9A\\u7684Realm\\u540D\\u5B57({1})\\u4E0D\\u543B\\u5408\nDigestAuthenticationFilter.incorrectResponse=\\u9519\\u8BEF\\u7684\\u54CD\\u5E94\\u7ED3\\u679C\nDigestAuthenticationFilter.missingAuth=\\u9057\\u6F0F\\u4E86\\u9488\\u5BF9'auth' QOP\\u7684\\u3001\\u5FC5\\u987B\\u7ED9\\u5B9A\\u7684\\u6458\\u8981\\u53D6\\u503C; \\u63A5\\u6536\\u5230\\u7684\\u5934\\u4FE1\\u606F\\u4E3A{0}\nDigestAuthenticationFilter.missingMandatory=\\u9057\\u6F0F\\u4E86\\u5FC5\\u987B\\u7ED9\\u5B9A\\u7684\\u6458\\u8981\\u53D6\\u503C; \\u63A5\\u6536\\u5230\\u7684\\u5934\\u4FE1\\u606F\\u4E3A{0}\nDigestAuthenticationFilter.nonceCompromised=Nonce\\u4EE4\\u724C\\u5DF2\\u7ECF\\u5B58\\u5728\\u95EE\\u9898\\u4E86\\uFF0C{0}\nDigestAuthenticationFilter.nonceEncoding=Nonce\\u672A\\u7ECF\\u8FC7Base64\\u7F16\\u7801; \\u76F8\\u5E94\\u7684nonce\\u53D6\\u503C\\u4E3A {0}\nDigestAuthenticationFilter.nonceExpired=Nonce\\u5DF2\\u7ECF\\u8FC7\\u671F/\\u8D85\\u65F6\nDigestAuthenticationFilter.nonceNotNumeric=Nonce\\u4EE4\\u724C\\u7684\\u7B2C1\\u90E8\\u5206\\u5E94\\u8BE5\\u662F\\u6570\\u5B57\\uFF0C\\u4F46\\u7ED3\\u679C\\u5374\\u662F{0}\nDigestAuthenticationFilter.nonceNotTwoTokens=Nonce\\u5E94\\u8BE5\\u7531\\u4E24\\u90E8\\u5206\\u53D6\\u503C\\u6784\\u6210\\uFF0C\\u4F46\\u7ED3\\u679C\\u5374\\u662F{0}\nDigestAuthenticationFilter.usernameNotFound=\\u7528\\u6237\\u540D{0}\\u672A\\u627E\\u5230\nExceptionTranslationFilter.insufficientAuthentication=\\u8BBF\\u95EE\\u6B64\\u8D44\\u6E90\\u9700\\u8981\\u5B8C\\u5168\\u8EAB\\u4EFD\\u9A8C\\u8BC1\nJdbcDaoImpl.noAuthority=\\u6CA1\\u6709\\u4E3A\\u7528\\u6237{0}\\u6307\\u5B9A\\u89D2\\u8272\nJdbcDaoImpl.notFound=\\u672A\\u627E\\u5230\\u7528\\u6237{0}\nLdapAuthenticationProvider.badCredentials=\\u7528\\u6237\\u540D\\u6216\\u5BC6\\u7801\\u9519\\u8BEF\nLdapAuthenticationProvider.credentialsExpired=\\u7528\\u6237\\u51ED\\u8BC1\\u5DF2\\u8FC7\\u671F\nLdapAuthenticationProvider.disabled=\\u7528\\u6237\\u5DF2\\u5931\\u6548\nLdapAuthenticationProvider.expired=\\u7528\\u6237\\u5E10\\u53F7\\u5DF2\\u8FC7\\u671F\nLdapAuthenticationProvider.locked=\\u7528\\u6237\\u5E10\\u53F7\\u5DF2\\u88AB\\u9501\\u5B9A\nLdapAuthenticationProvider.emptyUsername=\\u7528\\u6237\\u540D\\u4E0D\\u5141\\u8BB8\\u4E3A\\u7A7A\nLdapAuthenticationProvider.onlySupports=\\u4EC5\\u4EC5\\u652F\\u6301UsernamePasswordAuthenticationToken\nPasswordComparisonAuthenticator.badCredentials=\\u7528\\u6237\\u540D\\u6216\\u5BC6\\u7801\\u9519\\u8BEF\n#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.\nProviderManager.providerNotFound=\\u672A\\u67E5\\u627E\\u5230\\u9488\\u5BF9{0}\\u7684AuthenticationProvider\nRememberMeAuthenticationProvider.incorrectKey=\\u5C55\\u793ARememberMeAuthenticationToken\\u4E0D\\u542B\\u6709\\u9884\\u671F\\u7684key\nRunAsImplAuthenticationProvider.incorrectKey=\\u5C55\\u793A\\u7684RunAsUserToken\\u4E0D\\u542B\\u6709\\u9884\\u671F\\u7684key\nSubjectDnX509PrincipalExtractor.noMatching=\\u672A\\u5728subjectDN\\: {0}\\u4E2D\\u627E\\u5230\\u5339\\u914D\\u7684\\u6A21\\u5F0F\nSwitchUserFilter.noCurrentUser=\\u4E0D\\u5B58\\u5728\\u5F53\\u524D\\u7528\\u6237\nSwitchUserFilter.noOriginalAuthentication=\\u4E0D\\u80FD\\u591F\\u67E5\\u627E\\u5230\\u539F\\u5148\\u7684\\u5DF2\\u8BA4\\u8BC1\\u5BF9\\u8C61\n"
  },
  {
    "path": "pig-common/pig-common-swagger/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-swagger</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 接口文档</description>\n\n\n    <dependencies>\n        <!--接口文档-->\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>\n        </dependency>\n        <!--webflux 相关包-->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webflux</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <!--网关 swagger 聚合依赖-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-gateway-server</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-commons</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/annotation/EnablePigDoc.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.swagger.annotation;\n\nimport com.pig4cloud.pig.common.core.factory.YamlPropertySourceFactory;\nimport com.pig4cloud.pig.common.swagger.config.OpenAPIDefinitionImportSelector;\nimport com.pig4cloud.pig.common.swagger.support.SwaggerProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.context.annotation.PropertySource;\n\nimport java.lang.annotation.*;\n\n/**\n * 启用Pig框架的Spring文档支持\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Target({ ElementType.TYPE })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@EnableConfigurationProperties(SwaggerProperties.class)\n@Import(OpenAPIDefinitionImportSelector.class)\n@PropertySource(value = \"classpath:openapi-config.yaml\", factory = YamlPropertySourceFactory.class)\npublic @interface EnablePigDoc {\n\n\t/**\n\t * 网关路由前缀\n\t * @return String\n\t */\n\tString value();\n\n\t/**\n\t * 是否是微服务架构\n\t * @return true\n\t */\n\tboolean isMicro() default true;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/config/OpenAPIDefinition.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.common.swagger.config;\n\nimport com.pig4cloud.pig.common.swagger.support.SwaggerProperties;\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.info.Info;\nimport io.swagger.v3.oas.models.security.OAuthFlow;\nimport io.swagger.v3.oas.models.security.OAuthFlows;\nimport io.swagger.v3.oas.models.security.Scopes;\nimport io.swagger.v3.oas.models.security.SecurityScheme;\nimport io.swagger.v3.oas.models.servers.Server;\nimport lombok.RequiredArgsConstructor;\nimport lombok.Setter;\nimport org.springdoc.core.utils.SpringDocUtils;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.http.HttpHeaders;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Swagger配置类，用于配置OpenAPI定义\n * <p>\n * 支持通过配置控制Swagger的启用状态，并提供OAuth2安全认证配置\n * </p>\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RequiredArgsConstructor\n@ConditionalOnProperty(name = \"swagger.enabled\", matchIfMissing = true)\npublic class OpenAPIDefinition extends OpenAPI implements InitializingBean, ApplicationContextAware {\n\n\t@Setter\n\tprivate String path;\n\n\t/**\n\t * 应用上下文\n\t */\n\tprivate ApplicationContext applicationContext;\n\n\t/**\n\t * 创建并配置OAuth2安全方案\n\t * @param swaggerProperties Swagger配置属性\n\t * @return 配置好的SecurityScheme对象\n\t */\n\tprivate SecurityScheme securityScheme(SwaggerProperties swaggerProperties) {\n\t\tOAuthFlow clientCredential = new OAuthFlow();\n\t\tclientCredential.setTokenUrl(swaggerProperties.getTokenUrl());\n\t\tclientCredential.setScopes(new Scopes().addString(swaggerProperties.getScope(), swaggerProperties.getScope()));\n\t\tOAuthFlows oauthFlows = new OAuthFlows();\n\t\toauthFlows.password(clientCredential);\n\t\tSecurityScheme securityScheme = new SecurityScheme();\n\t\tsecurityScheme.setType(SecurityScheme.Type.OAUTH2);\n\t\tsecurityScheme.setFlows(oauthFlows);\n\t\treturn securityScheme;\n\t}\n\n\t/**\n\t * 初始化Swagger配置\n\t * @throws Exception 初始化过程中可能抛出的异常\n\t */\n\t@Override\n\tpublic void afterPropertiesSet() throws Exception {\n\t\tSwaggerProperties swaggerProperties = applicationContext.getBean(SwaggerProperties.class);\n\t\tthis.info(new Info().title(swaggerProperties.getTitle()));\n\t\t// oauth2.0 password\n\t\tthis.schemaRequirement(HttpHeaders.AUTHORIZATION, this.securityScheme(swaggerProperties));\n\t\t// servers\n\t\tList<Server> serverList = new ArrayList<>();\n\t\tserverList.add(new Server().url(swaggerProperties.getGateway() + \"/\" + path));\n\t\tthis.servers(serverList);\n\t\t// 支持参数平铺\n\t\tSpringDocUtils.getConfig().addSimpleTypesForParameterObject(Class.class);\n\t}\n\n\t/**\n\t * 设置应用上下文\n\t * @param applicationContext 应用上下文对象\n\t * @throws BeansException 如果设置上下文时发生错误\n\t */\n\t@Override\n\tpublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n\t\tthis.applicationContext = applicationContext;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/config/OpenAPIDefinitionImportSelector.java",
    "content": "package com.pig4cloud.pig.common.swagger.config;\n\nimport com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;\nimport org.springframework.beans.factory.support.BeanDefinitionBuilder;\nimport org.springframework.beans.factory.support.BeanDefinitionRegistry;\nimport org.springframework.context.annotation.ImportBeanDefinitionRegistrar;\nimport org.springframework.core.type.AnnotationMetadata;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * OpenAPI 配置类，用于动态注册 OpenAPI 相关 Bean 定义\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class OpenAPIDefinitionImportSelector implements ImportBeanDefinitionRegistrar {\n\n\t/**\n\t * 注册Bean定义，根据注解元数据配置OpenAPI相关Bean\n\t * @param metadata 注解元数据\n\t * @param registry Bean定义注册器\n\t */\n\t@Override\n\tpublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {\n\n\t\tMap<String, Object> annotationAttributes = metadata.getAnnotationAttributes(EnablePigDoc.class.getName(), true);\n\t\tObject value = annotationAttributes.get(\"value\");\n\t\tif (Objects.isNull(value)) {\n\t\t\treturn;\n\t\t}\n\n\t\tBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(OpenAPIDefinition.class);\n\t\tdefinition.addPropertyValue(\"path\", value);\n\t\tdefinition.setPrimary(true);\n\n\t\tregistry.registerBeanDefinition(\"openAPIDefinition\", definition.getBeanDefinition());\n\n\t\t// 如果是微服务架构则，引入了服务发现声明相关的元数据配置\n\t\tObject isMicro = annotationAttributes.getOrDefault(\"isMicro\", true);\n\t\tif (isMicro.equals(false)) {\n\t\t\treturn;\n\t\t}\n\n\t\tBeanDefinitionBuilder openAPIMetadata = BeanDefinitionBuilder\n\t\t\t.genericBeanDefinition(OpenAPIMetadataConfiguration.class);\n\t\topenAPIMetadata.addPropertyValue(\"path\", value);\n\t\tregistry.registerBeanDefinition(\"openAPIMetadata\", openAPIMetadata.getBeanDefinition());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/config/OpenAPIMetadataConfiguration.java",
    "content": "package com.pig4cloud.pig.common.swagger.config;\n\nimport lombok.Setter;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\n\n/**\n * OpenAPI 元数据配置类，用于配置并注册OpenAPI相关元数据\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class OpenAPIMetadataConfiguration implements InitializingBean, ApplicationContextAware {\n\n\t/**\n\t * 应用上下文\n\t */\n\tprivate ApplicationContext applicationContext;\n\n\t@Setter\n\tprivate String path;\n\n\t/**\n\t * 在属性设置完成后执行，将spring-doc路径信息注册到ServiceInstance的元数据中\n\t * @throws Exception 如果执行过程中发生错误\n\t */\n\t@Override\n\tpublic void afterPropertiesSet() throws Exception {\n\n\t\tString[] beanNamesForType = applicationContext.getBeanNamesForType(ServiceInstance.class);\n\n\t\tif (beanNamesForType.length == 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tServiceInstance serviceInstance = applicationContext.getBean(ServiceInstance.class);\n\t\tserviceInstance.getMetadata().put(\"spring-doc\", path);\n\t}\n\n\t/**\n\t * 设置应用上下文\n\t * @param applicationContext 应用上下文对象\n\t * @throws BeansException 如果设置上下文时发生错误\n\t */\n\t@Override\n\tpublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n\t\tthis.applicationContext = applicationContext;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/support/SwaggerProperties.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.common.swagger.support;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Swagger配置属性类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Data\n@ConfigurationProperties(\"swagger\")\npublic class SwaggerProperties {\n\n\t/**\n\t * 是否开启swagger\n\t */\n\tprivate Boolean enabled = true;\n\n\t/**\n\t * swagger会解析的包路径\n\t **/\n\tprivate String basePackage = \"\";\n\n\t/**\n\t * swagger会解析的url规则\n\t **/\n\tprivate List<String> basePath = new ArrayList<>();\n\n\t/**\n\t * 在basePath基础上需要排除的url规则\n\t **/\n\tprivate List<String> excludePath = new ArrayList<>();\n\n\t/**\n\t * 需要排除的服务\n\t */\n\tprivate List<String> ignoreProviders = new ArrayList<>();\n\n\t/**\n\t * 标题\n\t **/\n\tprivate String title = \"\";\n\n\t/**\n\t * 网关\n\t */\n\tprivate String gateway;\n\n\t/**\n\t * 获取token\n\t */\n\tprivate String tokenUrl;\n\n\t/**\n\t * 作用域\n\t */\n\tprivate String scope;\n\n\t/**\n\t * 服务转发配置\n\t */\n\tprivate Map<String, String> services;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-swagger/src/main/resources/openapi-config.yaml",
    "content": "# swagger 配置\nswagger:\n  enabled: true\n  title: Pig Swagger API\n  gateway: http://${GATEWAY-HOST:127.0.0.1}:${GATEWAY-PORT:9999}\n  token-url: ${swagger.gateway}/auth/oauth2/token\n  scope: server\n"
  },
  {
    "path": "pig-common/pig-common-websocket/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>com.pig4cloud</groupId>\n\t\t<artifactId>pig-common</artifactId>\n\t\t<version>${revision}</version>\n\t</parent>\n\n    <artifactId>pig-common-websocket</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig websocket 链接模块</description>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-websocket</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.data</groupId>\n            <artifactId>spring-data-redis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-json</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-security</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/LocalMessageDistributorConfiguration.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\nimport com.pig4cloud.pig.common.websocket.distribute.LocalMessageDistributor;\nimport com.pig4cloud.pig.common.websocket.distribute.MessageDistributor;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 本地的消息分发器配置\n *\n * @author hccake\n */\n@ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = \"message-distributor\",\n\t\thavingValue = MessageDistributorTypeConstants.LOCAL)\n@Configuration(proxyBeanMethods = false)\npublic class LocalMessageDistributorConfiguration {\n\n\t/**\n\t * 配置本地消息分发器，使用本地内存实现，不支持集群环境。\n\t * @return 返回一个 {@link LocalMessageDistributor} 实例，用于处理本地消息分发。\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean(MessageDistributor.class)\n\tpublic LocalMessageDistributor messageDistributor() {\n\t\treturn new LocalMessageDistributor();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/MessageDistributorTypeConstants.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\n/**\n * 消息分发器类型常量\n * <p>\n * 定义了不同消息分发策略的常量标识符。\n * </p>\n *\n * @author hccake\n */\npublic final class MessageDistributorTypeConstants {\n\n\tprivate MessageDistributorTypeConstants() {\n\t}\n\n\t/**\n\t * 本地消息分发\n\t * <p>\n\t * 适用于单机环境，消息在当前服务实例内部分发。\n\t * </p>\n\t */\n\tpublic static final String LOCAL = \"local\";\n\n\t/**\n\t * 基于 Redis PUB/SUB 的消息分发\n\t * <p>\n\t * 适用于集群环境，通过 Redis 的发布/订阅机制实现跨服务实例的消息分发。\n\t * </p>\n\t */\n\tpublic static final String REDIS = \"redis\";\n\n\t/**\n\t * 自定义消息分发\n\t * <p>\n\t * 用户可以实现自定义的消息分发逻辑。\n\t * </p>\n\t */\n\tpublic static final String CUSTOM = \"custom\";\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/RedisMessageDistributorConfiguration.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\nimport com.pig4cloud.pig.common.websocket.distribute.MessageDistributor;\nimport com.pig4cloud.pig.common.websocket.distribute.RedisMessageDistributor;\nimport com.pig4cloud.pig.common.websocket.distribute.RedisWebsocketMessageListener;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.data.redis.listener.PatternTopic;\nimport org.springframework.data.redis.listener.RedisMessageListenerContainer;\n\nimport jakarta.annotation.PostConstruct;\n\n/**\n * 基于 Redis Pub/Sub 的消息分发器配置\n * <p>\n * 当 WebSocket 配置为使用 Redis 进行消息分发时，此配置类生效。 它提供了基于 Redis 的 {@link MessageDistributor}\n * 实现，适用于集群环境。\n * </p>\n *\n * @author hccake\n */\n@ConditionalOnClass(StringRedisTemplate.class)\n@ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = \"message-distributor\",\n\t\thavingValue = MessageDistributorTypeConstants.REDIS, matchIfMissing = true)\n@Configuration(proxyBeanMethods = false)\npublic class RedisMessageDistributorConfiguration {\n\n\t/**\n\t * 创建一个基于 Redis 的消息分发器。\n\t * @param stringRedisTemplate Spring Data Redis 提供的 Redis 操作模板。\n\t * @return 返回一个 {@link RedisMessageDistributor} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean(MessageDistributor.class)\n\tpublic RedisMessageDistributor messageDistributor(StringRedisTemplate stringRedisTemplate) {\n\t\treturn new RedisMessageDistributor(stringRedisTemplate);\n\t}\n\n\t/**\n\t * 创建 Redis WebSocket 消息监听器。\n\t * @param stringRedisTemplate Spring Data Redis 提供的 Redis 操作模板。\n\t * @return 返回一个 {@link RedisWebsocketMessageListener} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnBean(RedisMessageDistributor.class)\n\t@ConditionalOnMissingBean\n\tpublic RedisWebsocketMessageListener redisWebsocketMessageListener(StringRedisTemplate stringRedisTemplate) {\n\t\treturn new RedisWebsocketMessageListener(stringRedisTemplate);\n\t}\n\n\t/**\n\t * 创建 Redis 消息监听器容器，用于管理消息监听器。\n\t * @param connectionFactory Redis 连接工厂。\n\t * @return 返回一个 {@link RedisMessageListenerContainer} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnBean(RedisMessageDistributor.class)\n\t@ConditionalOnMissingBean\n\tpublic RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {\n\t\tRedisMessageListenerContainer container = new RedisMessageListenerContainer();\n\t\tcontainer.setConnectionFactory(connectionFactory);\n\t\treturn container;\n\t}\n\n\t/**\n\t * Redis 消息监听器注册配置\n\t * <p>\n\t * 在 Spring 初始化后，将 {@link RedisWebsocketMessageListener} 注册到 Redis 消息监听器容器中。\n\t * </p>\n\t */\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnMissingBean(MessageDistributor.class)\n\t@RequiredArgsConstructor\n\tstatic class RedisMessageListenerRegisterConfiguration {\n\n\t\tprivate final RedisMessageListenerContainer redisMessageListenerContainer;\n\n\t\tprivate final RedisWebsocketMessageListener redisWebsocketMessageListener;\n\n\t\t/**\n\t\t * 将 WebSocket 消息监听器添加到 Redis 监听容器中，监听指定频道的消息。\n\t\t */\n\t\t@PostConstruct\n\t\tpublic void addMessageListener() {\n\t\t\tredisMessageListenerContainer.addMessageListener(redisWebsocketMessageListener,\n\t\t\t\t\tnew PatternTopic(RedisWebsocketMessageListener.CHANNEL));\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketAutoConfiguration.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\nimport com.pig4cloud.pig.common.websocket.handler.JsonMessageHandler;\nimport com.pig4cloud.pig.common.websocket.holder.JsonMessageHandlerHolder;\nimport jakarta.annotation.PostConstruct;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.config.annotation.EnableWebSocket;\nimport org.springframework.web.socket.config.annotation.WebSocketConfigurer;\nimport org.springframework.web.socket.server.HandshakeInterceptor;\n\nimport java.util.List;\n\n/**\n * WebSocket 自动配置类\n * <p>\n * 负责初始化和配置 WebSocket 功能，包括处理器、拦截器和消息分发机制。\n * </p>\n *\n * @author Yakir\n */\n@Import(WebSocketHandlerConfig.class)\n@EnableWebSocket\n@RequiredArgsConstructor\npublic class WebSocketAutoConfiguration {\n\n\tprivate final WebSocketProperties webSocketProperties;\n\n\tprivate final List<JsonMessageHandler> jsonMessageHandlerList;\n\n\t/**\n\t * 配置 WebSocket 连接处理器。\n\t * @param handshakeInterceptor 握手拦截器列表，用于在 WebSocket 握手阶段执行自定义逻辑。\n\t * @param webSocketHandler WebSocket 处理器，负责处理 WebSocket 连接和消息。\n\t * @return 返回一个 {@link WebSocketConfigurer} 实例，用于注册 WebSocket 处理器和拦截器。\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic WebSocketConfigurer webSocketConfigurer(List<HandshakeInterceptor> handshakeInterceptor,\n\t\t\tWebSocketHandler webSocketHandler) {\n\t\treturn registry -> registry.addHandler(webSocketHandler, webSocketProperties.getPath())\n\t\t\t.setAllowedOrigins(webSocketProperties.getAllowOrigins())\n\t\t\t.addInterceptors(handshakeInterceptor.toArray(new HandshakeInterceptor[0]));\n\t}\n\n\t/**\n\t * 初始化 JSON 消息处理器持有者。\n\t * <p>\n\t * 在应用启动时，将所有实现了 {@link JsonMessageHandler} 接口的处理器注册到 {@link JsonMessageHandlerHolder}\n\t * 中， 以便后续根据消息类型快速查找和调用。\n\t * </p>\n\t */\n\t@PostConstruct\n\tpublic void initJsonMessageHandlerHolder() {\n\t\tfor (JsonMessageHandler jsonMessageHandler : jsonMessageHandlerList) {\n\t\t\tJsonMessageHandlerHolder.addHandler(jsonMessageHandler);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketHandlerConfig.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\nimport com.pig4cloud.pig.common.websocket.custom.PigxSessionKeyGenerator;\nimport com.pig4cloud.pig.common.websocket.custom.UserAttributeHandshakeInterceptor;\nimport com.pig4cloud.pig.common.websocket.handler.CustomPlanTextMessageHandler;\nimport com.pig4cloud.pig.common.websocket.handler.CustomWebSocketHandler;\nimport com.pig4cloud.pig.common.websocket.handler.PingJsonMessageHandler;\nimport com.pig4cloud.pig.common.websocket.handler.PlanTextMessageHandler;\nimport com.pig4cloud.pig.common.websocket.holder.MapSessionWebSocketHandlerDecorator;\nimport com.pig4cloud.pig.common.websocket.holder.SessionKeyGenerator;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.handler.TextWebSocketHandler;\nimport org.springframework.web.socket.server.HandshakeInterceptor;\n\n/**\n * WebSocket 处理器配置类\n * <p>\n * 负责配置和创建 WebSocket 相关的处理器、拦截器和会话管理组件。\n * </p>\n *\n * @author Hccake 2021/1/5\n * @version 1.0\n */\n@RequiredArgsConstructor\n@EnableConfigurationProperties(WebSocketProperties.class)\npublic class WebSocketHandlerConfig {\n\n\tprivate final WebSocketProperties webSocketProperties;\n\n\t/**\n\t * 创建会话密钥生成器，用于生成 WebSocket 会话的唯一标识。\n\t * @return 返回一个 {@link SessionKeyGenerator} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean(SessionKeyGenerator.class)\n\tpublic SessionKeyGenerator sessionKeyGenerator() {\n\t\treturn new PigxSessionKeyGenerator();\n\t}\n\n\t/**\n\t * 创建握手拦截器，用于在 WebSocket 握手阶段添加用户属性。\n\t * @return 返回一个 {@link HandshakeInterceptor} 实例。\n\t */\n\t@Bean\n\tpublic HandshakeInterceptor handshakeInterceptor() {\n\t\treturn new UserAttributeHandshakeInterceptor();\n\t}\n\n\t/**\n\t * 创建默认的纯文本消息处理器。\n\t * @return 返回一个 {@link PlanTextMessageHandler} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean(PlanTextMessageHandler.class)\n\tpublic PlanTextMessageHandler planTextMessageHandler() {\n\t\treturn new CustomPlanTextMessageHandler();\n\t}\n\n\t/**\n\t * 创建 WebSocket 处理器，当没有纯文本消息处理器时使用。\n\t * @param sessionKeyGenerator 会话密钥生成器。\n\t * @return 返回一个 {@link WebSocketHandler} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean({ TextWebSocketHandler.class, PlanTextMessageHandler.class })\n\tpublic WebSocketHandler webSocketHandler1(@Autowired(required = false) SessionKeyGenerator sessionKeyGenerator) {\n\t\tCustomWebSocketHandler customWebSocketHandler = new CustomWebSocketHandler();\n\t\tif (webSocketProperties.isMapSession()) {\n\t\t\treturn new MapSessionWebSocketHandlerDecorator(customWebSocketHandler, sessionKeyGenerator,\n\t\t\t\t\twebSocketProperties);\n\t\t}\n\t\treturn customWebSocketHandler;\n\t}\n\n\t/**\n\t * 创建 WebSocket 处理器，当存在纯文本消息处理器时使用。\n\t * @param sessionKeyGenerator 会话密钥生成器。\n\t * @param planTextMessageHandler 纯文本消息处理器。\n\t * @return 返回一个 {@link WebSocketHandler} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnBean(PlanTextMessageHandler.class)\n\t@ConditionalOnMissingBean(TextWebSocketHandler.class)\n\tpublic WebSocketHandler webSocketHandler2(@Autowired(required = false) SessionKeyGenerator sessionKeyGenerator,\n\t\t\tPlanTextMessageHandler planTextMessageHandler) {\n\t\tCustomWebSocketHandler customWebSocketHandler = new CustomWebSocketHandler(planTextMessageHandler);\n\t\tif (webSocketProperties.isMapSession()) {\n\t\t\treturn new MapSessionWebSocketHandlerDecorator(customWebSocketHandler, sessionKeyGenerator,\n\t\t\t\t\twebSocketProperties);\n\t\t}\n\t\treturn customWebSocketHandler;\n\t}\n\n\t/**\n\t * 创建 Ping 消息处理器，用于处理心跳检测。\n\t * @return 返回一个 {@link PingJsonMessageHandler} 实例。\n\t */\n\t@Bean\n\t@ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = \"heartbeat\", havingValue = \"true\",\n\t\t\tmatchIfMissing = true)\n\tpublic PingJsonMessageHandler pingJsonMessageHandler() {\n\t\treturn new PingJsonMessageHandler();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketMessageSender.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\nimport cn.hutool.json.JSONUtil;\nimport com.pig4cloud.pig.common.websocket.holder.WebSocketSessionHolder;\nimport com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.socket.TextMessage;\nimport org.springframework.web.socket.WebSocketSession;\n\nimport java.io.IOException;\nimport java.util.Collection;\n\n/**\n * WebSocket 消息发送器\n * <p>\n * 提供向 WebSocket 客户端发送消息的静态方法，支持广播和单点发送。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\n@Slf4j\npublic class WebSocketMessageSender {\n\n\t/**\n\t * 向所有在线的 WebSocket 会话广播消息。\n\t * @param message 要发送的消息文本。\n\t */\n\tpublic static void broadcast(String message) {\n\t\tCollection<WebSocketSession> sessions = WebSocketSessionHolder.getSessions();\n\t\tfor (WebSocketSession session : sessions) {\n\t\t\tsend(session, message);\n\t\t}\n\t}\n\n\t/**\n\t * 向指定会话标识的客户端发送消息。\n\t * @param sessionKey 会话的唯一标识。\n\t * @param message 要发送的消息文本。\n\t * @return 如果找到会话并成功发送，返回 {@code true}；否则返回 {@code false}。\n\t */\n\tpublic static boolean send(Object sessionKey, String message) {\n\t\tWebSocketSession session = WebSocketSessionHolder.getSession(sessionKey);\n\t\tif (session == null) {\n\t\t\tlog.info(\"[send] 当前 sessionKey：{} 对应 session 不在本服务中\", sessionKey);\n\t\t\treturn false;\n\t\t}\n\t\telse {\n\t\t\treturn send(session, message);\n\t\t}\n\t}\n\n\t/**\n\t * 向指定会话发送 JSON 格式的 WebSocket 消息。\n\t * @param session WebSocket 会话。\n\t * @param message 要发送的 JSON 消息对象。\n\t */\n\tpublic static void send(WebSocketSession session, JsonWebSocketMessage message) {\n\t\tsend(session, JSONUtil.toJsonStr(message));\n\t}\n\n\t/**\n\t * 向指定会话发送文本消息。\n\t * @param session WebSocket 会话。\n\t * @param message 要发送的消息文本。\n\t * @return 如果发送成功，返回 {@code true}；否则返回 {@code false}。\n\t */\n\tpublic static boolean send(WebSocketSession session, String message) {\n\t\tif (session == null) {\n\t\t\tlog.error(\"[send] session 为 null\");\n\t\t\treturn false;\n\t\t}\n\t\tif (!session.isOpen()) {\n\t\t\tlog.error(\"[send] session 已经关闭\");\n\t\t\treturn false;\n\t\t}\n\t\ttry {\n\t\t\tsession.sendMessage(new TextMessage(message));\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tlog.error(\"[send] session({}) 发送消息({}) 异常\", session, message, e);\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketProperties.java",
    "content": "package com.pig4cloud.pig.common.websocket.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n/**\n * WebSocket 属性配置类\n * <p>\n * 用于配置 WebSocket 的各种行为，如路径、心跳、消息分发等。\n * </p>\n *\n * @author Yakir\n */\n@Data\n@ConfigurationProperties(WebSocketProperties.PREFIX)\npublic class WebSocketProperties {\n\n\t/**\n\t * WebSocket 配置属性的前缀。\n\t */\n\tpublic static final String PREFIX = \"pigx.websocket\";\n\n\t/**\n\t * WebSocket 连接路径。\n\t * <p>\n\t * 支持路径参数，例如：/ws/{param} 或 /ws/{param1}/{param2}。 同时支持查询参数，例如：/ws?uid=1&name=test。\n\t * </p>\n\t */\n\tprivate String path = \"/ws/info\";\n\n\t/**\n\t * 允许的跨域来源，默认为 \"*\"，表示允许所有来源。\n\t */\n\tprivate String allowOrigins = \"*\";\n\n\t/**\n\t * 是否支持部分消息传输，默认为 {@code false}。\n\t */\n\tprivate boolean supportPartialMessages = false;\n\n\t/**\n\t * 是否启用心跳处理，默认为 {@code true}。\n\t */\n\tprivate boolean heartbeat = true;\n\n\t/**\n\t * 是否开启会话映射记录，默认为 {@code true}。\n\t * <p>\n\t * 开启后，会话将与唯一标识符关联，方便进行单点消息发送。\n\t * </p>\n\t */\n\tprivate boolean mapSession = true;\n\n\t/**\n\t * 消息分发器类型，默认为 \"local\"。\n\t * <p>\n\t * 可选值为 \"local\"（本地内存）或 \"redis\"（Redis Pub/Sub）。 也可配置为其他值以使用自定义分发器。\n\t * </p>\n\t */\n\tprivate String messageDistributor = MessageDistributorTypeConstants.LOCAL;\n\n\t/**\n\t * 消息发送时间限制（毫秒），默认为 10000。\n\t */\n\tprivate Integer sendTimeLimit = 10000;\n\n\t/**\n\t * 消息发送缓冲区大小限制（字节），默认为 64000。\n\t */\n\tprivate Integer sendBufferSizeLimit = 64000;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/custom/PigxSessionKeyGenerator.java",
    "content": "package com.pig4cloud.pig.common.websocket.custom;\n\nimport com.pig4cloud.pig.common.security.service.PigUser;\nimport com.pig4cloud.pig.common.websocket.holder.SessionKeyGenerator;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * WebSocket Session 标识生成器\n * <p>\n * 默认的 WebSocket 会话密钥生成器实现，用于根据用户信息生成唯一的会话标识。\n * </p>\n *\n * @author lengleng\n * @date 2021/10/4\n */\n@Configuration\n@RequiredArgsConstructor\npublic class PigxSessionKeyGenerator implements SessionKeyGenerator {\n\n\t/**\n\t * 根据 WebSocket 会话中的用户信息生成会话的唯一标识。\n\t * <p>\n\t * 此实现从会话属性中获取 {@link PigUser} 对象，并使用其 ID 作为唯一标识。\n\t * </p>\n\t * @param webSocketSession 当前的 WebSocket 会话。\n\t * @return 返回会话的唯一标识，如果无法确定用户，则返回 {@code null}。\n\t */\n\t@Override\n\tpublic Object sessionKey(WebSocketSession webSocketSession) {\n\n\t\tObject obj = webSocketSession.getAttributes().get(\"USER_KEY_ATTR_NAME\");\n\n\t\tif (obj instanceof PigUser user) {\n\t\t\t// userId 作为唯一区分\n\t\t\treturn String.valueOf(user.getId());\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/custom/UserAttributeHandshakeInterceptor.java",
    "content": "package com.pig4cloud.pig.common.websocket.custom;\n\nimport com.pig4cloud.pig.common.security.service.PigUser;\nimport com.pig4cloud.pig.common.security.util.SecurityUtils;\nimport org.springframework.http.server.ServerHttpRequest;\nimport org.springframework.http.server.ServerHttpResponse;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.server.HandshakeInterceptor;\n\nimport java.util.Map;\n\n/**\n * 用户属性握手拦截器\n * <p>\n * 在 WebSocket 握手阶段，将当前通过 Spring Security 认证的用户信息存入 WebSocket 会话属性中， 以便后续在 WebSocket\n * 处理流程中访问用户信息。\n * </p>\n *\n * @author lengleng\n * @date 2021/10/4\n */\npublic class UserAttributeHandshakeInterceptor implements HandshakeInterceptor {\n\n\t/**\n\t * 在 WebSocket 握手前调用，用于将用户信息添加到会话属性中。\n\t * <p>\n\t * 由于 WebSocket 握手是基于 HTTP 的，此时可以通过 {@link SecurityUtils} 获取已认证的用户信息。\n\t * </p>\n\t * @param request 当前的服务器请求。\n\t * @param response 当前的服务器响应。\n\t * @param wsHandler 目标 WebSocket 处理器。\n\t * @param attributes 用于存储 WebSocket 会话属性的映射。\n\t * @return 始终返回 {@code true}，允许握手继续。\n\t * @throws Exception 如果在处理过程中发生错误。\n\t */\n\t@Override\n\tpublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,\n\t\t\tMap<String, Object> attributes) throws Exception {\n\t\t// 由于 WebSocket 握手是由 http 升级的，携带 token 已经被 Security 拦截验证了，所以可以直接获取到用户\n\t\tPigUser user = SecurityUtils.getUser();\n\t\tattributes.put(\"USER_KEY_ATTR_NAME\", user);\n\t\treturn true;\n\t}\n\n\t/**\n\t * 在 WebSocket 握手完成后调用。\n\t * <p>\n\t * 此方法在此实现中为空，没有执行任何操作。\n\t * </p>\n\t * @param request 当前的服务器请求。\n\t * @param response 当前的服务器响应。\n\t * @param wsHandler 目标 WebSocket 处理器。\n\t * @param exception 握手过程中抛出的异常，如果没有异常则为 {@code null}。\n\t */\n\t@Override\n\tpublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,\n\t\t\tException exception) {\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/LocalMessageDistributor.java",
    "content": "package com.pig4cloud.pig.common.websocket.distribute;\n\n/**\n * 本地消息分发器\n * <p>\n * 在单机环境下，直接将消息发送给本地的 WebSocket 会话，不涉及跨服务通信。\n * </p>\n *\n * @author Hccake 2021/1/12\n * @version 1.0\n */\npublic class LocalMessageDistributor implements MessageDistributor, MessageSender {\n\n\t/**\n\t * 分发消息，对于本地分发器，直接调用发送逻辑。\n\t * @param messageDO 待发送的消息对象，包含消息内容和目标会话信息。\n\t */\n\t@Override\n\tpublic void distribute(MessageDO messageDO) {\n\t\tdoSend(messageDO);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/MessageDO.java",
    "content": "package com.pig4cloud.pig.common.websocket.distribute;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\nimport java.util.List;\n\n/**\n * 消息数据对象（Data Object）\n * <p>\n * 用于在消息分发过程中传递消息内容和元数据，如是否广播、目标会话等。\n * </p>\n *\n * @author Hccake 2021/1/12\n * @version 1.0\n */\n@Data\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\n@Accessors(chain = true)\npublic class MessageDO {\n\n\t/**\n\t * 是否需要广播消息。\n\t * <p>\n\t * 如果为 {@code true}，则消息将发送给所有在线的 WebSocket 会话。\n\t * </p>\n\t */\n\tprivate Boolean needBroadcast;\n\n\t/**\n\t * 目标会话的唯一标识列表。\n\t * <p>\n\t * 当 {@code needBroadcast} 为 {@code false} 或 {@code null} 时，消息将发送给此列表中的所有会话。\n\t * </p>\n\t */\n\tprivate List<Object> sessionKeys;\n\n\t/**\n\t * 需要发送的消息文本内容。\n\t */\n\tprivate String messageText;\n\n\t/**\n\t * 构建一个需要广播的消息对象。\n\t * @param text 要广播的消息文本。\n\t * @return 返回一个配置为广播模式的 {@link MessageDO} 实例。\n\t * @author lingting 2021-03-25 17:28\n\t */\n\tpublic static MessageDO broadcastMessage(String text) {\n\t\treturn new MessageDO().setMessageText(text).setNeedBroadcast(true);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/MessageDistributor.java",
    "content": "package com.pig4cloud.pig.common.websocket.distribute;\n\n/**\n * 消息分发器接口\n * <p>\n * 定义了消息分发的契约，负责将 WebSocket 消息路由到适当的目标， 支持本地内存分发和基于消息中间件（如 Redis）的分布式分发。\n * </p>\n *\n * @author Hccake 2021/1/12\n * @version 1.0\n */\npublic interface MessageDistributor {\n\n\t/**\n\t * 分发消息。\n\t * <p>\n\t * 根据实现类的不同，此方法可以将消息发送到本地会话或发布到消息队列中， 以便在集群环境中进行广播或单点发送。\n\t * </p>\n\t * @param messageDO 待发送的消息对象，包含消息内容和目标会话信息。\n\t */\n\tvoid distribute(MessageDO messageDO);\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/MessageSender.java",
    "content": "package com.pig4cloud.pig.common.websocket.distribute;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.pig4cloud.pig.common.websocket.config.WebSocketMessageSender;\n\nimport java.util.List;\n\n/**\n * 消息发送者接口\n * <p>\n * 提供了一个默认方法 {@code doSend}，用于实际执行 WebSocket 消息的发送操作。 此接口可由不同的消息分发策略实现，以统一发送逻辑。\n * </p>\n *\n * @author Hccake 2021/1/12\n * @version 1.0\n */\npublic interface MessageSender {\n\n\t/**\n\t * 执行消息发送。\n\t * <p>\n\t * 此方法会检查消息是否为广播，如果是，则向所有会话发送； 否则，向指定列表的会话发送。\n\t * </p>\n\t * @param messageDO 待发送的消息对象，包含消息内容和目标会话信息。\n\t */\n\tdefault void doSend(MessageDO messageDO) {\n\t\tBoolean needBroadcast = messageDO.getNeedBroadcast();\n\t\tString messageText = messageDO.getMessageText();\n\t\tList<Object> sessionKeys = messageDO.getSessionKeys();\n\t\tif (needBroadcast != null && needBroadcast) {\n\t\t\t// 广播信息\n\t\t\tWebSocketMessageSender.broadcast(messageText);\n\t\t}\n\t\telse if (CollectionUtil.isNotEmpty(sessionKeys)) {\n\t\t\t// 指定用户发送\n\t\t\tfor (Object sessionKey : sessionKeys) {\n\t\t\t\tWebSocketMessageSender.send(sessionKey, messageText);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/RedisMessageDistributor.java",
    "content": "package com.pig4cloud.pig.common.websocket.distribute;\n\nimport cn.hutool.json.JSONUtil;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 基于 Redis 的消息分发器\n * <p>\n * 在集群环境下，通过 Redis 的发布/订阅机制，将消息发布到指定频道， 由所有订阅该频道的服务实例进行消费和处理，从而实现跨服务的消息分发。\n * </p>\n *\n * @author Hccake 2021/1/12\n * @version 1.0\n */\n@RequiredArgsConstructor\npublic class RedisMessageDistributor implements MessageDistributor {\n\n\tprivate final StringRedisTemplate stringRedisTemplate;\n\n\t/**\n\t * 将消息发布到 Redis 频道。\n\t * <p>\n\t * 此方法首先将消息对象序列化为 JSON 字符串，然后通过 Redis 发布/订阅机制 将其发送到\n\t * {@link RedisWebsocketMessageListener#CHANNEL} 频道。\n\t * </p>\n\t * @param messageDO 待发送的消息对象，包含消息内容和目标会话信息。\n\t */\n\t@Override\n\tpublic void distribute(MessageDO messageDO) {\n\t\t// 包装 sessionKey 适配分布式多环境\n\t\tList<Object> sessionKeyList = new ArrayList<>(messageDO.getSessionKeys());\n\t\tmessageDO.setSessionKeys(sessionKeyList);\n\n\t\tString str = JSONUtil.toJsonStr(messageDO);\n\t\tstringRedisTemplate.convertAndSend(RedisWebsocketMessageListener.CHANNEL, str);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/RedisWebsocketMessageListener.java",
    "content": "package com.pig4cloud.pig.common.websocket.distribute;\n\nimport cn.hutool.json.JSONUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.data.redis.connection.Message;\nimport org.springframework.data.redis.connection.MessageListener;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.data.redis.serializer.RedisSerializer;\n\n/**\n * Redis WebSocket 消息监听器\n * <p>\n * 监听 Redis 的特定频道，接收并处理来自其他服务实例的 WebSocket 消息， 从而实现集群环境下的消息同步和分发。\n * </p>\n *\n * @author Hccake 2021/1/12\n * @version 1.0\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class RedisWebsocketMessageListener implements MessageListener, MessageSender {\n\n\t/**\n\t * WebSocket 消息在 Redis 发布/订阅中的频道名称。\n\t */\n\tpublic static final String CHANNEL = \"websocket-send\";\n\n\tprivate final StringRedisTemplate stringRedisTemplate;\n\n\t/**\n\t * 当从 Redis 频道接收到消息时调用此方法。\n\t * <p>\n\t * 此方法会反序列化消息内容，并调用 {@link #doSend(MessageDO)} 方法 将消息发送给当前服务实例中的目标 WebSocket 会话。\n\t * </p>\n\t * @param message 接收到的 Redis 消息。\n\t * @param bytes 订阅的频道模式（未使用）。\n\t */\n\t@Override\n\tpublic void onMessage(Message message, byte[] bytes) {\n\t\tlog.info(\"redis channel Listener message send {}\", message);\n\t\tbyte[] channelBytes = message.getChannel();\n\t\tRedisSerializer<String> stringSerializer = stringRedisTemplate.getStringSerializer();\n\t\tString channel = stringSerializer.deserialize(channelBytes);\n\n\t\t// 这里没有使用通配符，所以一定是true\n\t\tif (CHANNEL.equals(channel)) {\n\t\t\tbyte[] bodyBytes = message.getBody();\n\t\t\tString body = stringSerializer.deserialize(bodyBytes);\n\t\t\tMessageDO messageDO = JSONUtil.toBean(body, MessageDO.class);\n\t\t\tdoSend(messageDO);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/CustomPlanTextMessageHandler.java",
    "content": "package com.pig4cloud.pig.common.websocket.handler;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * 默认的纯文本消息处理器\n * <p>\n * 当 WebSocket 消息不符合预定义的 JSON 格式时，此处理器将作为默认实现， 仅记录接收到的消息内容和会话 ID，不执行其他业务逻辑。\n * </p>\n *\n * @author lengleng\n * @date 2021/11/4\n */\n@Slf4j\npublic class CustomPlanTextMessageHandler implements PlanTextMessageHandler {\n\n\t/**\n\t * 处理纯文本 WebSocket 消息。\n\t * <p>\n\t * 默认实现仅将接收到的消息内容和会话 ID 记录到日志中。\n\t * </p>\n\t * @param session 当前接收消息的 WebSocket 会话。\n\t * @param message 接收到的文本消息。\n\t */\n\t@Override\n\tpublic void handle(WebSocketSession session, String message) {\n\t\tlog.info(\"sessionId {} ,msg {}\", session.getId(), message);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/CustomWebSocketHandler.java",
    "content": "package com.pig4cloud.pig.common.websocket.handler;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.json.JsonReadFeature;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.common.websocket.holder.JsonMessageHandlerHolder;\nimport com.pig4cloud.pig.common.websocket.message.AbstractJsonWebSocketMessage;\nimport com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.socket.TextMessage;\nimport org.springframework.web.socket.WebSocketSession;\nimport org.springframework.web.socket.handler.TextWebSocketHandler;\n\n/**\n * 自定义 WebSocket 处理器\n * <p>\n * 继承自 {@link TextWebSocketHandler}，负责处理接收到的文本 WebSocket 消息。 它能够根据消息内容（JSON\n * 格式或纯文本）分发给不同的消息处理器。\n * </p>\n *\n * @author Hccake 2020/12/31\n * @version 1.0\n */\n@Slf4j\npublic class CustomWebSocketHandler extends TextWebSocketHandler {\n\n\tprivate static final ObjectMapper MAPPER = new ObjectMapper();\n\n\tstatic {\n\t\t// 有特殊需要转义字符, 不报错\n\t\tMAPPER.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature());\n\t}\n\n\tprivate PlanTextMessageHandler planTextMessageHandler;\n\n\t/**\n\t * 构造函数，创建一个不带纯文本消息处理器的 {@code CustomWebSocketHandler} 实例。\n\t */\n\tpublic CustomWebSocketHandler() {\n\t}\n\n\t/**\n\t * 构造函数，创建一个带纯文本消息处理器的 {@code CustomWebSocketHandler} 实例。\n\t * @param planTextMessageHandler 用于处理非 JSON 格式的纯文本消息。\n\t */\n\tpublic CustomWebSocketHandler(PlanTextMessageHandler planTextMessageHandler) {\n\t\tthis.planTextMessageHandler = planTextMessageHandler;\n\t}\n\n\t/**\n\t * 处理接收到的文本 WebSocket 消息。\n\t * <p>\n\t * 如果消息是 JSON 格式且包含 'type' 字段，则根据 'type' 查找并调用对应的 {@link JsonMessageHandler}。\n\t * 如果消息是纯文本或不包含 'type' 字段，则尝试使用 {@link PlanTextMessageHandler} 处理。\n\t * </p>\n\t * @param session 当前的 WebSocket 会话。\n\t * @param message 接收到的文本消息。\n\t * @throws JsonProcessingException 如果 JSON 消息处理失败。\n\t */\n\t@Override\n\tpublic void handleTextMessage(WebSocketSession session, TextMessage message) throws JsonProcessingException {\n\t\t// 空消息不处理\n\t\tif (message.getPayloadLength() == 0) {\n\t\t\treturn;\n\t\t}\n\n\t\t// 消息类型必有一属性type，先解析，获取该属性\n\t\tString payload = message.getPayload();\n\t\tJsonNode jsonNode = MAPPER.readTree(payload);\n\t\tJsonNode typeNode = jsonNode.get(AbstractJsonWebSocketMessage.TYPE_FIELD);\n\n\t\tif (typeNode == null) {\n\t\t\tif (planTextMessageHandler != null) {\n\t\t\t\tplanTextMessageHandler.handle(session, payload);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlog.error(\"[handleTextMessage] 普通文本消息（{}）没有对应的消息处理器\", payload);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tString messageType = typeNode.asText();\n\t\t\t// 获得对应的消息处理器\n\t\t\tJsonMessageHandler jsonMessageHandler = JsonMessageHandlerHolder.getHandler(messageType);\n\t\t\tif (jsonMessageHandler == null) {\n\t\t\t\tlog.error(\"[handleTextMessage] 消息类型（{}）不存在对应的消息处理器\", messageType);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// 消息处理\n\t\t\tClass<? extends JsonWebSocketMessage> messageClass = jsonMessageHandler.getMessageClass();\n\t\t\tJsonWebSocketMessage websocketMessageJson = MAPPER.treeToValue(jsonNode, messageClass);\n\t\t\tjsonMessageHandler.handle(session, websocketMessageJson);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/JsonMessageHandler.java",
    "content": "package com.pig4cloud.pig.common.websocket.handler;\n\nimport com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage;\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * JSON 消息处理器接口\n * <p>\n * 定义了处理特定类型 JSON 格式 WebSocket 消息的契约。 实现此接口的类负责解析和处理对应 {@link JsonWebSocketMessage} 类型的消息。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic interface JsonMessageHandler<T extends JsonWebSocketMessage> {\n\n\t/**\n\t * 处理 JSON 格式的 WebSocket 消息。\n\t * @param session 当前接收消息的 WebSocket 会话。\n\t * @param message 接收到的 JSON 消息对象，其类型由泛型 {@code T} 指定。\n\t */\n\tvoid handle(WebSocketSession session, T message);\n\n\t/**\n\t * 获取当前处理器能够处理的消息类型标识。\n\t * <p>\n\t * 此类型标识用于在 {@link JsonMessageHandlerHolder} 中查找对应的处理器。\n\t * </p>\n\t * @return 消息类型字符串。\n\t */\n\tString type();\n\n\t/**\n\t * 获取当前处理器对应的消息类的 Class 对象。\n\t * <p>\n\t * 此 Class 对象用于 JSON 消息的反序列化。\n\t * </p>\n\t * @return 消息类的 Class 对象。\n\t */\n\tClass<T> getMessageClass();\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/PingJsonMessageHandler.java",
    "content": "package com.pig4cloud.pig.common.websocket.handler;\n\nimport com.pig4cloud.pig.common.websocket.config.WebSocketMessageSender;\nimport com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage;\nimport com.pig4cloud.pig.common.websocket.message.PingJsonWebSocketMessage;\nimport com.pig4cloud.pig.common.websocket.message.PongJsonWebSocketMessage;\nimport com.pig4cloud.pig.common.websocket.message.WebSocketMessageTypeEnum;\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * Ping 消息处理器\n * <p>\n * 负责处理客户端发送的 Ping 消息，并立即回复一个 Pong 消息，用于维持 WebSocket 连接的心跳。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic class PingJsonMessageHandler implements JsonMessageHandler<PingJsonWebSocketMessage> {\n\n\t/**\n\t * 处理接收到的 Ping 消息。\n\t * <p>\n\t * 当接收到 Ping 消息时，会构建一个 Pong 消息并通过 {@link WebSocketMessageSender} 发送回客户端。\n\t * </p>\n\t * @param session 当前的 WebSocket 会话。\n\t * @param message 接收到的 Ping 消息对象。\n\t */\n\t@Override\n\tpublic void handle(WebSocketSession session, PingJsonWebSocketMessage message) {\n\t\tJsonWebSocketMessage pongJsonWebSocketMessage = new PongJsonWebSocketMessage();\n\t\tWebSocketMessageSender.send(session, pongJsonWebSocketMessage);\n\t}\n\n\t/**\n\t * 获取此处理器处理的消息类型。\n\t * @return 返回 {@link WebSocketMessageTypeEnum#PING} 的值。\n\t */\n\t@Override\n\tpublic String type() {\n\t\treturn WebSocketMessageTypeEnum.PING.getValue();\n\t}\n\n\t/**\n\t * 获取此处理器对应的消息 Class。\n\t * @return 返回 {@link PingJsonWebSocketMessage} 的 Class 对象。\n\t */\n\t@Override\n\tpublic Class<PingJsonWebSocketMessage> getMessageClass() {\n\t\treturn PingJsonWebSocketMessage.class;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/PlanTextMessageHandler.java",
    "content": "package com.pig4cloud.pig.common.websocket.handler;\n\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * 纯文本消息处理器接口\n * <p>\n * 定义了处理非 JSON 格式的纯文本 WebSocket 消息的契约。 当接收到的消息不符合预定义的 JSON 消息结构时，将由实现此接口的处理器进行处理。\n * </p>\n *\n * @see com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage\n * @author Hccake 2021/1/5\n * @version 1.0\n */\npublic interface PlanTextMessageHandler {\n\n\t/**\n\t * 处理纯文本 WebSocket 消息。\n\t * @param session 当前接收消息的 WebSocket 会话。\n\t * @param message 接收到的文本消息内容。\n\t */\n\tvoid handle(WebSocketSession session, String message);\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/JsonMessageHandlerHolder.java",
    "content": "package com.pig4cloud.pig.common.websocket.holder;\n\nimport com.pig4cloud.pig.common.websocket.handler.JsonMessageHandler;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * JSON 消息处理器持有者\n * <p>\n * 负责管理和存储所有注册的 {@link JsonMessageHandler} 实例。 通过消息类型（{@code type}）可以快速查找对应的处理器。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic final class JsonMessageHandlerHolder {\n\n\tprivate JsonMessageHandlerHolder() {\n\t}\n\n\tprivate static final Map<String, JsonMessageHandler> MESSAGE_HANDLER_MAP = new ConcurrentHashMap<>();\n\n\t/**\n\t * 根据消息类型获取对应的 JSON 消息处理器。\n\t * @param type 消息类型字符串。\n\t * @return 对应的 {@link JsonMessageHandler} 实例，如果不存在则返回 {@code null}。\n\t */\n\tpublic static JsonMessageHandler getHandler(String type) {\n\t\treturn MESSAGE_HANDLER_MAP.get(type);\n\t}\n\n\t/**\n\t * 添加一个 JSON 消息处理器到持有者中。\n\t * <p>\n\t * 处理器会根据其 {@link JsonMessageHandler#type()} 方法返回的类型进行注册。\n\t * </p>\n\t * @param jsonMessageHandler 待添加的 JSON 消息处理器实例。\n\t */\n\tpublic static void addHandler(JsonMessageHandler jsonMessageHandler) {\n\t\tMESSAGE_HANDLER_MAP.put(jsonMessageHandler.type(), jsonMessageHandler);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/MapSessionWebSocketHandlerDecorator.java",
    "content": "package com.pig4cloud.pig.common.websocket.holder;\n\nimport com.pig4cloud.pig.common.websocket.config.WebSocketProperties;\nimport org.springframework.web.socket.CloseStatus;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.WebSocketSession;\nimport org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;\nimport org.springframework.web.socket.handler.WebSocketHandlerDecorator;\n\n/**\n * WebSocketHandler 装饰器\n * <p>\n * 此装饰器主要用于在 WebSocket 连接建立和关闭时，对会话进行映射存储和释放。 它确保了会话的生命周期管理，并支持并发消息发送的控制。\n * </p>\n *\n * @author Hccake 2020/12/31\n * @version 1.0\n */\npublic class MapSessionWebSocketHandlerDecorator extends WebSocketHandlerDecorator {\n\n\tprivate final SessionKeyGenerator sessionKeyGenerator;\n\n\tprivate final WebSocketProperties webSocketProperties;\n\n\t/**\n\t * 构造一个新的 {@code MapSessionWebSocketHandlerDecorator} 实例。\n\t * @param delegate 被装饰的原始 {@link WebSocketHandler}。\n\t * @param sessionKeyGenerator 会话密钥生成器，用于生成会话的唯一标识。\n\t * @param webSocketProperties WebSocket 配置属性，包含发送时间限制和缓冲区大小限制。\n\t */\n\tpublic MapSessionWebSocketHandlerDecorator(WebSocketHandler delegate, SessionKeyGenerator sessionKeyGenerator,\n\t\t\tWebSocketProperties webSocketProperties) {\n\t\tsuper(delegate);\n\t\tthis.sessionKeyGenerator = sessionKeyGenerator;\n\t\tthis.webSocketProperties = webSocketProperties;\n\t}\n\n\t/**\n\t * 在 WebSocket 连接建立后执行的动作。\n\t * <p>\n\t * 此方法会生成会话的唯一标识，并将带有并发控制的 {@link WebSocketSession} 存储到 {@link WebSocketSessionHolder}\n\t * 中。 {@link ConcurrentWebSocketSessionDecorator}\n\t * 确保一次只有一个线程可以发送消息，并根据配置的缓冲区大小和发送时间限制管理会话。\n\t * </p>\n\t * @param session 建立的 WebSocket 会话对象。\n\t * @throws Exception 如果在处理过程中发生错误。\n\t */\n\t@Override\n\tpublic void afterConnectionEstablished(final WebSocketSession session) throws Exception {\n\t\tObject sessionKey = sessionKeyGenerator.sessionKey(session);\n\t\tWebSocketSessionHolder.addSession(sessionKey, new ConcurrentWebSocketSessionDecorator(session,\n\t\t\t\twebSocketProperties.getSendTimeLimit(), webSocketProperties.getSendBufferSizeLimit()));\n\t}\n\n\t/**\n\t * 在 WebSocket 连接关闭后执行的动作。\n\t * <p>\n\t * 此方法会根据会话的唯一标识从 {@link WebSocketSessionHolder} 中移除对应的会话。\n\t * </p>\n\t * @param session 关闭的 WebSocket 会话对象。\n\t * @param closeStatus 关闭状态对象，包含关闭原因和状态码。\n\t * @throws Exception 如果在处理过程中发生错误。\n\t */\n\t@Override\n\tpublic void afterConnectionClosed(final WebSocketSession session, CloseStatus closeStatus) throws Exception {\n\t\tObject sessionKey = sessionKeyGenerator.sessionKey(session);\n\t\tWebSocketSessionHolder.removeSession(sessionKey);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/SessionKeyGenerator.java",
    "content": "package com.pig4cloud.pig.common.websocket.holder;\n\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * WebSocketSession 唯一标识生成器接口\n * <p>\n * 定义了生成 WebSocket 会话唯一标识的契约。 不同的实现可以根据业务需求（如用户ID、会话ID等）生成唯一的会话标识。\n * </p>\n *\n * @author Hccake 2021/1/5\n * @version 1.0\n */\npublic interface SessionKeyGenerator {\n\n\t/**\n\t * 获取当前 WebSocket 会话的唯一标识。\n\t * @param webSocketSession 当前的 WebSocket 会话对象。\n\t * @return 返回会话的唯一标识对象。\n\t */\n\tObject sessionKey(WebSocketSession webSocketSession);\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/WebSocketSessionHolder.java",
    "content": "package com.pig4cloud.pig.common.websocket.holder;\n\nimport org.springframework.web.socket.WebSocketSession;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * WebSocketSession 持有者\n * <p>\n * 负责管理和存储所有在线的 WebSocket 会话信息。 提供添加、删除、获取会话以及获取所有会话和会话键的方法。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic final class WebSocketSessionHolder {\n\n\tprivate WebSocketSessionHolder() {\n\t}\n\n\tprivate static final Map<String, WebSocketSession> USER_SESSION_MAP = new ConcurrentHashMap<>();\n\n\t/**\n\t * 添加一个 WebSocket 会话。\n\t * @param sessionKey 会话的唯一标识。\n\t * @param session 待添加的 WebSocketSession 对象。\n\t */\n\tpublic static void addSession(Object sessionKey, WebSocketSession session) {\n\t\tUSER_SESSION_MAP.put(sessionKey.toString(), session);\n\t}\n\n\t/**\n\t * 删除一个 WebSocket 会话。\n\t * @param sessionKey 会话的唯一标识。\n\t */\n\tpublic static void removeSession(Object sessionKey) {\n\t\tUSER_SESSION_MAP.remove(sessionKey.toString());\n\t}\n\n\t/**\n\t * 获取指定标识的 WebSocket 会话。\n\t * @param sessionKey 会话的唯一标识。\n\t * @return 对应的 WebSocketSession 对象，如果不存在则返回 {@code null}。\n\t */\n\tpublic static WebSocketSession getSession(Object sessionKey) {\n\t\treturn USER_SESSION_MAP.get(sessionKey.toString());\n\t}\n\n\t/**\n\t * 获取当前所有在线的 WebSocket 会话集合。\n\t * @return 所有 WebSocketSession 对象的集合。\n\t */\n\tpublic static Collection<WebSocketSession> getSessions() {\n\t\treturn USER_SESSION_MAP.values();\n\t}\n\n\t/**\n\t * 获取所有在线 WebSocket 会话的唯一标识集合。\n\t * @return 所有会话唯一标识（字符串形式）的集合。\n\t */\n\tpublic static Set<String> getSessionKeys() {\n\t\treturn USER_SESSION_MAP.keySet();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/AbstractJsonWebSocketMessage.java",
    "content": "package com.pig4cloud.pig.common.websocket.message;\n\n/**\n * 抽象的 JSON WebSocket 消息基类\n * <p>\n * 提供了 JSON 格式 WebSocket 消息的通用结构，包含一个类型字段 {@code type}， 用于标识消息的具体类型，以便于消息分发和处理。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic abstract class AbstractJsonWebSocketMessage implements JsonWebSocketMessage {\n\n\t/**\n\t * 消息类型字段的名称。\n\t */\n\tpublic static final String TYPE_FIELD = \"type\";\n\n\tprivate final String type;\n\n\t/**\n\t * 构造函数，用于创建抽象 JSON WebSocket 消息实例。\n\t * @param type 消息的类型标识符。\n\t */\n\tprotected AbstractJsonWebSocketMessage(String type) {\n\t\tthis.type = type;\n\t}\n\n\t/**\n\t * 获取消息的类型。\n\t * @return 消息类型字符串。\n\t */\n\t@Override\n\tpublic String getType() {\n\t\treturn type;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/JsonWebSocketMessage.java",
    "content": "package com.pig4cloud.pig.common.websocket.message;\n\n/**\n * JSON WebSocket 消息接口\n * <p>\n * 定义了所有 JSON 格式 WebSocket 消息应遵循的契约， 强制实现类提供一个消息类型标识符。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic interface JsonWebSocketMessage {\n\n\t/**\n\t * 获取消息类型。\n\t * <p>\n\t * 消息类型主要用于匹配对应的消息处理器，实现消息的路由和分发。\n\t * </p>\n\t * @return 当前消息的类型字符串。\n\t */\n\tString getType();\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/PingJsonWebSocketMessage.java",
    "content": "package com.pig4cloud.pig.common.websocket.message;\n\n/**\n * Ping JSON WebSocket 消息\n * <p>\n * 表示一个 Ping 类型的 WebSocket 消息，通常用于客户端向服务器发送心跳请求。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic class PingJsonWebSocketMessage extends AbstractJsonWebSocketMessage {\n\n\t/**\n\t * 构造函数，创建一个 Ping 类型的 JSON WebSocket 消息。\n\t */\n\tpublic PingJsonWebSocketMessage() {\n\t\tsuper(WebSocketMessageTypeEnum.PING.getValue());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/PongJsonWebSocketMessage.java",
    "content": "package com.pig4cloud.pig.common.websocket.message;\n\n/**\n * Pong JSON WebSocket 消息\n * <p>\n * 表示一个 Pong 类型的 WebSocket 消息，通常用于服务器响应客户端的 Ping 请求，作为心跳机制的一部分。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\npublic class PongJsonWebSocketMessage extends AbstractJsonWebSocketMessage {\n\n\t/**\n\t * 构造函数，创建一个 Pong 类型的 JSON WebSocket 消息。\n\t */\n\tpublic PongJsonWebSocketMessage() {\n\t\tsuper(WebSocketMessageTypeEnum.PONG.getValue());\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/WebSocketMessageTypeEnum.java",
    "content": "package com.pig4cloud.pig.common.websocket.message;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * WebSocket 消息类型枚举\n * <p>\n * 定义了 WebSocket 消息的常见类型，例如 Ping 和 Pong，用于心跳机制。\n * </p>\n *\n * @author Hccake 2021/1/4\n * @version 1.0\n */\n@Getter\n@RequiredArgsConstructor\npublic enum WebSocketMessageTypeEnum {\n\n\t/**\n\t * Ping 消息类型，通常由客户端发送，用于心跳检测。\n\t */\n\tPING(\"ping\"),\n\n\t/**\n\t * Pong 消息类型，通常由服务器响应 Ping 消息发送，用于心跳检测。\n\t */\n\tPONG(\"pong\");\n\n\tprivate final String value;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/package-info.java",
    "content": "/**\n * 此包内代码 来源至 BallCat 关于BallCat: 组织旨在为项目快速开发提供一系列的基础能力，方便使用者根据项目需求快速进行功能拓展。\n * https://github.com/ballcat-projects/ballcat\n */\npackage com.pig4cloud.pig.common.websocket;\n"
  },
  {
    "path": "pig-common/pig-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.websocket.config.WebSocketAutoConfiguration\ncom.pig4cloud.pig.common.websocket.config.RedisMessageDistributorConfiguration\ncom.pig4cloud.pig.common.websocket.config.LocalMessageDistributorConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-xss/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-common</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common-xss</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pigx xss 安全过滤插件 基于 JSOUP</description>\n    <properties>\n        <jsoup.version>1.18.3</jsoup.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.jsoup</groupId>\n            <artifactId>jsoup</artifactId>\n            <version>${jsoup.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-context</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/PigXssAutoConfiguration.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss;\n\nimport com.pig4cloud.pig.common.xss.config.PigXssProperties;\nimport com.pig4cloud.pig.common.xss.core.*;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.core.Ordered;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport java.util.List;\n\n/**\n * Jackson XSS 自动配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@AutoConfiguration\n@RequiredArgsConstructor\n@EnableConfigurationProperties(PigXssProperties.class)\n@ConditionalOnProperty(prefix = PigXssProperties.PREFIX, name = \"enabled\", havingValue = \"true\", matchIfMissing = true)\n@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)\npublic class PigXssAutoConfiguration implements WebMvcConfigurer {\n\n\tprivate final PigXssProperties xssProperties;\n\n\t/**\n\t * 创建XSS清理器Bean\n\t * @param properties XSS配置属性\n\t * @return XSS清理器实例\n\t * @see DefaultXssCleaner\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic XssCleaner xssCleaner(PigXssProperties properties) {\n\t\treturn new DefaultXssCleaner(properties);\n\t}\n\n\t/**\n\t * 创建FormXssClean实例\n\t * @param properties PigXss配置属性\n\t * @param xssCleaner XSS清理器\n\t * @return FormXssClean实例\n\t */\n\t@Bean\n\tpublic FormXssClean formXssClean(PigXssProperties properties, XssCleaner xssCleaner) {\n\t\treturn new FormXssClean(properties, xssCleaner);\n\t}\n\n\t/**\n\t * 创建Jackson2ObjectMapperBuilderCustomizer Bean，用于XSS防护\n\t * @param properties XSS配置属性\n\t * @param xssCleaner XSS清理器\n\t * @return 自定义的Jackson2ObjectMapperBuilder\n\t */\n\t@Bean\n\tpublic Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(PigXssProperties properties,\n\t\t\tXssCleaner xssCleaner) {\n\t\treturn builder -> builder.deserializerByType(String.class, new JacksonXssClean(properties, xssCleaner));\n\t}\n\n\t/**\n\t * 添加XSS拦截器\n\t * @param registry 拦截器注册器\n\t */\n\t@Override\n\tpublic void addInterceptors(InterceptorRegistry registry) {\n\t\tList<String> patterns = xssProperties.getPathPatterns();\n\t\tif (patterns.isEmpty()) {\n\t\t\tpatterns.add(\"/**\");\n\t\t}\n\t\tXssCleanInterceptor interceptor = new XssCleanInterceptor(xssProperties);\n\t\tregistry.addInterceptor(interceptor)\n\t\t\t.addPathPatterns(patterns)\n\t\t\t.excludePathPatterns(xssProperties.getPathExcludePatterns())\n\t\t\t.order(Ordered.LOWEST_PRECEDENCE);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/config/PigXssProperties.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.config;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * XSS 防护配置属性类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\n@Setter\n@RefreshScope\n@ConfigurationProperties(PigXssProperties.PREFIX)\npublic class PigXssProperties {\n\n\tpublic static final String PREFIX = \"security.xss\";\n\n\t/**\n\t * 开启xss\n\t */\n\tprivate boolean enabled = true;\n\n\t/**\n\t * 全局：对文件进行首尾 trim\n\t */\n\tprivate boolean trimText = true;\n\n\t/**\n\t * 模式：clear 清理（默认），escape 转义\n\t */\n\tprivate Mode mode = Mode.clear;\n\n\t/**\n\t * [clear 专用] prettyPrint，默认关闭： 保留换行\n\t */\n\tprivate boolean prettyPrint = false;\n\n\t/**\n\t * [clear 专用] 使用转义，默认关闭\n\t */\n\tprivate boolean enableEscape = false;\n\n\t/**\n\t * 拦截的路由，默认为空\n\t */\n\tprivate List<String> pathPatterns = new ArrayList<>();\n\n\t/**\n\t * 放行的路由，默认为空\n\t */\n\tprivate List<String> pathExcludePatterns = new ArrayList<>();\n\n\tpublic enum Mode {\n\n\t\t/**\n\t\t * 清理\n\t\t */\n\t\tclear,\n\t\t/**\n\t\t * 转义\n\t\t */\n\t\tescape,\n\t\t/**\n\t\t * 校验，抛出异常\n\t\t */\n\t\tvalidate;\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/config/package-info.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.config;\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/DefaultXssCleaner.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport cn.hutool.core.util.CharsetUtil;\nimport com.pig4cloud.pig.common.xss.config.PigXssProperties;\nimport com.pig4cloud.pig.common.xss.utils.XssUtil;\nimport org.jsoup.Jsoup;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Entities;\nimport org.springframework.web.util.HtmlUtils;\n\n/**\n * 默认的XSS清理器实现类，提供HTML内容的安全清理功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class DefaultXssCleaner implements XssCleaner {\n\n\tprivate final PigXssProperties properties;\n\n\tpublic DefaultXssCleaner(PigXssProperties properties) {\n\t\tthis.properties = properties;\n\t}\n\n\t/**\n\t * 获取文档输出设置\n\t * @param properties PigXss配置属性\n\t * @return 文档输出设置对象\n\t */\n\tprivate static Document.OutputSettings getOutputSettings(PigXssProperties properties) {\n\t\treturn new Document.OutputSettings()\n\t\t\t// 2. 转义，没找到关闭的方法，目前这个规则最少\n\t\t\t.escapeMode(Entities.EscapeMode.xhtml)\n\t\t\t// 3. 保留换行\n\t\t\t.prettyPrint(properties.isPrettyPrint());\n\t}\n\n\t/**\n\t * 清理HTML内容，根据XSS类型和模式进行处理\n\t * @param bodyHtml 待清理的HTML内容\n\t * @param type XSS处理类型\n\t * @return 清理后的HTML内容\n\t * @throws XssException 当模式为validate且内容不合法时抛出异常\n\t */\n\t@Override\n\tpublic String clean(String bodyHtml, XssType type) {\n\t\t// 1. 为空直接返回\n\t\tif (StringUtil.isBlank(bodyHtml)) {\n\t\t\treturn bodyHtml;\n\t\t}\n\t\tPigXssProperties.Mode mode = properties.getMode();\n\t\tif (PigXssProperties.Mode.escape == mode) {\n\t\t\t// html 转义\n\t\t\treturn HtmlUtils.htmlEscape(bodyHtml, CharsetUtil.UTF_8);\n\t\t}\n\t\telse if (PigXssProperties.Mode.validate == mode) {\n\t\t\t// 校验\n\t\t\tif (Jsoup.isValid(bodyHtml, XssUtil.WHITE_LIST)) {\n\t\t\t\treturn bodyHtml;\n\t\t\t}\n\t\t\tthrow type.getXssException(bodyHtml, \"Xss validate fail, input value:\" + bodyHtml);\n\t\t}\n\t\telse {\n\t\t\t// 4. 清理后的 html\n\t\t\tString escapedHtml = Jsoup.clean(bodyHtml, \"\", XssUtil.WHITE_LIST, getOutputSettings(properties));\n\t\t\tif (properties.isEnableEscape()) {\n\t\t\t\treturn escapedHtml;\n\t\t\t}\n\t\t\t// 5. 反转义\n\t\t\treturn Entities.unescape(escapedHtml);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/FormXssClean.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.common.xss.config.PigXssProperties;\nimport com.pig4cloud.pig.common.xss.utils.XssUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.web.bind.WebDataBinder;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.InitBinder;\n\nimport java.beans.PropertyEditorSupport;\n\n/**\n * 表单XSS清理控制器\n * <p>\n * 用于处理前端表单提交的XSS清理工作\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@ControllerAdvice\n@ConditionalOnProperty(prefix = PigXssProperties.PREFIX, name = \"enabled\", havingValue = \"true\", matchIfMissing = true)\n@RequiredArgsConstructor\npublic class FormXssClean {\n\n\tprivate final PigXssProperties properties;\n\n\tprivate final XssCleaner xssCleaner;\n\n\t/**\n\t * 初始化数据绑定器，注册自定义属性编辑器\n\t * @param binder 数据绑定器\n\t */\n\t@InitBinder\n\tpublic void initBinder(WebDataBinder binder) {\n\t\t// 处理前端传来的表单字符串\n\t\tbinder.registerCustomEditor(String.class, new StringPropertiesEditor(xssCleaner, properties));\n\t}\n\n\t/**\n\t * 字符串属性编辑器，用于处理XSS防护的字符串属性编辑\n\t *\n\t * @author lengleng\n\t * @date 2025/05/31\n\t */\n\t@Slf4j\n\t@RequiredArgsConstructor\n\tpublic static class StringPropertiesEditor extends PropertyEditorSupport {\n\n\t\tprivate final XssCleaner xssCleaner;\n\n\t\tprivate final PigXssProperties properties;\n\n\t\t/**\n\t\t * 获取属性值的文本表示\n\t\t * @return 属性值的字符串表示，如果值为null则返回空字符串\n\t\t */\n\t\t@Override\n\t\tpublic String getAsText() {\n\t\t\tObject value = getValue();\n\t\t\treturn value != null ? value.toString() : StrUtil.EMPTY;\n\t\t}\n\n\t\t/**\n\t\t * 设置文本值，根据XSS防护状态进行相应处理\n\t\t * @param text 要设置的文本\n\t\t * @throws IllegalArgumentException 如果参数不合法时抛出异常\n\t\t */\n\t\t@Override\n\t\tpublic void setAsText(String text) throws IllegalArgumentException {\n\t\t\tif (text == null) {\n\t\t\t\tsetValue(null);\n\t\t\t}\n\t\t\telse if (XssHolder.isEnabled()) {\n\t\t\t\tString value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText()));\n\t\t\t\tsetValue(value);\n\t\t\t\tlog.debug(\"Request parameter value:{} cleaned up by mica-xss, current value is:{}.\", text, value);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsetValue(XssUtil.trim(text, properties.isTrimText()));\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/FromXssException.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport java.io.Serial;\n\nimport lombok.Getter;\n\n/**\n * XSS 表单异常类，用于处理表单相关的 XSS 异常情况\n *\n * @author lengleng\n * @date 2025/05/31\n * @see IllegalStateException\n * @see XssException\n */\n@Getter\npublic class FromXssException extends IllegalStateException implements XssException {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tprivate final String input;\n\n\t/**\n\t * 构造FromXssException异常\n\t * @param input 引发异常的输入内容\n\t * @param message 异常信息\n\t */\n\tpublic FromXssException(String input, String message) {\n\t\tsuper(message);\n\t\tthis.input = input;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/JacksonXssClean.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport com.pig4cloud.pig.common.xss.config.PigXssProperties;\nimport com.pig4cloud.pig.common.xss.utils.XssUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.util.Objects;\n\n/**\n * Jackson XSS 处理类，用于清理JSON数据中的XSS风险内容\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class JacksonXssClean extends XssCleanDeserializerBase {\n\n\tprivate final PigXssProperties properties;\n\n\tprivate final XssCleaner xssCleaner;\n\n\t/**\n\t * 清理文本内容，根据XSS防护设置进行处理\n\t * @param name 属性名称\n\t * @param text 待清理的文本\n\t * @return 清理后的文本\n\t * @throws IOException 如果清理过程中发生IO异常\n\t */\n\t@Override\n\tpublic String clean(String name, String text) throws IOException {\n\t\tif (XssHolder.isEnabled() && Objects.isNull(XssHolder.getXssCleanIgnore())) {\n\t\t\tString value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText()));\n\t\t\tlog.debug(\"Json property value:{} cleaned up by mica-xss, current value is:{}.\", text, value);\n\t\t\treturn value;\n\t\t}\n\t\telse if (XssHolder.isEnabled() && Objects.nonNull(XssHolder.getXssCleanIgnore())) {\n\t\t\tXssCleanIgnore xssCleanIgnore = XssHolder.getXssCleanIgnore();\n\t\t\tif (ArrayUtil.contains(xssCleanIgnore.value(), name)) {\n\t\t\t\treturn XssUtil.trim(text, properties.isTrimText());\n\t\t\t}\n\n\t\t\tString value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText()));\n\t\t\tlog.debug(\"Json property value:{} cleaned up by mica-xss, current value is:{}.\", text, value);\n\t\t\treturn value;\n\t\t}\n\t\telse {\n\t\t\treturn XssUtil.trim(text, properties.isTrimText());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/JacksonXssException.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport lombok.Getter;\n\nimport java.io.IOException;\nimport java.io.Serial;\n\n/**\n * Jackson XSS 异常类，用于处理 JSON 序列化/反序列化过程中的 XSS 安全问题\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\npublic class JacksonXssException extends IOException implements XssException {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tprivate final String input;\n\n\t/**\n\t * 构造JacksonXssException异常\n\t * @param input 引发异常的输入内容\n\t * @param message 异常信息\n\t */\n\tpublic JacksonXssException(String input, String message) {\n\t\tsuper(message);\n\t\tthis.input = input;\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanDeserializer.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.xss.config.PigXssProperties;\nimport com.pig4cloud.pig.common.xss.utils.XssUtil;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\n\n/**\n * Jackson XSS 处理反序列化器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\npublic class XssCleanDeserializer extends XssCleanDeserializerBase {\n\n\t/**\n\t * 清理文本中的XSS内容\n\t * @param name 名称\n\t * @param text 待清理的文本\n\t * @return 清理后的文本\n\t * @throws IOException IO异常\n\t */\n\t@Override\n\tpublic String clean(String name, String text) throws IOException {\n\t\t// 读取 xss 配置\n\t\tPigXssProperties properties = SpringContextHolder.getBean(PigXssProperties.class);\n\t\t// 读取 XssCleaner bean\n\t\tXssCleaner xssCleaner = SpringContextHolder.getBean(XssCleaner.class);\n\t\tif (xssCleaner != null) {\n\t\t\tString value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText()));\n\t\t\tlog.debug(\"Json property value:{} cleaned up by mica-xss, current value is:{}.\", text, value);\n\t\t\treturn value;\n\t\t}\n\t\telse {\n\t\t\treturn XssUtil.trim(text, properties.isTrimText());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanDeserializerBase.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.core.JsonToken;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.exc.MismatchedInputException;\n\nimport java.io.IOException;\n\n/**\n * Jackson XSS 处理基类\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic abstract class XssCleanDeserializerBase extends JsonDeserializer<String> {\n\n\t/**\n\t * 反序列化方法，用于处理JSON字符串的反序列化并进行XSS清洗\n\t * @param p JSON解析器\n\t * @param ctx 反序列化上下文\n\t * @return 经过XSS清洗后的字符串，如果输入为null则返回null\n\t * @throws IOException 当反序列化过程中发生I/O错误时抛出\n\t * @throws MismatchedInputException 当当前JSON令牌不是VALUE_STRING时抛出\n\t */\n\t@Override\n\tpublic String deserialize(JsonParser p, DeserializationContext ctx) throws IOException {\n\t\tJsonToken jsonToken = p.getCurrentToken();\n\t\tif (JsonToken.VALUE_STRING != jsonToken) {\n\t\t\tthrow MismatchedInputException.from(p, String.class,\n\t\t\t\t\t\"mica-xss: can't deserialize value of type java.lang.String from \" + jsonToken);\n\t\t}\n\t\t// 解析字符串\n\t\tString text = p.getValueAsString();\n\t\tif (text == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// xss 配置\n\t\treturn this.clean(p.getParsingContext().getCurrentName(), text);\n\t}\n\n\t/**\n\t * 清理文本中的XSS攻击内容\n\t * @param name 文本名称标识\n\t * @param text 待清理的文本内容\n\t * @return 清理后的安全文本\n\t * @throws IOException 清理过程中发生IO异常时抛出\n\t */\n\tpublic abstract String clean(String name, String text) throws IOException;\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanIgnore.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport java.lang.annotation.*;\n\n/**\n * 忽略 xss\n *\n * @author L.cm\n */\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface XssCleanIgnore {\n\n\t/**\n\t * @return 需要跳过的字段列表\n\t */\n\tString[] value() default {};\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanInterceptor.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport com.pig4cloud.pig.common.xss.config.PigXssProperties;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.AsyncHandlerInterceptor;\n\n/**\n * xss 处理拦截器\n *\n * @author L.cm\n */\n@RequiredArgsConstructor\npublic class XssCleanInterceptor implements AsyncHandlerInterceptor {\n\n\t/**\n\t * XSS防护配置属性\n\t */\n\tprivate final PigXssProperties xssProperties;\n\n\t/**\n\t * 预处理XSS过滤\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param handler 处理器对象\n\t * @return 总是返回true\n\t * @throws Exception 处理过程中可能抛出的异常\n\t */\n\t@Override\n\tpublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)\n\t\t\tthrows Exception {\n\t\t// 1. 非控制器请求直接跳出\n\t\tif (!(handler instanceof HandlerMethod)) {\n\t\t\treturn true;\n\t\t}\n\t\t// 2. 没有开启\n\t\tif (!xssProperties.isEnabled()) {\n\t\t\treturn true;\n\t\t}\n\t\t// 3. 处理 XssIgnore 注解\n\t\tHandlerMethod handlerMethod = (HandlerMethod) handler;\n\t\tXssCleanIgnore xssCleanIgnore = AnnotationUtils.getAnnotation(handlerMethod.getMethod(), XssCleanIgnore.class);\n\t\tif (xssCleanIgnore == null) {\n\t\t\tXssHolder.setEnable();\n\t\t}\n\t\telse if (ArrayUtil.isNotEmpty(xssCleanIgnore.value())) {\n\t\t\tXssHolder.setEnable();\n\t\t\tXssHolder.setXssCleanIgnore(xssCleanIgnore);\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 请求完成后清理XSS过滤器持有的线程局部变量\n\t * @param request HTTP请求对象\n\t * @param response HTTP响应对象\n\t * @param handler 处理请求的处理器\n\t * @param ex 处理过程中抛出的异常\n\t */\n\t@Override\n\tpublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)\n\t\t\tthrows Exception {\n\t\tXssHolder.remove();\n\t}\n\n\t/**\n\t * 在并发处理开始后清除XSSHolder中的内容\n\t * @param request HTTP请求对象\n\t * @param response HTTP响应对象\n\t * @param handler 处理器对象\n\t * @throws Exception 可能抛出的异常\n\t */\n\t@Override\n\tpublic void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)\n\t\t\tthrows Exception {\n\t\tXssHolder.remove();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleaner.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\nimport com.pig4cloud.pig.common.xss.utils.XssUtil;\nimport org.jsoup.Jsoup;\n\n/**\n * xss 清理器\n *\n * @author L.cm\n */\npublic interface XssCleaner {\n\n\t/**\n\t * 清理 html\n\t * @param html html\n\t * @return 清理后的数据\n\t */\n\tdefault String clean(String html) {\n\t\treturn clean(html, XssType.FORM);\n\t}\n\n\t/**\n\t * 清理 html\n\t * @param html html\n\t * @param type XssType\n\t * @return 清理后的数据\n\t */\n\tString clean(String html, XssType type);\n\n\t/**\n\t * 判断输入是否安全\n\t * @param html html\n\t * @return 是否安全\n\t */\n\tdefault boolean isValid(String html) {\n\t\treturn Jsoup.isValid(html, XssUtil.WHITE_LIST);\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssException.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\n/**\n * xss 异常，校验模式抛出\n *\n * @author L.cm\n */\npublic interface XssException {\n\n\t/**\n\t * 输入的数据\n\t * @return 数据\n\t */\n\tString getInput();\n\n\t/**\n\t * 获取异常的消息\n\t * @return 消息\n\t */\n\tString getMessage();\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssHolder.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\n/**\n * 利用 ThreadLocal 缓存线程间的数据\n *\n * @author L.cm\n */\npublic class XssHolder {\n\n\tprivate static final ThreadLocal<Boolean> TL = new ThreadLocal<>();\n\n\tprivate static final ThreadLocal<XssCleanIgnore> TL_IGNORE = new ThreadLocal<>();\n\n\t/**\n\t * 是否开启\n\t * @return boolean\n\t */\n\tpublic static boolean isEnabled() {\n\t\treturn Boolean.TRUE.equals(TL.get());\n\t}\n\n\t/**\n\t * 标记为开启\n\t */\n\tstatic void setEnable() {\n\t\tTL.set(Boolean.TRUE);\n\t}\n\n\t/**\n\t * 保存接口上的 XssCleanIgnore\n\t * @param xssCleanIgnore XssCleanIgnore\n\t */\n\tpublic static void setXssCleanIgnore(XssCleanIgnore xssCleanIgnore) {\n\t\tTL_IGNORE.set(xssCleanIgnore);\n\t}\n\n\t/**\n\t * 获取接口上的 XssCleanIgnore\n\t * @return XssCleanIgnore\n\t */\n\tpublic static XssCleanIgnore getXssCleanIgnore() {\n\t\treturn TL_IGNORE.get();\n\t}\n\n\t/**\n\t * 关闭 xss 清理\n\t */\n\tpublic static void remove() {\n\t\tTL.remove();\n\t\tTL_IGNORE.remove();\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssType.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.core;\n\n/**\n * xss 数据处理类型\n */\npublic enum XssType {\n\n\t/**\n\t * 表单\n\t */\n\tFORM() {\n\t\t@Override\n\t\tpublic RuntimeException getXssException(String input, String message) {\n\t\t\treturn new FromXssException(input, message);\n\t\t}\n\t},\n\n\t/**\n\t * body json\n\t */\n\tJACKSON() {\n\t\t@Override\n\t\tpublic RuntimeException getXssException(String input, String message) {\n\t\t\treturn new RuntimeException(message);\n\t\t}\n\t};\n\n\t/**\n\t * 获取 xss 异常\n\t * @param input input\n\t * @param message message\n\t * @return XssException\n\t */\n\tpublic abstract RuntimeException getXssException(String input, String message);\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/package-info.java",
    "content": "/**\n * 此包代码来源至: https://gitee.com/596392912/mica/tree/master/mica-xss\n */\npackage com.pig4cloud.pig.common.xss;\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/utils/XssUtil.java",
    "content": "/*\n * Copyright (c) 2016-2020, Michael Yang 杨福海 (fuhai999@gmail.com).\n * <p>\n * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl-3.0.txt\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.utils;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Element;\nimport org.springframework.util.StringUtils;\n\nimport cn.hutool.core.util.StrUtil;\n\n/**\n * xss clean\n *\n * <p>\n * 参考自 jpress：https://gitee.com/fuhai/jpress\n * </p>\n *\n * @author L.cm\n * @author michael\n */\npublic class XssUtil {\n\n\tpublic static final HtmlSafeList WHITE_LIST = HtmlSafeList.INSTANCE;\n\n\t/**\n\t * trim 字符串\n\t * @param text text\n\t * @return 清理后的 text\n\t */\n\tpublic static String trim(String text, boolean trim) {\n\t\treturn trim ? StrUtil.trim(text) : text;\n\t}\n\n\t/**\n\t * xss 清理\n\t * @param html html\n\t * @return 清理后的 html\n\t */\n\tpublic static String clean(String html) {\n\t\tif (StringUtils.hasText(html)) {\n\t\t\treturn Jsoup.clean(html, WHITE_LIST);\n\t\t}\n\t\treturn html;\n\t}\n\n\t/**\n\t * 做自己的白名单，允许base64的图片通过等\n\t *\n\t * @author michael\n\t */\n\tpublic static class HtmlSafeList extends org.jsoup.safety.Safelist {\n\n\t\tpublic static final HtmlSafeList INSTANCE = new HtmlSafeList();\n\n\t\tpublic HtmlSafeList() {\n\t\t\taddTags(\"a\", \"b\", \"blockquote\", \"br\", \"caption\", \"cite\", \"code\", \"col\", \"colgroup\", \"dd\", \"div\", \"span\",\n\t\t\t\t\t\"embed\", \"object\", \"dl\", \"dt\", \"em\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"i\", \"img\", \"li\", \"ol\",\n\t\t\t\t\t\"p\", \"pre\", \"q\", \"small\", \"strike\", \"strong\", \"sub\", \"sup\", \"table\", \"tbody\", \"td\", \"tfoot\", \"th\",\n\t\t\t\t\t\"thead\", \"tr\", \"u\", \"ul\");\n\n\t\t\taddAttributes(\"a\", \"href\", \"title\", \"target\");\n\t\t\taddAttributes(\"blockquote\", \"cite\");\n\t\t\taddAttributes(\"col\", \"span\");\n\t\t\taddAttributes(\"colgroup\", \"span\");\n\t\t\taddAttributes(\"img\", \"align\", \"alt\", \"src\", \"title\");\n\t\t\taddAttributes(\"ol\", \"start\");\n\t\t\taddAttributes(\"q\", \"cite\");\n\t\t\taddAttributes(\"table\", \"summary\");\n\t\t\taddAttributes(\"td\", \"abbr\", \"axis\", \"colspan\", \"rowspan\", \"width\");\n\t\t\taddAttributes(\"th\", \"abbr\", \"axis\", \"colspan\", \"rowspan\", \"scope\", \"width\");\n\t\t\taddAttributes(\"video\", \"src\", \"autoplay\", \"controls\", \"loop\", \"muted\", \"poster\", \"preload\");\n\t\t\taddAttributes(\"object\", \"width\", \"height\", \"classid\", \"codebase\");\n\t\t\taddAttributes(\"param\", \"name\", \"value\");\n\t\t\taddAttributes(\"embed\", \"src\", \"quality\", \"width\", \"height\", \"allowFullScreen\", \"allowScriptAccess\",\n\t\t\t\t\t\"flashvars\", \"name\", \"type\", \"pluginspage\");\n\n\t\t\taddAttributes(\":all\", \"class\", \"style\", \"height\", \"width\", \"type\", \"id\", \"name\");\n\n\t\t\taddProtocols(\"blockquote\", \"cite\", \"http\", \"https\");\n\t\t\taddProtocols(\"cite\", \"cite\", \"http\", \"https\");\n\t\t\taddProtocols(\"q\", \"cite\", \"http\", \"https\");\n\n\t\t\t// 如果添加以下的协议，那么href 必须是http、 https 等开头，相对路径则被过滤掉了\n\t\t\t// addProtocols(\"a\", \"href\", \"ftp\", \"http\", \"https\", \"mailto\", \"tel\");\n\n\t\t\t// 如果添加以下的协议，那么src必须是http 或者 https 开头，相对路径则被过滤掉了，\n\t\t\t// 所以必须注释掉，允许相对路径的图片资源\n\t\t\t// addProtocols(\"img\", \"src\", \"http\", \"https\");\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isSafeAttribute(String tagName, Element el, Attribute attr) {\n\t\t\t// 不允许 javascript 开头的 src 和 href\n\t\t\tif (\"src\".equalsIgnoreCase(attr.getKey()) || \"href\".equalsIgnoreCase(attr.getKey())) {\n\t\t\t\tString value = attr.getValue();\n\t\t\t\tif (StringUtils.hasText(value) && value.toLowerCase().startsWith(\"javascript\")) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 允许 base64 的图片内容\n\t\t\tif (\"img\".equals(tagName) && \"src\".equals(attr.getKey()) && attr.getValue().startsWith(\"data:;base64\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn super.isSafeAttribute(tagName, el, attr);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/utils/package-info.java",
    "content": "/*\n * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).\n * <p>\n * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.gnu.org/licenses/lgpl.html\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.common.xss.utils;\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.pig4cloud.pig.common.xss.PigXssAutoConfiguration\n"
  },
  {
    "path": "pig-common/pig-common-xss/src/main/resources/META-INF/spring-configuration-metadata.json",
    "content": "{\n  \"groups\": [\n    {\n      \"name\": \"security.xss\",\n      \"type\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\"\n    }\n  ],\n  \"properties\": [\n    {\n      \"name\": \"security.xss.enable-escape\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"[clear 专用] 使用转义，默认关闭\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\",\n      \"defaultValue\": false\n    },\n    {\n      \"name\": \"security.xss.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"开启xss\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\",\n      \"defaultValue\": true\n    },\n    {\n      \"name\": \"security.xss.mode\",\n      \"type\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties$Mode\",\n      \"description\": \"模式：clear 清理（默认），escape 转义\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\"\n    },\n    {\n      \"name\": \"security.xss.path-exclude-patterns\",\n      \"type\": \"java.util.List<java.lang.String>\",\n      \"description\": \"放行的路由，默认为空\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\"\n    },\n    {\n      \"name\": \"security.xss.path-patterns\",\n      \"type\": \"java.util.List<java.lang.String>\",\n      \"description\": \"拦截的路由，默认为空\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\"\n    },\n    {\n      \"name\": \"security.xss.pretty-print\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"[clear 专用] prettyPrint，默认关闭： 保留换行\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\",\n      \"defaultValue\": false\n    },\n    {\n      \"name\": \"security.xss.trim-text\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"全局：对文件进行首尾 trim\",\n      \"sourceType\": \"com.pig4cloud.pig.common.xss.config.PigXssProperties\",\n      \"defaultValue\": true\n    }\n  ],\n  \"hints\": []\n}"
  },
  {
    "path": "pig-common/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-common</artifactId>\n    <packaging>pom</packaging>\n\n    <description>pig 公共聚合模块</description>\n\n    <modules>\n        <module>pig-common-bom</module>\n        <module>pig-common-core</module>\n        <module>pig-common-datasource</module>\n        <module>pig-common-log</module>\n        <module>pig-common-mybatis</module>\n        <module>pig-common-oss</module>\n        <module>pig-common-seata</module>\n        <module>pig-common-security</module>\n        <module>pig-common-feign</module>\n        <module>pig-common-swagger</module>\n        <module>pig-common-websocket</module>\n        <module>pig-common-xss</module>\n        <module>pig-common-excel</module>\n    </modules>\n</project>\n"
  },
  {
    "path": "pig-gateway/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-gateway\n\nARG JAR_FILE=target/pig-gateway.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 9999\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-gateway/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. ~ ~ Licensed \n\tunder the Apache License, Version 2.0 (the \"License\"); ~ you may not use \n\tthis file except in compliance with the License. ~ You may obtain a copy \n\tof the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless \n\trequired by applicable law or agreed to in writing, software ~ distributed \n\tunder the License is distributed on an \"AS IS\" BASIS, ~ WITHOUT WARRANTIES \n\tOR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for \n\tthe specific language governing permissions and ~ limitations under the License. -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txmlns=\"http://maven.apache.org/POM/4.0.0\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>com.pig4cloud</groupId>\n\t\t<artifactId>pig</artifactId>\n\t\t<version>${revision}</version>\n\t</parent>\n\n\t<artifactId>pig-gateway</artifactId>\n\t<packaging>jar</packaging>\n\n\t<description>pig 服务网关，基于 spring cloud gateway</description>\n\n\t<dependencies>\n\t\t<!--gateway 网关依赖,内置webflux 依赖 -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-data-redis-reactive</artifactId>\n\t\t</dependency>\n\t\t<!--注册中心客户端 -->\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n\t\t</dependency>\n\t\t<!--配置中心客户端 -->\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n\t\t</dependency>\n\t\t<!-- LB 扩展 -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-loadbalancer</artifactId>\n\t\t</dependency>\n\t\t<!--caffeine 替换LB 默认缓存实现 -->\n\t\t<dependency>\n\t\t\t<groupId>com.github.ben-manes.caffeine</groupId>\n\t\t\t<artifactId>caffeine</artifactId>\n\t\t</dependency>\n\t\t<!-- 工具包依赖 -->\n\t\t<dependency>\n\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t<artifactId>pig-common-core</artifactId>\n\t\t</dependency>\n\t\t<!--接口文档 -->\n\t\t<dependency>\n\t\t\t<groupId>org.springdoc</groupId>\n\t\t\t<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>\n\t\t</dependency>\n\t\t<!--引入Knife4j的官方ui包 -->\n\t\t<dependency>\n\t\t\t<groupId>com.github.xiaoymin</groupId>\n\t\t\t<artifactId>knife4j-openapi3-ui</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>cn.hutool</groupId>\n\t\t\t<artifactId>hutool-crypto</artifactId>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t\t<artifactId>docker-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "pig-gateway/src/main/java/com/pig4cloud/pig/gateway/PigGatewayApplication.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.gateway;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n/**\n * 网关应用\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class PigGatewayApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigGatewayApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/GatewayConfiguration.java",
    "content": "package com.pig4cloud.pig.gateway.config;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.gateway.filter.PigRequestGlobalFilter;\nimport com.pig4cloud.pig.gateway.handler.GlobalExceptionHandler;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 网关配置类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Configuration(proxyBeanMethods = false)\npublic class GatewayConfiguration {\n\n\t/**\n\t * 创建PigRequest全局过滤器\n\t * @return PigRequest全局过滤器\n\t */\n\t@Bean\n\tpublic PigRequestGlobalFilter pigRequestGlobalFilter() {\n\t\treturn new PigRequestGlobalFilter();\n\t}\n\n\t/**\n\t * 创建全局异常处理程序\n\t * @param objectMapper 对象映射器\n\t * @return 全局异常处理程序\n\t */\n\t@Bean\n\tpublic GlobalExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) {\n\t\treturn new GlobalExceptionHandler(objectMapper);\n\t}\n\n}\n"
  },
  {
    "path": "pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/RateLimiterConfiguration.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.gateway.config;\n\nimport org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport reactor.core.publisher.Mono;\n\nimport java.util.Objects;\n\n/**\n * 路由限流配置类\n *\n * @author lengleng\n * @date 2019/2/1\n */\n@Configuration(proxyBeanMethods = false)\npublic class RateLimiterConfiguration {\n\n\t/**\n\t * 创建基于远程地址的KeyResolver实例\n\t * @return 根据请求的远程地址生成限流key的KeyResolver\n\t * @see <a href=\n\t * \"https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-requestratelimiter-gatewayfilter-factory\">Spring\n\t * Cloud Gateway文档</a>\n\t */\n\t@Bean\n\tpublic KeyResolver remoteAddrKeyResolver() {\n\t\treturn exchange -> Mono\n\t\t\t.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress());\n\t}\n\n}\n"
  },
  {
    "path": "pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/SpringDocConfiguration.java",
    "content": "package com.pig4cloud.pig.gateway.config;\n\nimport com.alibaba.nacos.client.naming.event.InstancesChangeEvent;\nimport com.alibaba.nacos.common.notify.Event;\nimport com.alibaba.nacos.common.notify.NotifyCenter;\nimport com.alibaba.nacos.common.notify.listener.Subscriber;\nimport com.alibaba.nacos.common.utils.StringUtils;\nimport lombok.RequiredArgsConstructor;\nimport org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;\nimport org.springdoc.core.properties.SwaggerUiConfigProperties;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.cloud.client.discovery.DiscoveryClient;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * SpringDoc配置类，实现InitializingBean接口，用于Swagger 3.0文档展示\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RequiredArgsConstructor\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(value = \"springdoc.api-docs.enabled\", matchIfMissing = true)\npublic class SpringDocConfiguration implements InitializingBean {\n\n\tprivate final SwaggerUiConfigProperties swaggerUiConfigProperties;\n\n\tprivate final DiscoveryClient discoveryClient;\n\n\t/**\n\t * 在初始化后调用的方法，用于注册SwaggerDocRegister订阅器\n\t */\n\t@Override\n\tpublic void afterPropertiesSet() {\n\t\tNotifyCenter.registerSubscriber(new SwaggerDocRegister(swaggerUiConfigProperties, discoveryClient));\n\t}\n\n}\n\n/**\n * Swagger文档注册器，用于处理服务实例变更事件并更新Swagger UI配置\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RequiredArgsConstructor\nclass SwaggerDocRegister extends Subscriber<InstancesChangeEvent> {\n\n\tprivate final SwaggerUiConfigProperties swaggerUiConfigProperties;\n\n\tprivate final DiscoveryClient discoveryClient;\n\n\t/**\n\t * 处理服务实例变更事件\n\t * @param event 服务实例变更事件对象\n\t */\n\t@Override\n\tpublic void onEvent(InstancesChangeEvent event) {\n\t\tSet<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrlSet = discoveryClient.getServices()\n\t\t\t.stream()\n\t\t\t.flatMap(serviceId -> discoveryClient.getInstances(serviceId).stream())\n\t\t\t.filter(instance -> StringUtils.isNotBlank(instance.getMetadata().get(\"spring-doc\")))\n\t\t\t.map(instance -> {\n\t\t\t\tAbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();\n\t\t\t\tswaggerUrl.setName(instance.getServiceId());\n\t\t\t\tswaggerUrl.setUrl(String.format(\"/%s/v3/api-docs\", instance.getMetadata().get(\"spring-doc\")));\n\t\t\t\treturn swaggerUrl;\n\t\t\t})\n\t\t\t.collect(Collectors.toSet());\n\n\t\tswaggerUiConfigProperties.setUrls(swaggerUrlSet);\n\t}\n\n\t/**\n\t * 订阅类型方法，返回订阅的事件类型\n\t * @return 订阅的事件类型\n\t */\n\t@Override\n\tpublic Class<? extends Event> subscribeType() {\n\t\treturn InstancesChangeEvent.class;\n\t}\n\n}\n"
  },
  {
    "path": "pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/PigRequestGlobalFilter.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.gateway.filter;\n\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport org.springframework.cloud.gateway.filter.GatewayFilterChain;\nimport org.springframework.cloud.gateway.filter.GlobalFilter;\nimport org.springframework.core.Ordered;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.stream.Collectors;\n\nimport static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;\nimport static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;\n\n/**\n * 全局拦截器，作用于所有微服务\n * <p>\n * 1. 清洗请求头中的from参数 2. 重写StripPrefix = 1，支持全局路由\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic class PigRequestGlobalFilter implements GlobalFilter, Ordered {\n\n\t/**\n\t * 处理Web请求并（可选地）通过给定的网关过滤器链委托给下一个过滤器\n\t * @param exchange 当前服务器交换对象\n\t * @param chain 提供委托给下一个过滤器的方式\n\t * @return {@code Mono<Void>} 表示请求处理完成\n\t */\n\t@Override\n\tpublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {\n\n\t\t// 1. 清洗请求头中from 参数\n\t\tServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {\n\t\t\thttpHeaders.remove(SecurityConstants.FROM);\n\t\t\t// 设置请求时间\n\t\t\thttpHeaders.put(CommonConstants.REQUEST_START_TIME,\n\t\t\t\t\tCollections.singletonList(String.valueOf(System.currentTimeMillis())));\n\t\t}).build();\n\n\t\t// 2. 重写StripPrefix\n\t\taddOriginalRequestUrl(exchange, request.getURI());\n\t\tString rawPath = request.getURI().getRawPath();\n\t\tString newPath = \"/\" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, \"/\"))\n\t\t\t.skip(1L)\n\t\t\t.collect(Collectors.joining(\"/\"));\n\n\t\tServerHttpRequest newRequest = request.mutate().path(newPath).build();\n\t\texchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());\n\n\t\treturn chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());\n\t}\n\n\t@Override\n\tpublic int getOrder() {\n\t\treturn 10;\n\t}\n\n}\n"
  },
  {
    "path": "pig-gateway/src/main/java/com/pig4cloud/pig/gateway/handler/GlobalExceptionHandler.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.gateway.handler;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.pig4cloud.pig.common.core.util.R;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.core.io.buffer.DataBufferFactory;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.server.reactive.ServerHttpResponse;\nimport org.springframework.web.server.ResponseStatusException;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\n/**\n * 网关异常通用处理器，作用于WebFlux环境，优先级低于ResponseStatusExceptionHandler\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\n@Order(-1)\n@RequiredArgsConstructor\npublic class GlobalExceptionHandler implements ErrorWebExceptionHandler {\n\n\t/**\n\t * 对象映射器，用于JSON序列化与反序列化\n\t */\n\tprivate final ObjectMapper objectMapper;\n\n\t/**\n\t * @param exchange 服务器网络交换对象\n\t * @param ex 抛出的异常\n\t * @return Mono<Void> 异步处理结果\n\t */\n\t@Override\n\tpublic Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {\n\t\tServerHttpResponse response = exchange.getResponse();\n\n\t\tif (response.isCommitted()) {\n\t\t\treturn Mono.error(ex);\n\t\t}\n\n\t\t// header set\n\t\tresponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);\n\t\tif (ex instanceof ResponseStatusException) {\n\t\t\tresponse.setStatusCode(((ResponseStatusException) ex).getStatusCode());\n\t\t}\n\n\t\treturn response.writeWith(Mono.fromSupplier(() -> {\n\t\t\tDataBufferFactory bufferFactory = response.bufferFactory();\n\t\t\ttry {\n\t\t\t\tlog.debug(\"Error Spring Cloud Gateway : {} {}\", exchange.getRequest().getPath(), ex.getMessage());\n\t\t\t\treturn bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));\n\t\t\t}\n\t\t\tcatch (JsonProcessingException e) {\n\t\t\t\tlog.error(\"Error writing response\", ex);\n\t\t\t\treturn bufferFactory.wrap(new byte[0]);\n\t\t\t}\n\t\t}));\n\t}\n\n}\n"
  },
  {
    "path": "pig-gateway/src/main/resources/application.yml",
    "content": "server:\n  port: 9999\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      username: @nacos.username@\n      password: @nacos.password@\n      discovery:\n        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}\n        watch:\n          enabled: true\n        watch-delay: 1000\n      config:\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n  config:\n    import:\n      - optional:nacos:application-@profiles.active@.yml\n      - optional:nacos:${spring.application.name}-@profiles.active@.yml\n\n\n\n\n"
  },
  {
    "path": "pig-gateway/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration debug=\"false\" scan=\"false\">\n\t<springProperty scop=\"context\" name=\"spring.application.name\" source=\"spring.application.name\" defaultValue=\"\"/>\n\t<property name=\"log.path\" value=\"logs/${spring.application.name}\"/>\n\t<!-- 彩色日志格式 -->\n\t<property name=\"CONSOLE_LOG_PATTERN\"\n\t\t\t  value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n\t<conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n\t<conversionRule conversionWord=\"wex\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n\t<conversionRule conversionWord=\"wEx\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n\t<!-- Console log output -->\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>${CONSOLE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file debug output -->\n\t<appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/debug.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file error output -->\n\t<appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/error.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t\t<filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n\t\t\t<level>ERROR</level>\n\t\t</filter>\n\t</appender>\n\n\t<!--nacos 心跳 INFO 屏蔽-->\n\t<logger name=\"com.alibaba.nacos\" level=\"OFF\">\n\t\t<appender-ref ref=\"error\"/>\n\t</logger>\n\n\t<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n\t<root level=\"INFO\">\n\t\t<appender-ref ref=\"console\"/>\n\t\t<appender-ref ref=\"debug\"/>\n\t\t<appender-ref ref=\"error\"/>\n\t</root>\n</configuration>\n"
  },
  {
    "path": "pig-register/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-register\n\nARG JAR_FILE=target/pig-register.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 8848 9848 8080\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 30; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-register/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright 1999-2018 Alibaba Group Holding Ltd.\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n -->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n\t\t xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\t xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>com.pig4cloud</groupId>\n\t\t<artifactId>pig</artifactId>\n\t\t<version>${revision}</version>\n\t</parent>\n\n\t<artifactId>pig-register</artifactId>\n\t<packaging>jar</packaging>\n\t<name>pig-register</name>\n\t<description>nacos 注册配置中心</description>\n\t<properties>\n\t\t<nacos.version>3.1.0-bugfix</nacos.version>\n\t</properties>\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>io.github.pig-mesh.nacos</groupId>\n\t\t\t<artifactId>nacos-console</artifactId>\n\t\t\t<version>${nacos.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.github.pig-mesh.nacos</groupId>\n\t\t\t<artifactId>nacos-server</artifactId>\n\t\t\t<version>${nacos.version}</version>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>repackage</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t\t<artifactId>docker-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t\t<resources>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<filtering>true</filtering>\n\t\t\t\t<excludes>\n\t\t\t\t\t<exclude>**/*.woff</exclude>\n\t\t\t\t\t<exclude>**/*.woff2</exclude>\n\t\t\t\t\t<exclude>**/*.ttf</exclude>\n\t\t\t\t\t<exclude>**/*.eot</exclude>\n\t\t\t\t</excludes>\n\t\t\t</resource>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<filtering>false</filtering>\n\t\t\t\t<includes>\n\t\t\t\t\t<include>**/*.woff</include>\n\t\t\t\t\t<include>**/*.woff2</include>\n\t\t\t\t\t<include>**/*.ttf</include>\n\t\t\t\t\t<include>**/*.eot</include>\n\t\t\t\t</includes>\n\t\t\t</resource>\n\t\t</resources>\n\t</build>\n</project>\n"
  },
  {
    "path": "pig-register/src/main/java/com/alibaba/nacos/bootstrap/PigNacosApplication.java",
    "content": "/*\n * Copyright 1999-2018 Alibaba Group Holding Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.alibaba.nacos.bootstrap;\n\nimport static org.springframework.boot.context.logging.LoggingApplicationListener.CONFIG_PROPERTY;\nimport static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX;\n\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\n\nimport com.alibaba.nacos.NacosServerBasicApplication;\nimport com.alibaba.nacos.NacosServerWebApplication;\nimport com.alibaba.nacos.console.NacosConsole;\nimport com.alibaba.nacos.core.listener.startup.NacosStartUp;\nimport com.alibaba.nacos.core.listener.startup.NacosStartUpManager;\nimport com.alibaba.nacos.sys.env.Constants;\nimport com.alibaba.nacos.sys.env.DeploymentType;\nimport com.alibaba.nacos.sys.env.EnvUtil;\n\n/**\n * @author nacos\n * <p>\n * nacos console 源码运行，方便开发 生产从官网下载zip最新版集群配置运行\n */\npublic class PigNacosApplication {\n\n\t/**\n\t * 独立模式系统属性名称\n\t */\n\tprivate static String STANDALONE_MODE = \"nacos.standalone\";\n\n\tpublic static void main(String[] args) {\n\t\tSystem.setProperty(STANDALONE_MODE, \"true\");\n\t\tSystem.setProperty(CONFIG_PROPERTY, CLASSPATH_URL_PREFIX + \"logback-spring.xml\");\n\n\t\tString type = System.getProperty(Constants.NACOS_DEPLOYMENT_TYPE, Constants.NACOS_DEPLOYMENT_TYPE_MERGED);\n\t\tDeploymentType deploymentType = DeploymentType.getType(type);\n\t\tEnvUtil.setDeploymentType(deploymentType);\n\n\t\t// Start Core Context\n\t\tNacosStartUpManager.start(NacosStartUp.CORE_START_UP_PHASE);\n\t\tConfigurableApplicationContext coreContext = new SpringApplicationBuilder(NacosServerBasicApplication.class)\n\t\t\t.web(WebApplicationType.NONE)\n\t\t\t.run(args);\n\n\t\t// Start Server Web Context\n\t\tNacosStartUpManager.start(NacosStartUp.WEB_START_UP_PHASE);\n\t\tConfigurableApplicationContext serverWebContext = new SpringApplicationBuilder(NacosServerWebApplication.class)\n\t\t\t.parent(coreContext)\n\t\t\t.run(args);\n\n\t\t// Start Console Context\n\t\tNacosStartUpManager.start(NacosStartUp.CONSOLE_START_UP_PHASE);\n\t\tConfigurableApplicationContext consoleContext = new SpringApplicationBuilder(NacosConsole.class)\n\t\t\t.parent(coreContext)\n\t\t\t.run(args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-register/src/main/resources/application.properties",
    "content": "# Nacos \\u63A7\\u5236\\u53F0\\u7AEF\\u53E3\\uFF0C\\u8BF7\\u6CE8\\u610F\\u8BBF\\u95EE\\u7684 IP:8080\nnacos.console.port=8080 \n# Nacos \\u670D\\u52A1\\u7AEF\\u4E3B\\u7AEF\\u53E3\nnacos.server.main.port=8848\n# \\u6570\\u636E\\u5E93\\u8BBE\\u7F6E\nspring.sql.init.platform=mysql\ndb.num=1\ndb.url.0=jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig_config}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\ndb.user=root\ndb.password=root\n# nacos \\u4F1A\\u81EA\\u52A8\\u88C5\\u914D\\u6570\\u636E\\u6E90\\uFF0C\\u6240\\u4EE5\\u6392\\u9664 spring.datasource \\u81EA\\u52A8\\u914D\\u7F6E\nspring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration\n#*************** \\u76D1\\u63A7\\u76F8\\u5173\\u914D\\u7F6E ***************#\n# Prometheus \\u76D1\\u63A7\\u914D\\u7F6E\n#management.endpoints.web.exposure.include=prometheus\n# ElasticSearch \\u76D1\\u63A7\\u914D\\u7F6E - \\u5DF2\\u7981\\u7528\nmanagement.elastic.metrics.export.enabled=false\n# InfluxDB \\u76D1\\u63A7\\u914D\\u7F6E - \\u5DF2\\u7981\\u7528\nmanagement.influx.metrics.export.enabled=false\n#*************** \\u914D\\u7F6E\\u4E2D\\u5FC3\\u76F8\\u5173 ***************#\n# Nacos \\u914D\\u7F6E\\u63A8\\u9001\\u6700\\u5927\\u91CD\\u8BD5\\u6B21\\u6570\nnacos.config.push.maxRetryTime=50\n#*************** \\u547D\\u540D\\u670D\\u52A1\\u76F8\\u5173 ***************#\n# \\u7A7A\\u670D\\u52A1\\u81EA\\u52A8\\u6E05\\u7406\nnacos.naming.empty-service.auto-clean=true\nnacos.naming.empty-service.clean.initial-delay-ms=50000\nnacos.naming.empty-service.clean.period-time-ms=30000\n#*************** Nacos Web \\u76F8\\u5173\\u914D\\u7F6E ***************#\n# Nacos \\u670D\\u52A1\\u7AEF\\u4E0A\\u4E0B\\u6587\\u8DEF\\u5F84\nnacos.server.contextPath=/nacos\n#*************** \\u65E5\\u5FD7\\u76F8\\u5173\\u914D\\u7F6E ***************#\n# \\u5F00\\u542F\\u8BBF\\u95EE\\u65E5\\u5FD7\nserver.tomcat.accesslog.enabled=true\n# \\u8BBF\\u95EE\\u65E5\\u5FD7\\u4FDD\\u7559\\u5929\\u6570\\u914D\\u7F6E\nserver.tomcat.accesslog.max-days=30\n# \\u8BBF\\u95EE\\u65E5\\u5FD7\\u683C\\u5F0F\nserver.tomcat.accesslog.pattern=%h %l %u %t \"%r\" %s %b %D %{User-Agent}i %{Request-Source}i\n# \\u65E5\\u5FD7\\u57FA\\u7840\\u76EE\\u5F55\nserver.tomcat.basedir=file:.\n#*************** API \\u9519\\u8BEF\\u5904\\u7406 ***************#\n# \\u9519\\u8BEF\\u4FE1\\u606F\\u663E\\u793A\\u8BBE\\u7F6E\nserver.error.include-message=ALWAYS\n#*************** Nacos \\u63A7\\u5236\\u53F0\\u914D\\u7F6E ***************#\n# Nacos \\u63A7\\u5236\\u53F0\\u4E0A\\u4E0B\\u6587\\u8DEF\\u5F84\nnacos.console.contextPath=\n# Nacos \\u63A7\\u5236\\u53F0\\u8FDC\\u7A0B\\u670D\\u52A1\\u4E0A\\u4E0B\\u6587\\u8DEF\\u5F84\nnacos.console.remote.server.context-path=/nacos\n#*************** \\u5B89\\u5168\\u76F8\\u5173\\u914D\\u7F6E ***************#\n# \\u5B89\\u5168\\u5FFD\\u7565 URL\nnacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**\n# \\u8BA4\\u8BC1\\u7CFB\\u7EDF\\u7C7B\\u578B\nnacos.core.auth.system.type=nacos\n# \\u5F00\\u542F\\u8BA4\\u8BC1\nnacos.core.auth.enabled=true\n# \\u542F\\u7528 API \\u8BBF\\u95EE\\u6743\\u9650\nnacos.core.auth.admin.enabled=true\n# \\u542F\\u7528\\u63A7\\u5236\\u53F0 API \\u8BA4\\u8BC1\nnacos.core.auth.console.enabled=true\n# \\u5F00\\u542F\\u8BA4\\u8BC1\\u7F13\\u5B58\nnacos.core.auth.caching.enabled=true\n# \\u670D\\u52A1\\u5668\\u8EAB\\u4EFD\\u8BA4\\u8BC1 (\\u5F53 nacos.core.auth.enabled=true \\u65F6\\u751F\\u6548)\nnacos.core.auth.server.identity.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=\nnacos.core.auth.server.identity.value=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=\n# Nacos \\u4EE4\\u724C\\u76F8\\u5173\\u914D\\u7F6E\nnacos.core.auth.plugin.nacos.token.cache.enable=false\nnacos.core.auth.plugin.nacos.token.expire.seconds=18000\n# \\u8BA4\\u8BC1\\u5BC6\\u94A5 (Base64 \\u7F16\\u7801)\nnacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=\n#*************** Istio \\u670D\\u52A1\\u7F51\\u683C\\u914D\\u7F6E ***************#\n# MCP \\u670D\\u52A1\\u652F\\u6301 - \\u5DF2\\u7981\\u7528\nnacos.istio.mcp.server.enabled=false\n#*************** K8s \\u76F8\\u5173\\u914D\\u7F6E ***************#\n# K8s \\u540C\\u6B65\\u652F\\u6301 - \\u5DF2\\u7981\\u7528\nnacos.k8s.sync.enabled=false\n#*************** \\u90E8\\u7F72\\u6A21\\u5F0F\\u914D\\u7F6E ***************#\n# \\u90E8\\u7F72\\u6A21\\u5F0F: 'merged' \\u6DF7\\u5408\\u6A21\\u5F0F, 'server' \\u670D\\u52A1\\u7AEF\\u6A21\\u5F0F, 'console' \\u63A7\\u5236\\u53F0\\u6A21\\u5F0F\nnacos.deployment.type=merged\n#*************** \\u6A21\\u7CCA\\u5339\\u914D\\u914D\\u7F6E ***************#\n# \\u914D\\u7F6E\\u4E2D\\u5FC3\\u6A21\\u7CCA\\u5339\\u914D\\u6700\\u5927\\u503C\nnacos.config.fuzzy.watch.max.pattern.count=20\nnacos.config.fuzzy.watch.max.pattern.match.config.count=500\n# \\u670D\\u52A1\\u53D1\\u73B0\\u6A21\\u7CCA\\u5339\\u914D\\u6700\\u5927\\u503C\nnacos.naming.fuzzy.watch.max.pattern.count=20\nnacos.naming.fuzzy.watch.max.pattern.match.service.count=500\n"
  },
  {
    "path": "pig-register/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~    Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~ Redistribution and use in source and binary forms, with or without\n  ~ modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~ this list of conditions and the following disclaimer.\n  ~ Redistributions in binary form must reproduce the above copyright\n  ~ notice, this list of conditions and the following disclaimer in the\n  ~ documentation and/or other materials provided with the distribution.\n  ~ Neither the name of the pig4cloud.com developer nor the names of its\n  ~ contributors may be used to endorse or promote products derived from\n  ~ this software without specific prior written permission.\n  ~ Author: lengleng (wangiegie@gmail.com)\n  -->\n\n<!--\n    小技巧: 在根pom里面设置统一存放路径，统一管理方便维护\n    <properties>\n        <log-path>/Users/lengleng</log-path>\n    </properties>\n    1. 其他模块加日志输出，直接copy本文件放在resources 目录即可\n    2. 注意修改 <property name=\"${log-path}/log.path\" value=\"\"/> 的value模块\n-->\n<configuration debug=\"false\" scan=\"false\">\n    <property name=\"log.path\" value=\"logs/${project.artifactId}\"/>\n    <!-- 彩色日志格式 -->\n    <property name=\"CONSOLE_LOG_PATTERN\"\n              value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n    <!-- 彩色日志依赖的渲染类 -->\n    <conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n    <conversionRule conversionWord=\"wex\"\n                    class=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n    <conversionRule conversionWord=\"wEx\"\n                    class=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n    <!-- Console log output -->\n    <appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>${CONSOLE_LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Log file debug output -->\n    <appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/debug.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n            <maxFileSize>50MB</maxFileSize>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Log file error output -->\n    <appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/error.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n            <maxFileSize>50MB</maxFileSize>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>\n        </encoder>\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>ERROR</level>\n        </filter>\n    </appender>\n\n    <logger name=\"org.activiti.engine.impl.db\" level=\"DEBUG\">\n        <appender-ref ref=\"debug\"/>\n    </logger>\n\n    <!--nacos 心跳 INFO 屏蔽-->\n    <logger name=\"com.alibaba.nacos\" level=\"OFF\">\n        <appender-ref ref=\"error\"/>\n    </logger>\n\n    <!--AJ 验证码INFO 屏蔽-->\n    <logger name=\"com.anji.captcha\" level=\"OFF\">\n        <appender-ref ref=\"error\"/>\n    </logger>\n\n    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n    <root level=\"INFO\">\n        <appender-ref ref=\"console\"/>\n        <appender-ref ref=\"debug\"/>\n        <appender-ref ref=\"error\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "pig-upms/pig-upms-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-upms</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-upms-api</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 通用用户权限管理系统公共api模块</description>\n\n\n    <dependencies>\n        <!--core 工具类-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <!--feign 注解依赖-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-feign</artifactId>\n        </dependency>\n        <!--mybatis 依赖-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-mybatis</artifactId>\n        </dependency>\n        <!-- excel 导入导出 -->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-excel</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/RegisterUserDTO.java",
    "content": "package com.pig4cloud.pig.admin.api.dto;\n\nimport lombok.Data;\n\n/**\n * 注册用户 DTO\n *\n * @author lengleng\n * @date 2024/12/23\n */\n@Data\npublic class RegisterUserDTO {\n\n\t/**\n\t * 用户名\n\t */\n\tprivate String username;\n\n\t/**\n\t * 新密码\n\t */\n\tprivate String password;\n\n\t/**\n\t * 电话\n\t */\n\tprivate String phone;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/SysLogDTO.java",
    "content": "package com.pig4cloud.pig.admin.api.dto;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n/**\n * @author lengleng\n * @date 2020/10/9\n * <p>\n * 日志查询传输对象\n */\n@Data\n@Schema(description = \"日志查询对象\")\npublic class SysLogDTO {\n\n\t/**\n\t * 编号\n\t */\n\tprivate Long id;\n\n\t/**\n\t * 日志类型\n\t */\n\t@NotBlank(message = \"日志类型不能为空\")\n\tprivate String logType;\n\n\t/**\n\t * 日志标题\n\t */\n\t@NotBlank(message = \"日志标题不能为空\")\n\tprivate String title;\n\n\t/**\n\t * 创建者\n\t */\n\tprivate String createBy;\n\n\t/**\n\t * 更新时间\n\t */\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 操作IP地址\n\t */\n\tprivate String remoteAddr;\n\n\t/**\n\t * 用户代理\n\t */\n\tprivate String userAgent;\n\n\t/**\n\t * 请求URI\n\t */\n\tprivate String requestUri;\n\n\t/**\n\t * 操作方式\n\t */\n\tprivate String method;\n\n\t/**\n\t * 操作提交的数据\n\t */\n\tprivate String params;\n\n\t/**\n\t * 执行时间\n\t */\n\tprivate Long time;\n\n\t/**\n\t * 异常信息\n\t */\n\tprivate String exception;\n\n\t/**\n\t * 服务ID\n\t */\n\tprivate String serviceId;\n\n\t/**\n\t * 创建时间区间 [开始时间，结束时间]\n\t */\n\tprivate LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/UserDTO.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.dto;\n\nimport java.io.Serial;\nimport java.util.List;\n\nimport com.pig4cloud.pig.admin.api.entity.SysUser;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * @author lengleng\n * @date 2017/11/5\n */\n@Data\n@Schema(description = \"系统用户传输对象\")\n@EqualsAndHashCode(callSuper = true)\npublic class UserDTO extends SysUser {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 角色ID\n\t */\n\t@Schema(description = \"角色id集合\")\n\tprivate List<Long> role;\n\n\t/**\n\t * 部门id\n\t */\n\t@Schema(description = \"部门id\")\n\tprivate Long deptId;\n\n\t/**\n\t * 岗位ID\n\t */\n\tprivate List<Long> post;\n\n\t/**\n\t * 新密码\n\t */\n\t@Schema(description = \"新密码\")\n\tprivate String newpassword1;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/UserInfo.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.dto;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.pig4cloud.pig.admin.api.vo.UserVO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 用户信息实体类，继承自UserVO并实现Serializable接口 , spring security\n *\n * @author lengleng\n * @date 2025/06/28\n */\n@Data\n@Schema(description = \"spring security 用户信息\")\n@EqualsAndHashCode(callSuper = true)\npublic class UserInfo extends UserVO implements Serializable {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 密码\n\t */\n\t@JsonIgnore(value = false)\n\tprivate String password;\n\n\t/**\n\t * 随机盐\n\t */\n\t@JsonIgnore(value = false)\n\tprivate String salt;\n\n\t/**\n\t * 权限标识集合\n\t */\n\t@Schema(description = \"权限标识集合\")\n\tprivate List<String> permissions = new ArrayList<>();\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDept.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.FieldNameConstants;\n\nimport java.time.LocalDateTime;\n\n/**\n * <p>\n * 部门管理\n * </p>\n *\n * @author lengleng\n * @since 2018-01-22\n */\n@Data\n@Schema(description = \"部门\")\n@FieldNameConstants\n@EqualsAndHashCode(callSuper = true)\npublic class SysDept extends Model<SysDept> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t@TableId(value = \"dept_id\", type = IdType.ASSIGN_ID)\n\t@Schema(description = \"部门id\")\n\tprivate Long deptId;\n\n\t/**\n\t * 部门名称\n\t */\n\t@NotBlank(message = \"部门名称不能为空\")\n\t@Schema(description = \"部门名称\")\n\tprivate String name;\n\n\t/**\n\t * 排序\n\t */\n\t@NotNull(message = \"排序值不能为空\")\n\t@Schema(description = \"排序值\")\n\tprivate Integer sortOrder;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@Schema(description = \"修改时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 父级部门id\n\t */\n\t@Schema(description = \"父级部门id\")\n\tprivate Long parentId;\n\n\t/**\n\t * 是否删除 1：已删除 0：正常\n\t */\n\t@TableLogic\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDeptRelation.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 部门关系表\n * </p>\n *\n * @author lengleng\n * @since 2018-01-22\n */\n@Data\n@Schema(description = \"部门关系\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysDeptRelation extends Model<SysDeptRelation> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 祖先节点\n\t */\n\t@Schema(description = \"祖先节点\")\n\tprivate Long ancestor;\n\n\t/**\n\t * 后代节点\n\t */\n\t@Schema(description = \"后代节点\")\n\tprivate Long descendant;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDict.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 字典表\n *\n * @author lengleng\n * @date 2019/03/19\n */\n@Data\n@Schema(description = \"字典类型\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysDict extends Model<SysDict> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 编号\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"字典编号\")\n\tprivate Long id;\n\n\t/**\n\t * 类型\n\t */\n\t@Schema(description = \"字典类型\")\n\tprivate String dictType;\n\n\t/**\n\t * 描述\n\t */\n\t@Schema(description = \"字典描述\")\n\tprivate String description;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@Schema(description = \"更新时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 是否是系统内置\n\t */\n\t@Schema(description = \"是否系统内置\")\n\tprivate String systemFlag;\n\n\t/**\n\t * 备注信息\n\t */\n\t@Schema(description = \"备注信息\")\n\tprivate String remarks;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 删除标记\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDictItem.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 字典项\n *\n * @author lengleng\n * @date 2019/03/19\n */\n@Data\n@Schema(description = \"字典项\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysDictItem extends Model<SysDictItem> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 编号\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"字典项id\")\n\tprivate Long id;\n\n\t/**\n\t * 所属字典类id\n\t */\n\t@Schema(description = \"所属字典类id\")\n\tprivate Long dictId;\n\n\t/**\n\t * 数据值\n\t */\n\t@Schema(description = \"数据值\")\n\t@JsonProperty(value = \"value\")\n\tprivate String itemValue;\n\n\t/**\n\t * 标签名\n\t */\n\t@Schema(description = \"标签名\")\n\tprivate String label;\n\n\t/**\n\t * 类型\n\t */\n\t@Schema(description = \"类型\")\n\tprivate String dictType;\n\n\t/**\n\t * 描述\n\t */\n\t@Schema(description = \"描述\")\n\tprivate String description;\n\n\t/**\n\t * 排序（升序）\n\t */\n\t@Schema(description = \"排序值，默认升序\")\n\tprivate Integer sortOrder;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"更新时间\")\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 备注信息\n\t */\n\t@Schema(description = \"备注信息\")\n\tprivate String remarks;\n\n\t/**\n\t * 删除标记\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysFile.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.FieldNameConstants;\n\nimport java.io.Serial;\nimport java.time.LocalDateTime;\n\n/**\n * 文件管理实体类\n *\n * @author lengleng\n * @date 2025/07/03\n */\n@Data\n@FieldNameConstants\n@Schema(description = \"文件\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysFile extends Model<SysFile> {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 编号\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"文件编号\")\n\tprivate Long id;\n\n\t/**\n\t * 文件名\n\t */\n\t@Schema(description = \"文件名\")\n\tprivate String fileName;\n\n\t/**\n\t * 原文件名\n\t */\n\t@Schema(description = \"原始文件名\")\n\tprivate String original;\n\n\t/**\n\t * 容器名称\n\t */\n\t@Schema(description = \"存储桶名称\")\n\tprivate String bucketName;\n\n\t/**\n\t * 文件类型\n\t */\n\t@Schema(description = \"文件类型\")\n\tprivate String type;\n\n\t/**\n\t * 文件大小\n\t */\n\t@Schema(description = \"文件大小\")\n\tprivate Long fileSize;\n\n\t/**\n\t * 上传人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建者\")\n\tprivate String createBy;\n\n\t/**\n\t * 上传时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"更新者\")\n\tprivate String updateBy;\n\n\t/**\n\t * 更新时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"更新时间\")\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 删除标识：1-删除，0-正常\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysLog.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport cn.idev.excel.annotation.ExcelIgnore;\nimport cn.idev.excel.annotation.ExcelProperty;\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * <p>\n * 日志表\n * </p>\n *\n * @author lengleng\n * @since 2017-11-20\n */\n@Data\n@Schema(description = \"日志\")\npublic class SysLog implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 编号\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@ExcelProperty(\"日志编号\")\n\t@Schema(description = \"日志编号\")\n\tprivate Long id;\n\n\t/**\n\t * 日志类型\n\t */\n\t@NotBlank(message = \"日志类型不能为空\")\n\t@ExcelProperty(\"日志类型（0-正常 9-错误）\")\n\t@Schema(description = \"日志类型\")\n\tprivate String logType;\n\n\t/**\n\t * 日志标题\n\t */\n\t@NotBlank(message = \"日志标题不能为空\")\n\t@ExcelProperty(\"日志标题\")\n\t@Schema(description = \"日志标题\")\n\tprivate String title;\n\n\t/**\n\t * 创建者\n\t */\n\t@ExcelProperty(\"创建人\")\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@ExcelProperty(\"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@ExcelIgnore\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"更新时间\")\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 操作IP地址\n\t */\n\t@ExcelProperty(\"操作ip地址\")\n\t@Schema(description = \"操作ip地址\")\n\tprivate String remoteAddr;\n\n\t/**\n\t * 用户代理\n\t */\n\t@Schema(description = \"用户代理\")\n\tprivate String userAgent;\n\n\t/**\n\t * 请求URI\n\t */\n\t@ExcelProperty(\"浏览器\")\n\t@Schema(description = \"请求uri\")\n\tprivate String requestUri;\n\n\t/**\n\t * 操作方式\n\t */\n\t@ExcelProperty(\"操作方式\")\n\t@Schema(description = \"操作方式\")\n\tprivate String method;\n\n\t/**\n\t * 操作提交的数据\n\t */\n\t@ExcelProperty(\"提交数据\")\n\t@Schema(description = \"提交数据\")\n\tprivate String params;\n\n\t/**\n\t * 执行时间\n\t */\n\t@ExcelProperty(\"执行时间\")\n\t@Schema(description = \"方法执行时间\")\n\tprivate Long time;\n\n\t/**\n\t * 异常信息\n\t */\n\t@ExcelProperty(\"异常信息\")\n\t@Schema(description = \"异常信息\")\n\tprivate String exception;\n\n\t/**\n\t * 服务ID\n\t */\n\t@ExcelProperty(\"应用标识\")\n\t@Schema(description = \"应用标识\")\n\tprivate String serviceId;\n\n\t/**\n\t * 删除标记\n\t */\n\t@TableLogic\n\t@ExcelIgnore\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysMenu.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.FieldNameConstants;\n\nimport java.time.LocalDateTime;\n\n/**\n * <p>\n * 菜单权限表\n * </p>\n *\n * @author lengleng\n * @since 2017-11-08\n */\n@Data\n@Schema(description = \"菜单\")\n@FieldNameConstants\n@EqualsAndHashCode(callSuper = true)\npublic class SysMenu extends Model<SysMenu> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 菜单ID\n\t */\n\t@TableId(value = \"menu_id\", type = IdType.ASSIGN_ID)\n\t@Schema(description = \"菜单id\")\n\tprivate Long menuId;\n\n\t/**\n\t * 菜单名称\n\t */\n\t@NotBlank(message = \"菜单名称不能为空\")\n\t@Schema(description = \"菜单名称\")\n\tprivate String name;\n\n\t/**\n\t * 菜单名称\n\t */\n\t@Schema(description = \"菜单名称\")\n\tprivate String enName;\n\n\t/**\n\t * 菜单权限标识\n\t */\n\t@Schema(description = \"菜单权限标识\")\n\tprivate String permission;\n\n\t/**\n\t * 父菜单ID\n\t */\n\t@NotNull(message = \"菜单父ID不能为空\")\n\t@Schema(description = \"菜单父id\")\n\tprivate Long parentId;\n\n\t/**\n\t * 图标\n\t */\n\t@Schema(description = \"菜单图标\")\n\tprivate String icon;\n\n\t/**\n\t * 前端路由标识路径，默认和 comment 保持一致 过期\n\t */\n\t@Schema(description = \"前端路由标识路径\")\n\tprivate String path;\n\n\t/**\n\t * 菜单显示隐藏控制\n\t */\n\t@Schema(description = \"菜单是否显示\")\n\tprivate String visible;\n\n\t/**\n\t * 排序值\n\t */\n\t@Schema(description = \"排序值\")\n\tprivate Integer sortOrder;\n\n\t/**\n\t * 菜单类型 （0菜单 1按钮）\n\t */\n\t@NotNull(message = \"菜单类型不能为空\")\n\t@Schema(description = \"菜单类型,0:菜单 1:按钮\")\n\tprivate String menuType;\n\n\t/**\n\t * 路由缓冲\n\t */\n\t@Schema(description = \"路由缓冲\")\n\tprivate String keepAlive;\n\n\t@Schema(description = \"菜单是否内嵌\")\n\tprivate String embedded;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"更新时间\")\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 0--正常 1--删除\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysOauthClientDetails.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * <p>\n * 客户端信息\n * </p>\n *\n * @author lengleng\n * @since 2018-05-15\n */\n@Data\n@Schema(description = \"客户端信息\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysOauthClientDetails extends Model<SysOauthClientDetails> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t@TableId(value = \"id\", type = IdType.ASSIGN_ID)\n\t@Schema(description = \"id\")\n\tprivate Long id;\n\n\t/**\n\t * 客户端ID\n\t */\n\t@NotBlank(message = \"client_id 不能为空\")\n\t@Schema(description = \"客户端id\")\n\tprivate String clientId;\n\n\t/**\n\t * 客户端密钥\n\t */\n\t@NotBlank(message = \"client_secret 不能为空\")\n\t@Schema(description = \"客户端密钥\")\n\tprivate String clientSecret;\n\n\t/**\n\t * 资源ID\n\t */\n\t@Schema(description = \"资源id列表\")\n\tprivate String resourceIds;\n\n\t/**\n\t * 作用域\n\t */\n\t@NotBlank(message = \"scope 不能为空\")\n\t@Schema(description = \"作用域\")\n\tprivate String scope;\n\n\t/**\n\t * 授权方式[A,B,C]\n\t */\n\t@Schema(description = \"授权方式\")\n\tprivate String[] authorizedGrantTypes;\n\n\t/**\n\t * 回调地址\n\t */\n\t@Schema(description = \"回调地址\")\n\tprivate String webServerRedirectUri;\n\n\t/**\n\t * 权限\n\t */\n\t@Schema(description = \"权限列表\")\n\tprivate String authorities;\n\n\t/**\n\t * 请求令牌有效时间\n\t */\n\t@Schema(description = \"请求令牌有效时间\")\n\tprivate Integer accessTokenValidity;\n\n\t/**\n\t * 刷新令牌有效时间\n\t */\n\t@Schema(description = \"刷新令牌有效时间\")\n\tprivate Integer refreshTokenValidity;\n\n\t/**\n\t * 扩展信息\n\t */\n\t@Schema(description = \"扩展信息\")\n\tprivate String additionalInformation;\n\n\t/**\n\t * 是否自动放行\n\t */\n\t@Schema(description = \"是否自动放行\")\n\tprivate String autoapprove;\n\n\t/**\n\t * 删除标记\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"更新时间\")\n\tprivate LocalDateTime updateTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysPost.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 岗位信息表\n *\n * @author fxz\n * @date 2022-03-26 12:50:43\n */\n@Data\n@TableName(\"sys_post\")\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"岗位信息表\")\npublic class SysPost extends Model<SysPost> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 岗位ID\n\t */\n\t@TableId(value = \"post_id\", type = IdType.ASSIGN_ID)\n\t@Schema(description = \"岗位ID\")\n\tprivate Long postId;\n\n\t/**\n\t * 岗位编码\n\t */\n\t@NotBlank(message = \"岗位编码不能为空\")\n\t@Schema(description = \"岗位编码\")\n\tprivate String postCode;\n\n\t/**\n\t * 岗位名称\n\t */\n\t@NotBlank(message = \"岗位名称不能为空\")\n\t@Schema(description = \"岗位名称\")\n\tprivate String postName;\n\n\t/**\n\t * 岗位排序\n\t */\n\t@NotNull(message = \"排序值不能为空\")\n\t@Schema(description = \"岗位排序\")\n\tprivate Integer postSort;\n\n\t/**\n\t * 岗位描述\n\t */\n\t@Schema(description = \"岗位描述\")\n\tprivate String remark;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 是否删除 -1：已删除 0：正常\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"是否删除  -1：已删除  0：正常\")\n\tprivate String delFlag;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@Schema(description = \"更新时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysPublicParam.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 公共参数配置\n *\n * @author Lucky\n * @date 2019-04-29\n */\n@Data\n@Schema(description = \"公共参数\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysPublicParam extends Model<SysPublicParam> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 编号\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"公共参数编号\")\n\tprivate Long publicId;\n\n\t/**\n\t * 公共参数名称\n\t */\n\t@Schema(description = \"公共参数名称\", required = true, example = \"公共参数名称\")\n\tprivate String publicName;\n\n\t/**\n\t * 公共参数地址值,英文大写+下划线\n\t */\n\t@Schema(description = \"键[英文大写+下划线]\", required = true, example = \"PIGX_PUBLIC_KEY\")\n\tprivate String publicKey;\n\n\t/**\n\t * 值\n\t */\n\t@Schema(description = \"值\", required = true, example = \"999\")\n\tprivate String publicValue;\n\n\t/**\n\t * 状态（1有效；2无效；）\n\t */\n\t@Schema(description = \"标识[1有效；2无效]\", example = \"1\")\n\tprivate String status;\n\n\t/**\n\t * 公共参数编码\n\t */\n\t@Schema(description = \"编码\", example = \"^(PIG|PIGX)$\")\n\tprivate String validateCode;\n\n\t/**\n\t * 是否是系统内置\n\t */\n\t@Schema(description = \"是否是系统内置\")\n\tprivate String systemFlag;\n\n\t/**\n\t * 配置类型：0-默认；1-检索；2-原文；3-报表；4-安全；5-文档；6-消息；9-其他\n\t */\n\t@Schema(description = \"类型[1-检索；2-原文...]\", example = \"1\")\n\tprivate String publicType;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 删除标记\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 更新时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"更新时间\")\n\tprivate LocalDateTime updateTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysRole.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * <p>\n * 角色表\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Data\n@Schema(description = \"角色\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysRole extends Model<SysRole> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t@TableId(value = \"role_id\", type = IdType.ASSIGN_ID)\n\t@Schema(description = \"角色编号\")\n\tprivate Long roleId;\n\n\t@NotBlank(message = \"角色名称不能为空\")\n\t@Schema(description = \"角色名称\")\n\tprivate String roleName;\n\n\t@NotBlank(message = \"角色标识不能为空\")\n\t@Schema(description = \"角色标识\")\n\tprivate String roleCode;\n\n\t@Schema(description = \"角色描述\")\n\tprivate String roleDesc;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@Schema(description = \"修改时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 删除标识（0-正常,1-删除）\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysRoleMenu.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 角色菜单表\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Data\n@Schema(description = \"角色菜单\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysRoleMenu extends Model<SysRoleMenu> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 角色ID\n\t */\n\t@Schema(description = \"角色id\")\n\tprivate Long roleId;\n\n\t/**\n\t * 菜单ID\n\t */\n\t@Schema(description = \"菜单id\")\n\tprivate Long menuId;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysUser.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * <p>\n * 用户表\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Data\n@Schema(description = \"用户\")\npublic class SysUser implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 主键ID\n\t */\n\t@TableId(value = \"user_id\", type = IdType.ASSIGN_ID)\n\t@Schema(description = \"主键id\")\n\tprivate Long userId;\n\n\t/**\n\t * 用户名\n\t */\n\t@Schema(description = \"用户名\")\n\tprivate String username;\n\n\t/**\n\t * 密码\n\t */\n\t@Schema(description = \"密码\")\n\tprivate String password;\n\n\t/**\n\t * 随机盐\n\t */\n\t@JsonIgnore\n\t@Schema(description = \"随机盐\")\n\tprivate String salt;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改时间\")\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 0-正常，1-删除\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n\t/**\n\t * 锁定标记\n\t */\n\t@Schema(description = \"锁定标记\")\n\tprivate String lockFlag;\n\n\t/**\n\t * 手机号\n\t */\n\t@Schema(description = \"手机号\")\n\tprivate String phone;\n\n\t/**\n\t * 头像\n\t */\n\t@Schema(description = \"头像地址\")\n\tprivate String avatar;\n\n\t/**\n\t * 部门ID\n\t */\n\t@Schema(description = \"用户所属部门id\")\n\tprivate Long deptId;\n\n\t/**\n\t * 微信openid\n\t */\n\t@Schema(description = \"微信openid\")\n\tprivate String wxOpenid;\n\n\t/**\n\t * 微信小程序openId\n\t */\n\t@Schema(description = \"微信小程序openid\")\n\tprivate String miniOpenid;\n\n\t/**\n\t * QQ openid\n\t */\n\t@Schema(description = \"QQ openid\")\n\tprivate String qqOpenid;\n\n\t/**\n\t * 码云唯一标识\n\t */\n\t@Schema(description = \"码云唯一标识\")\n\tprivate String giteeLogin;\n\n\t/**\n\t * 开源中国唯一标识\n\t */\n\t@Schema(description = \"开源中国唯一标识\")\n\tprivate String oscId;\n\n\t/**\n\t * 昵称\n\t */\n\t@Schema(description = \"昵称\")\n\tprivate String nickname;\n\n\t/**\n\t * 姓名\n\t */\n\t@Schema(description = \"姓名\")\n\tprivate String name;\n\n\t/**\n\t * 邮箱\n\t */\n\t@Schema(description = \"邮箱\")\n\tprivate String email;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysUserPost.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 用户岗位表\n * </p>\n *\n * @author fxz\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class SysUserPost extends Model<SysUserPost> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 用户ID\n\t */\n\t@Schema(description = \"用户id\")\n\tprivate Long userId;\n\n\t/**\n\t * 岗位ID\n\t */\n\t@Schema(description = \"岗位id\")\n\tprivate Long postId;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysUserRole.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.entity;\n\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 用户角色表\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Data\n@Schema(description = \"用户角色\")\n@EqualsAndHashCode(callSuper = true)\npublic class SysUserRole extends Model<SysUserRole> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 用户ID\n\t */\n\t@Schema(description = \"用户id\")\n\tprivate Long userId;\n\n\t/**\n\t * 角色ID\n\t */\n\t@Schema(description = \"角色id\")\n\tprivate Long roleId;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteClientDetailsService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.feign;\n\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\n\n/**\n * 远程客户端详情服务接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@FeignClient(contextId = \"remoteClientDetailsService\", value = ServiceNameConstants.UPMS_SERVICE)\npublic interface RemoteClientDetailsService {\n\n\t/**\n\t * 通过clientId 查询客户端信息 (未登录，需要无token 内部调用)\n\t * @param clientId 用户名\n\t * @return R\n\t */\n\t@NoToken\n\t@GetMapping(\"/client/getClientDetailsById/{clientId}\")\n\tR<SysOauthClientDetails> getClientDetailsById(@PathVariable(\"clientId\") String clientId);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteDictService.java",
    "content": "package com.pig4cloud.pig.admin.api.feign;\n\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\n\nimport java.util.List;\n\n/**\n * 远程字典服务接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@FeignClient(contextId = \"remoteDictService\", value = ServiceNameConstants.UPMS_SERVICE)\npublic interface RemoteDictService {\n\n\t/**\n\t * 通过字典类型查找字典\n\t * @param type 字典类型\n\t * @return 同类型字典\n\t */\n\t@NoToken\n\t@GetMapping(\"/dict/remote/type/{type}\")\n\tR<List<SysDictItem>> getDictByType(@PathVariable(\"type\") String type);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteLogService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.feign;\n\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\n\n/**\n * 远程日志服务接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@FeignClient(contextId = \"remoteLogService\", value = ServiceNameConstants.UPMS_SERVICE)\npublic interface RemoteLogService {\n\n\t/**\n\t * 保存日志 (异步多线程调用，无token)\n\t * @param sysLog 日志实体\n\t * @return succes、false\n\t */\n\t@NoToken\n\t@PostMapping(\"/log/save\")\n\tR<Boolean> saveLog(@RequestBody SysLog sysLog);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteParamService.java",
    "content": "package com.pig4cloud.pig.admin.api.feign;\n\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\n\n/**\n * 远程参数服务接口\n * <p>\n * 通过Feign客户端调用UPMS服务获取参数配置\n * </p>\n *\n * @author lengleng\n * @date 2025/05/30\n * @see FeignClient\n */\n@FeignClient(contextId = \"remoteParamService\", value = ServiceNameConstants.UPMS_SERVICE)\npublic interface RemoteParamService {\n\n\t/**\n\t * 通过key 查询参数配置\n\t * @param key key\n\t * @NoToken 声明成内部调用，避免MQ 等无法调用\n\t */\n\t@NoToken\n\t@GetMapping(\"/param/publicValue/{key}\")\n\tR<String> getByKey(@PathVariable(\"key\") String key);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteTokenService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.api.feign;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Map;\n\n/**\n * 远程令牌服务接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@FeignClient(contextId = \"remoteTokenService\", value = ServiceNameConstants.AUTH_SERVICE)\npublic interface RemoteTokenService {\n\n\t/**\n\t * 分页查询token 信息\n\t * @param params 分页参数\n\t * @return page\n\t */\n\t@NoToken\n\t@PostMapping(\"/token/page\")\n\tR<Page> getTokenPage(@RequestBody Map<String, Object> params);\n\n\t/**\n\t * 根据token删除token信息\n\t * @param token 要删除的token\n\t * @return 删除操作结果，包含是否成功的布尔值\n\t */\n\t@NoToken\n\t@DeleteMapping(\"/token/remove/{token}\")\n\tR<Boolean> removeTokenById(@PathVariable(\"token\") String token);\n\n\t/**\n\t * 根据令牌查询用户信息\n\t * @param token 用户令牌\n\t * @return 包含用户信息的响应结果\n\t */\n\t@NoToken\n\t@GetMapping(\"/token/query-token\")\n\tR<Map<String, Object>> queryToken(@RequestParam(\"token\") String token);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteUserService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.feign;\n\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.common.core.constant.ServiceNameConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.feign.annotation.NoToken;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.cloud.openfeign.SpringQueryMap;\nimport org.springframework.web.bind.annotation.GetMapping;\n\n/**\n * 远程用户服务接口：提供用户信息查询功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@FeignClient(contextId = \"remoteUserService\", value = ServiceNameConstants.UPMS_SERVICE)\npublic interface RemoteUserService {\n\n\t/**\n\t * (未登录状态调用，需要加 @NoToken) 通过用户名查询用户、角色信息\n\t * @param user 用户查询对象\n\t * @return R\n\t */\n\t@NoToken\n\t@GetMapping(\"/user/info/query\")\n\tR<UserInfo> info(@SpringQueryMap UserDTO user);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/util/DictResolver.java",
    "content": "package com.pig4cloud.pig.admin.api.util;\n\nimport cn.hutool.core.lang.Assert;\nimport com.baomidou.mybatisplus.core.toolkit.CollectionUtils;\nimport com.baomidou.mybatisplus.core.toolkit.ObjectUtils;\nimport com.baomidou.mybatisplus.core.toolkit.StringPool;\nimport com.baomidou.mybatisplus.core.toolkit.StringUtils;\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport com.pig4cloud.pig.admin.api.feign.RemoteDictService;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\n\n/**\n * 字典解析工具类：提供字典数据的查询和解析功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@UtilityClass\npublic class DictResolver {\n\n\t/**\n\t * 根据字典类型获取所有字典项\n\t * @param type 字典类型\n\t * @return 字典数据项集合\n\t */\n\tpublic List<SysDictItem> getDictItemsByType(String type) {\n\t\tAssert.isTrue(StringUtils.isNotBlank(type), \"参数不合法\");\n\n\t\tRemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);\n\n\t\treturn remoteDictService.getDictByType(type).getData();\n\t}\n\n\t/**\n\t * 根据字典类型以及字典项字典值获取字典标签\n\t * @param type 字典类型\n\t * @param itemValue 字典项字典值\n\t * @return 字典项标签值\n\t */\n\tpublic String getDictItemLabel(String type, String itemValue) {\n\t\tAssert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), \"参数不合法\");\n\n\t\tSysDictItem sysDictItem = getDictItemByItemValue(type, itemValue);\n\n\t\treturn ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getLabel() : StringPool.EMPTY;\n\t}\n\n\t/**\n\t * 根据字典类型以及字典标签获取字典值\n\t * @param type 字典类型\n\t * @param itemLabel 字典数据标签\n\t * @return 字典数据项值\n\t */\n\tpublic String getDictItemValue(String type, String itemLabel) {\n\t\tAssert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), \"参数不合法\");\n\n\t\tSysDictItem sysDictItem = getDictItemByItemLabel(type, itemLabel);\n\n\t\treturn ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getItemValue() : StringPool.EMPTY;\n\t}\n\n\t/**\n\t * 根据字典类型以及字典值获取字典项\n\t * @param type 字典类型\n\t * @param itemValue 字典数据值\n\t * @return 字典数据项\n\t */\n\tpublic SysDictItem getDictItemByItemValue(String type, String itemValue) {\n\t\tAssert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), \"参数不合法\");\n\n\t\tList<SysDictItem> dictItemList = getDictItemsByType(type);\n\n\t\tif (CollectionUtils.isNotEmpty(dictItemList)) {\n\t\t\treturn dictItemList.stream().filter(item -> itemValue.equals(item.getItemValue())).findFirst().orElse(null);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * 根据字典类型以及字典标签获取字典项\n\t * @param type 字典类型\n\t * @param itemLabel 字典数据项标签\n\t * @return 字典数据项\n\t */\n\tpublic SysDictItem getDictItemByItemLabel(String type, String itemLabel) {\n\t\tAssert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), \"参数不合法\");\n\n\t\tList<SysDictItem> dictItemList = getDictItemsByType(type);\n\n\t\tif (CollectionUtils.isNotEmpty(dictItemList)) {\n\t\t\treturn dictItemList.stream().filter(item -> itemLabel.equals(item.getLabel())).findFirst().orElse(null);\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/util/ParamResolver.java",
    "content": "package com.pig4cloud.pig.admin.api.util;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.admin.api.feign.RemoteParamService;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 系统参数配置解析器工具类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@UtilityClass\npublic class ParamResolver {\n\n\t/**\n\t * 根据key 查询value 配置\n\t * @param key key\n\t * @param defaultVal 默认值\n\t * @return value\n\t */\n\tpublic Long getLong(String key, Long... defaultVal) {\n\t\treturn checkAndGet(key, Long.class, defaultVal);\n\t}\n\n\t/**\n\t * 根据key 查询value 配置\n\t * @param key key\n\t * @param defaultVal 默认值\n\t * @return value\n\t */\n\tpublic String getStr(String key, String... defaultVal) {\n\t\treturn checkAndGet(key, String.class, defaultVal);\n\t}\n\n\t/**\n\t * 根据key获取远程参数值并转换为指定类型\n\t * @param key 参数key\n\t * @param clazz 目标类型\n\t * @param defaultVal 默认值(可选，最多一个)\n\t * @param <T> 泛型类型\n\t * @return 转换后的参数值，未找到且无默认值时返回null\n\t * @throws IllegalArgumentException 参数不合法时抛出异常\n\t */\n\tprivate <T> T checkAndGet(String key, Class<T> clazz, T... defaultVal) {\n\t\t// 校验入参是否合法\n\t\tif (StrUtil.isBlank(key) || defaultVal.length > 1) {\n\t\t\tthrow new IllegalArgumentException(\"参数不合法\");\n\t\t}\n\n\t\tRemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);\n\n\t\tString result = remoteParamService.getByKey(key).getData();\n\n\t\tif (StrUtil.isNotBlank(result)) {\n\t\t\treturn Convert.convert(clazz, result);\n\t\t}\n\n\t\tif (defaultVal.length == 1) {\n\t\t\treturn Convert.convert(clazz, defaultVal.clone()[0]);\n\n\t\t}\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/DeptExcelVo.java",
    "content": "package com.pig4cloud.pig.admin.api.vo;\n\nimport cn.idev.excel.annotation.ExcelIgnore;\nimport cn.idev.excel.annotation.ExcelProperty;\nimport com.pig4cloud.plugin.excel.annotation.ExcelLine;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 部门导入导出\n */\n@Data\npublic class DeptExcelVo implements Serializable {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 导入时候回显行号\n\t */\n\t@ExcelLine\n\t@ExcelIgnore\n\tprivate Long lineNum;\n\n\t/**\n\t * 上级部门\n\t */\n\t@NotBlank(message = \"上级部门不能为空\")\n\t@ExcelProperty(\"上级部门\")\n\tprivate String parentName;\n\n\t/**\n\t * 部门名称\n\t */\n\t@NotBlank(message = \"部门名称不能为空\")\n\t@ExcelProperty(\"部门名称\")\n\tprivate String name;\n\n\t/**\n\t * 排序\n\t */\n\t@ExcelProperty(value = \"排序值\")\n\tprivate Integer sortOrder;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/PostExcelVO.java",
    "content": "package com.pig4cloud.pig.admin.api.vo;\n\nimport cn.idev.excel.annotation.ExcelIgnore;\nimport cn.idev.excel.annotation.ExcelProperty;\nimport cn.idev.excel.annotation.write.style.ColumnWidth;\nimport com.pig4cloud.plugin.excel.annotation.ExcelLine;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * 岗位excel 对应的实体\n *\n * @author fxz\n * @date 2022/3/21\n */\n@Data\n@ColumnWidth(30)\npublic class PostExcelVO implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 导入时候回显行号\n\t */\n\t@ExcelLine\n\t@ExcelIgnore\n\tprivate Long lineNum;\n\n\t/**\n\t * 主键ID\n\t */\n\t@ExcelProperty(\"岗位编号\")\n\tprivate Long postId;\n\n\t/**\n\t * 岗位名称\n\t */\n\t@NotBlank(message = \"岗位名称不能为空\")\n\t@ExcelProperty(\"岗位名称\")\n\tprivate String postName;\n\n\t/**\n\t * 岗位标识\n\t */\n\t@NotBlank(message = \"岗位标识不能为空\")\n\t@ExcelProperty(\"岗位标识\")\n\tprivate String postCode;\n\n\t/**\n\t * 岗位排序\n\t */\n\t@NotNull(message = \"岗位排序不能为空\")\n\t@ExcelProperty(\"岗位排序\")\n\tprivate Integer postSort;\n\n\t/**\n\t * 岗位描述\n\t */\n\t@NotBlank(message = \"岗位描述不能为空\")\n\t@ExcelProperty(value = \"岗位描述\")\n\tprivate String remark;\n\n\t/**\n\t * 创建时间\n\t */\n\t@ExcelProperty(value = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/PreLogVO.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.api.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n/**\n * @author lengleng\n * @date 2018/8/27 前端日志vo\n */\n@Data\n@Schema(description = \"前端日志展示对象\")\npublic class PreLogVO {\n\n\t/**\n\t * 请求url\n\t */\n\t@Schema(description = \"请求url\")\n\tprivate String url;\n\n\t/**\n\t * 请求耗时\n\t */\n\t@Schema(description = \"请求耗时\")\n\tprivate String time;\n\n\t/**\n\t * 请求用户\n\t */\n\t@Schema(description = \"请求用户\")\n\tprivate String user;\n\n\t/**\n\t * 请求结果\n\t */\n\t@Schema(description = \"请求结果0:成功9:失败\")\n\tprivate String type;\n\n\t/**\n\t * 请求传递参数\n\t */\n\t@Schema(description = \"请求传递参数\")\n\tprivate String message;\n\n\t/**\n\t * 异常信息\n\t */\n\t@Schema(description = \"异常信息\")\n\tprivate String stack;\n\n\t/**\n\t * 日志标题\n\t */\n\t@Schema(description = \"日志标题\")\n\tprivate String info;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/RoleExcelVO.java",
    "content": "package com.pig4cloud.pig.admin.api.vo;\n\nimport cn.idev.excel.annotation.ExcelIgnore;\nimport cn.idev.excel.annotation.ExcelProperty;\nimport cn.idev.excel.annotation.write.style.ColumnWidth;\nimport com.pig4cloud.plugin.excel.annotation.ExcelLine;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * 角色excel 对应的实体\n *\n * @author fxz\n * @date 2022/3/21\n */\n@Data\n@ColumnWidth(30)\npublic class RoleExcelVO implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 导入时候回显行号\n\t */\n\t@ExcelLine\n\t@ExcelIgnore\n\tprivate Long lineNum;\n\n\t/**\n\t * 主键ID\n\t */\n\t@ExcelProperty(\"角色编号\")\n\tprivate Long roleId;\n\n\t/**\n\t * 角色名称\n\t */\n\t@NotBlank(message = \"角色名称不能为空\")\n\t@ExcelProperty(\"角色名称\")\n\tprivate String roleName;\n\n\t/**\n\t * 角色标识\n\t */\n\t@NotBlank(message = \"角色标识不能为空\")\n\t@ExcelProperty(\"角色标识\")\n\tprivate String roleCode;\n\n\t/**\n\t * 角色描述\n\t */\n\t@NotBlank(message = \"角色描述不能为空\")\n\t@ExcelProperty(\"角色描述\")\n\tprivate String roleDesc;\n\n\t/**\n\t * 创建时间\n\t */\n\t@ExcelProperty(value = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/RoleVO.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.api.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n/**\n * @author lengleng\n * @date 2020/2/10\n */\n@Data\n@Schema(description = \"前端角色展示对象\")\npublic class RoleVO {\n\n\t/**\n\t * 角色id\n\t */\n\tprivate Long roleId;\n\n\t/**\n\t * 菜单列表\n\t */\n\tprivate String menuIds;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/TokenVo.java",
    "content": "package com.pig4cloud.pig.admin.api.vo;\n\nimport lombok.Data;\n\n/**\n * 前端展示令牌管理\n *\n * @author lengleng\n * @date 2022/6/2\n */\n@Data\npublic class TokenVo {\n\n\tprivate String id;\n\n\tprivate Long userId;\n\n\tprivate String clientId;\n\n\tprivate String username;\n\n\tprivate String accessToken;\n\n\tprivate String issuedAt;\n\n\tprivate String expiresAt;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserExcelVO.java",
    "content": "package com.pig4cloud.pig.admin.api.vo;\n\nimport cn.idev.excel.annotation.ExcelIgnore;\nimport cn.idev.excel.annotation.ExcelProperty;\nimport cn.idev.excel.annotation.write.style.ColumnWidth;\nimport com.pig4cloud.plugin.excel.annotation.DictTypeProperty;\nimport com.pig4cloud.plugin.excel.annotation.ExcelLine;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * 用户excel 对应的实体\n *\n * @author lengleng\n * @date 2021/8/4\n */\n@Data\n@ColumnWidth(30)\npublic class UserExcelVO implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 导入时候回显行号\n\t */\n\t@ExcelLine\n\t@ExcelIgnore\n\tprivate Long lineNum;\n\n\t/**\n\t * 主键ID\n\t */\n\t@ExcelProperty(\"用户编号\")\n\tprivate Long userId;\n\n\t/**\n\t * 用户名\n\t */\n\t@NotBlank(message = \"用户名不能为空\")\n\t@ExcelProperty(\"用户名\")\n\tprivate String username;\n\n\t/**\n\t * 手机号\n\t */\n\t@NotBlank(message = \"手机号不能为空\")\n\t@ExcelProperty(\"手机号\")\n\tprivate String phone;\n\n\t/**\n\t * 手机号\n\t */\n\t@NotBlank(message = \"昵称不能为空\")\n\t@ExcelProperty(\"昵称\")\n\tprivate String nickname;\n\n\t/**\n\t * 手机号\n\t */\n\t@NotBlank(message = \"姓名不能为空\")\n\t@ExcelProperty(\"姓名\")\n\tprivate String name;\n\n\t/**\n\t * 手机号\n\t */\n\t@NotBlank(message = \"邮箱不能为空\")\n\t@ExcelProperty(\"邮箱\")\n\tprivate String email;\n\n\t/**\n\t * 部门名称\n\t */\n\t@NotBlank(message = \"部门名称不能为空\")\n\t@ExcelProperty(\"部门名称\")\n\tprivate String deptName;\n\n\t/**\n\t * 角色列表\n\t */\n\t@NotBlank(message = \"角色不能为空\")\n\t@ExcelProperty(\"角色\")\n\tprivate String roleNameList;\n\n\t/**\n\t * 角色列表\n\t */\n\t@NotBlank(message = \"岗位不能为空\")\n\t@ExcelProperty(\"岗位名称\")\n\tprivate String postNameList;\n\n\t/**\n\t * 锁定标记\n\t */\n\t@ExcelProperty(\"锁定标记,0:正常,9:已锁定\")\n\t@DictTypeProperty(\"lock_flag\")\n\tprivate String lockFlag;\n\n\t/**\n\t * 创建时间\n\t */\n\t@ExcelProperty(value = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserVO.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.api.vo;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.pig4cloud.pig.admin.api.entity.SysDept;\nimport com.pig4cloud.pig.admin.api.entity.SysPost;\nimport com.pig4cloud.pig.admin.api.entity.SysRole;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * @author lengleng\n * @date 2017/10/29\n */\n@Data\n@Schema(description = \"前端用户展示对象\")\npublic class UserVO implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 主键ID\n\t */\n\t@Schema(description = \"主键\")\n\tprivate Long userId;\n\n\t/**\n\t * 用户名\n\t */\n\t@Schema(description = \"用户名\")\n\tprivate String username;\n\n\t/**\n\t * 密码\n\t */\n\t@JsonIgnore\n\tprivate String password;\n\n\t/**\n\t * 随机盐\n\t */\n\t@JsonIgnore\n\tprivate String salt;\n\n\t/**\n\t * 微信openid\n\t */\n\t@Schema(description = \"微信open id\")\n\tprivate String wxOpenid;\n\n\t/**\n\t * QQ openid\n\t */\n\t@Schema(description = \"qq open id\")\n\tprivate String qqOpenid;\n\n\t/**\n\t * gitee openid\n\t */\n\t@Schema(description = \"gitee open id\")\n\tprivate String giteeOpenId;\n\n\t/**\n\t * 开源中国 openid\n\t */\n\t@Schema(description = \"开源中国 open id\")\n\tprivate String oscOpenId;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@Schema(description = \"修改时间\")\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 0-正常，1-删除\n\t */\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n\t/**\n\t * 锁定标记\n\t */\n\t@Schema(description = \"锁定标记,0:正常,9:已锁定\")\n\tprivate String lockFlag;\n\n\t/**\n\t * 手机号\n\t */\n\t@Schema(description = \"手机号\")\n\tprivate String phone;\n\n\t/**\n\t * 头像\n\t */\n\t@Schema(description = \"头像\")\n\tprivate String avatar;\n\n\t/**\n\t * 部门名称\n\t */\n\t@Schema(description = \"所属部门名称\")\n\tprivate SysDept dept;\n\n\t/**\n\t * 角色列表\n\t */\n\t@Schema(description = \"拥有的角色列表\")\n\tprivate List<SysRole> roleList;\n\n\t/**\n\t * 岗位列表\n\t */\n\tprivate List<SysPost> postList;\n\n\t/**\n\t * 昵称\n\t */\n\t@Schema(description = \"昵称\")\n\tprivate String nickname;\n\n\t/**\n\t * 姓名\n\t */\n\t@Schema(description = \"姓名\")\n\tprivate String name;\n\n\t/**\n\t * 邮箱\n\t */\n\t@Schema(description = \"邮箱\")\n\tprivate String email;\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports",
    "content": "com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService\ncom.pig4cloud.pig.admin.api.feign.RemoteDictService\ncom.pig4cloud.pig.admin.api.feign.RemoteLogService\ncom.pig4cloud.pig.admin.api.feign.RemoteParamService\ncom.pig4cloud.pig.admin.api.feign.RemoteTokenService\ncom.pig4cloud.pig.admin.api.feign.RemoteUserService\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-upms-biz\n\nARG JAR_FILE=target/pig-upms-biz.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 4000\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-upms</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-upms-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <description>pig 通用用户权限管理系统业务处理模块</description>\n\n    <dependencies>\n        <!--upms api、model 模块-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-upms-api</artifactId>\n        </dependency>\n        <!--文件管理-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-oss</artifactId>\n        </dependency>\n        <!--feign 调用-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-feign</artifactId>\n        </dependency>\n        <!--安全模块-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-security</artifactId>\n        </dependency>\n        <!--日志处理-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-log</artifactId>\n        </dependency>\n        <!--接口文档-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-swagger</artifactId>\n        </dependency>\n        <!-- orm 模块-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.mysql</groupId>\n            <artifactId>mysql-connector-j</artifactId>\n        </dependency>\n        <!--注册中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n        </dependency>\n        <!--配置中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n        </dependency>\n        <!-- 短信下发 -->\n        <dependency>\n\t\t\t<groupId>org.dromara.sms4j</groupId>\n\t\t\t<artifactId>sms4j-spring-boot-starter</artifactId>\n        </dependency>\n        <!--xss 过滤-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-xss</artifactId>\n        </dependency>\n        <!--undertow容器-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-undertow</artifactId>\n        </dependency>\n    </dependencies>\n\n    <profiles>\n        <profile>\n            <id>boot</id>\n        </profile>\n        <profile>\n            <id>cloud</id>\n            <activation>\n                <!-- 默认环境 -->\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-maven-plugin</artifactId>\n                    </plugin>\n                    <plugin>\n                        <groupId>io.fabric8</groupId>\n                        <artifactId>docker-maven-plugin</artifactId>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n    <build>\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <filtering>true</filtering>\n                <excludes>\n                    <exclude>**/*.xlsx</exclude>\n                    <exclude>**/*.xls</exclude>\n                </excludes>\n            </resource>\n            <resource>\n                <directory>src/main/resources</directory>\n                <filtering>false</filtering>\n                <includes>\n                    <include>**/*.xlsx</include>\n                    <include>**/*.xls</include>\n                </includes>\n            </resource>\n        </resources>\n    </build>\n</project>\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/PigAdminApplication.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin;\n\nimport com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;\nimport com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;\nimport com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n/**\n * 用户统一管理系统\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@EnablePigDoc(value = \"admin\")\n@EnablePigFeignClients\n@EnablePigResourceServer\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class PigAdminApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigAdminApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysClientController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport com.pig4cloud.pig.admin.service.SysOauthClientDetailsService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 客户端管理模块前端控制器\n *\n * @author lengleng\n * @date 2025/05/30\n * @since 2018-05-15\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/client\")\n@Tag(description = \"client\", name = \"客户端管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysClientController {\n\n\tprivate final SysOauthClientDetailsService clientDetailsService;\n\n\t/**\n\t * 通过客户端ID查询客户端详情\n\t * @param clientId 客户端ID\n\t * @return 包含客户端详情的响应对象\n\t */\n\t@GetMapping(\"/{clientId}\")\n\t@Operation(summary = \"通过客户端ID查询客户端详情\", description = \"通过客户端ID查询客户端详情\")\n\tpublic R getByClientId(@PathVariable String clientId) {\n\t\tSysOauthClientDetails details = clientDetailsService\n\t\t\t.getOne(Wrappers.<SysOauthClientDetails>lambdaQuery().eq(SysOauthClientDetails::getClientId, clientId));\n\t\treturn R.ok(details);\n\t}\n\n\t/**\n\t * 分页查询系统终端信息\n\t * @param page 分页参数对象\n\t * @param sysOauthClientDetails 系统终端查询条件\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询系统终端信息\", description = \"分页查询系统终端信息\")\n\tpublic R getClientPage(@ParameterObject Page page, @ParameterObject SysOauthClientDetails sysOauthClientDetails) {\n\t\tLambdaQueryWrapper<SysOauthClientDetails> wrapper = Wrappers.<SysOauthClientDetails>lambdaQuery()\n\t\t\t.like(StrUtil.isNotBlank(sysOauthClientDetails.getClientId()), SysOauthClientDetails::getClientId,\n\t\t\t\t\tsysOauthClientDetails.getClientId())\n\t\t\t.like(StrUtil.isNotBlank(sysOauthClientDetails.getClientSecret()), SysOauthClientDetails::getClientSecret,\n\t\t\t\t\tsysOauthClientDetails.getClientSecret());\n\t\treturn R.ok(clientDetailsService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 添加客户端终端\n\t * @param clientDetails 客户端详情实体\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"添加终端\")\n\t@PostMapping\n\t@HasPermission(\"sys_client_add\")\n\t@Operation(summary = \"添加客户端终端\", description = \"添加客户端终端\")\n\tpublic R saveClient(@Valid @RequestBody SysOauthClientDetails clientDetails) {\n\t\treturn R.ok(clientDetailsService.saveClient(clientDetails));\n\t}\n\n\t/**\n\t * 根据ID列表批量删除终端\n\t * @param ids 要删除的终端ID数组\n\t * @return 操作结果，成功返回success\n\t */\n\t@SysLog(\"删除终端\")\n\t@DeleteMapping\n\t@HasPermission(\"sys_client_del\")\n\t@Operation(summary = \"根据ID列表批量删除终端\", description = \"根据ID列表批量删除终端\")\n\tpublic R removeById(@RequestBody Long[] ids) {\n\t\tclientDetailsService.removeBatchByIds(CollUtil.toList(ids));\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 编辑终端信息\n\t * @param clientDetails 终端实体信息\n\t * @return 操作结果\n\t */\n\t@SysLog(\"编辑终端\")\n\t@PutMapping\n\t@HasPermission(\"sys_client_edit\")\n\t@Operation(summary = \"编辑终端信息\", description = \"编辑终端信息\")\n\tpublic R updateClient(@Valid @RequestBody SysOauthClientDetails clientDetails) {\n\t\treturn R.ok(clientDetailsService.updateClientById(clientDetails));\n\t}\n\n\t/**\n\t * 根据客户端ID获取客户端详情\n\t * @param clientId 客户端ID\n\t * @return 包含客户端详情的响应结果\n\t */\n\t@Inner\n\t@GetMapping(\"/getClientDetailsById/{clientId}\")\n\t@Operation(summary = \"根据客户端ID获取客户端详情\", description = \"根据客户端ID获取客户端详情\")\n\tpublic R getClientDetailsById(@PathVariable String clientId) {\n\t\treturn R.ok(clientDetailsService.getOne(\n\t\t\t\tWrappers.<SysOauthClientDetails>lambdaQuery().eq(SysOauthClientDetails::getClientId, clientId), false));\n\t}\n\n\t/**\n\t * 同步缓存字典\n\t * @return 操作结果\n\t */\n\t@SysLog(\"同步终端\")\n\t@PutMapping(\"/sync\")\n\t@Operation(summary = \"同步缓存字典\", description = \"同步缓存字典\")\n\tpublic R syncClient() {\n\t\treturn clientDetailsService.syncClientCache();\n\t}\n\n\t/**\n\t * 导出客户端信息到Excel\n\t * @param sysOauthClientDetails 客户端查询条件\n\t * @return 符合条件的客户端列表\n\t */\n\t@ResponseExcel\n\t@SysLog(\"导出excel\")\n\t@GetMapping(\"/export\")\n\t@Operation(summary = \"导出客户端信息到Excel\", description = \"导出客户端信息到Excel\")\n\tpublic List<SysOauthClientDetails> exportClients(SysOauthClientDetails sysOauthClientDetails) {\n\t\treturn clientDetailsService.list(Wrappers.query(sysOauthClientDetails));\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysDeptController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport com.pig4cloud.pig.admin.api.entity.SysDept;\nimport com.pig4cloud.pig.admin.api.vo.DeptExcelVo;\nimport com.pig4cloud.pig.admin.service.SysDeptService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.plugin.excel.annotation.RequestExcel;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 部门管理前端控制器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/dept\")\n@Tag(description = \"dept\", name = \"部门管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysDeptController {\n\n\tprivate final SysDeptService sysDeptService;\n\n\t/**\n\t * 通过ID查询部门信息\n\t * @param id 部门ID\n\t * @return 包含部门信息的响应对象\n\t */\n\t@GetMapping(\"/{id}\")\n\t@Operation(summary = \"通过ID查询部门信息\", description = \"通过ID查询部门信息\")\n\tpublic R getById(@PathVariable Long id) {\n\t\treturn R.ok(sysDeptService.getById(id));\n\t}\n\n\t/**\n\t * 查询全部部门列表\n\t * @return 包含全部部门列表的响应结果\n\t */\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"查询全部部门列表\", description = \"查询全部部门列表\")\n\tpublic R listDepts() {\n\t\treturn R.ok(sysDeptService.list());\n\t}\n\n\t/**\n\t * 获取树形菜单\n\t * @param deptName 部门名称\n\t * @return 包含树形菜单的响应结果\n\t */\n\t@GetMapping(value = \"/tree\")\n\t@Operation(summary = \"获取树形菜单\", description = \"获取树形菜单\")\n\tpublic R getDeptTree(String deptName) {\n\t\treturn R.ok(sysDeptService.getDeptTree(deptName));\n\t}\n\n\t/**\n\t * 保存部门信息\n\t * @param sysDept 部门实体\n\t * @return 操作结果\n\t */\n\t@SysLog(\"添加部门\")\n\t@PostMapping\n\t@HasPermission(\"sys_dept_add\")\n\t@Operation(summary = \"保存部门信息\", description = \"保存部门信息\")\n\tpublic R saveDept(@Valid @RequestBody SysDept sysDept) {\n\t\treturn R.ok(sysDeptService.save(sysDept));\n\t}\n\n\t/**\n\t * 根据ID删除部门\n\t * @param id 部门ID\n\t * @return 操作结果，成功返回true，失败返回false\n\t */\n\t@SysLog(\"删除部门\")\n\t@DeleteMapping(\"/{id}\")\n\t@HasPermission(\"sys_dept_del\")\n\t@Operation(summary = \"根据ID删除部门\", description = \"根据ID删除部门\")\n\tpublic R removeById(@PathVariable Long id) {\n\t\treturn R.ok(sysDeptService.removeDeptById(id));\n\t}\n\n\t/**\n\t * 编辑部门信息\n\t * @param sysDept 部门实体对象\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"编辑部门\")\n\t@PutMapping\n\t@HasPermission(\"sys_dept_edit\")\n\t@Operation(summary = \"编辑部门信息\", description = \"编辑部门信息\")\n\tpublic R updateDept(@Valid @RequestBody SysDept sysDept) {\n\t\tsysDept.setUpdateTime(LocalDateTime.now());\n\t\treturn R.ok(sysDeptService.updateById(sysDept));\n\t}\n\n\t/**\n\t * 获取部门子级列表\n\t * @param deptId 部门ID\n\t * @return 包含子级部门列表的响应结果\n\t */\n\t@GetMapping(value = \"/getDescendantList/{deptId}\")\n\t@Operation(summary = \"获取部门子级列表\", description = \"获取部门子级列表\")\n\tpublic R getDescendantList(@PathVariable Long deptId) {\n\t\treturn R.ok(sysDeptService.listDescendants(deptId));\n\t}\n\n\t/**\n\t * 导出部门数据\n\t * @return 部门数据列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@Operation(summary = \"导出部门数据\", description = \"导出部门数据\")\n\tpublic List<DeptExcelVo> exportDepts() {\n\t\treturn sysDeptService.exportDepts();\n\t}\n\n\t/**\n\t * 导入部门信息\n\t * @param excelVOList 部门Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果\n\t */\n\t@PostMapping(\"import\")\n\t@Operation(summary = \"导入部门信息\", description = \"导入部门信息\")\n\tpublic R importDept(@RequestExcel List<DeptExcelVo> excelVOList, BindingResult bindingResult) {\n\t\treturn sysDeptService.importDept(excelVOList, bindingResult);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysDictController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysDict;\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport com.pig4cloud.pig.admin.service.SysDictItemService;\nimport com.pig4cloud.pig.admin.service.SysDictService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 字典表前端控制器\n *\n * @author lengleng\n * @date 2025/05/30\n * @since 2019-03-19\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/dict\")\n@Tag(description = \"dict\", name = \"字典管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysDictController {\n\n\tprivate final SysDictService sysDictService;\n\n\tprivate final SysDictItemService sysDictItemService;\n\n\t/**\n\t * 通过ID查询字典信息\n\t * @param id 字典ID\n\t * @return 包含字典信息的响应对象\n\t */\n\t@GetMapping(\"/details/{id}\")\n\t@Operation(summary = \"通过ID查询字典信息\", description = \"通过ID查询字典信息\")\n\tpublic R getById(@PathVariable Long id) {\n\t\treturn R.ok(sysDictService.getById(id));\n\t}\n\n\t/**\n\t * 查询字典详细信息\n\t * @param query 字典查询条件对象\n\t * @return 包含字典信息的响应结果\n\t */\n\t@GetMapping(\"/details\")\n\t@Operation(summary = \"查询字典详细信息\", description = \"查询字典详细信息\")\n\tpublic R getDetails(@ParameterObject SysDict query) {\n\t\treturn R.ok(sysDictService.getOne(Wrappers.query(query), false));\n\t}\n\n\t/**\n\t * 分页查询字典信息\n\t * @param page 分页对象\n\t * @param sysDict 字典查询条件\n\t * @return 包含分页结果的响应对象\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询字典信息\", description = \"分页查询字典信息\")\n\tpublic R<IPage> getDictPage(@ParameterObject Page page, @ParameterObject SysDict sysDict) {\n\t\treturn R.ok(sysDictService.page(page,\n\t\t\t\tWrappers.<SysDict>lambdaQuery()\n\t\t\t\t\t.eq(StrUtil.isNotBlank(sysDict.getSystemFlag()), SysDict::getSystemFlag, sysDict.getSystemFlag())\n\t\t\t\t\t.like(StrUtil.isNotBlank(sysDict.getDictType()), SysDict::getDictType, sysDict.getDictType())));\n\t}\n\n\t/**\n\t * 保存字典信息\n\t * @param sysDict 字典信息对象\n\t * @return 操作结果，包含保存的字典信息\n\t */\n\t@SysLog(\"添加字典\")\n\t@PostMapping\n\t@Operation(summary = \"保存字典信息\", description = \"保存字典信息\")\n\t@PreAuthorize(\"@pms.hasPermission('sys_dict_add')\")\n\tpublic R saveDict(@Valid @RequestBody SysDict sysDict) {\n\t\tsysDictService.save(sysDict);\n\t\treturn R.ok(sysDict);\n\t}\n\n\t/**\n\t * 删除字典并清除字典缓存\n\t * @param ids 字典ID数组\n\t * @return 操作结果\n\t */\n\t@SysLog(\"删除字典\")\n\t@DeleteMapping\n\t@PreAuthorize(\"@pms.hasPermission('sys_dict_del')\")\n\t@Operation(summary = \"删除字典并清除字典缓存\", description = \"删除字典并清除字典缓存\")\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true)\n\tpublic R removeById(@RequestBody Long[] ids) {\n\t\treturn R.ok(sysDictService.removeDictByIds(ids));\n\t}\n\n\t/**\n\t * 修改字典信息\n\t * @param sysDict 字典信息\n\t * @return 操作结果 success/false\n\t */\n\t@PutMapping\n\t@SysLog(\"修改字典\")\n\t@PreAuthorize(\"@pms.hasPermission('sys_dict_edit')\")\n\t@Operation(summary = \"修改字典信息\", description = \"修改字典信息\")\n\tpublic R updateDict(@Valid @RequestBody SysDict sysDict) {\n\t\treturn sysDictService.updateDict(sysDict);\n\t}\n\n\t/**\n\t * 分页查询字典列表\n\t * @param name 字典类型名称或描述\n\t * @return 包含字典列表的响应结果\n\t */\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"分页查询字典列表\", description = \"分页查询字典列表\")\n\tpublic R listDicts(String name) {\n\t\treturn R.ok(sysDictService.list(Wrappers.<SysDict>lambdaQuery()\n\t\t\t.like(StrUtil.isNotBlank(name), SysDict::getDictType, name)\n\t\t\t.or()\n\t\t\t.like(StrUtil.isNotBlank(name), SysDict::getDescription, name)));\n\t}\n\n\t/**\n\t * 分页查询字典项\n\t * @param page 分页对象\n\t * @param sysDictItem 字典项查询条件\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/item/page\")\n\t@Operation(summary = \"分页查询字典项\", description = \"分页查询字典项\")\n\tpublic R getDictItemPage(Page page, SysDictItem sysDictItem) {\n\t\treturn R.ok(sysDictItemService.page(page, Wrappers.query(sysDictItem)));\n\t}\n\n\t/**\n\t * 通过id查询字典项详情\n\t * @param id 字典项id\n\t * @return 包含字典项详情的响应结果\n\t */\n\t@GetMapping(\"/item/details/{id}\")\n\t@Operation(summary = \"通过id查询字典项详情\", description = \"通过id查询字典项详情\")\n\tpublic R getDictItemById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(sysDictItemService.getById(id));\n\t}\n\n\t/**\n\t * 获取字典项详情\n\t * @param query 字典项查询条件\n\t * @return 包含字典项详情的响应结果\n\t */\n\t@GetMapping(\"/item/details\")\n\t@Operation(summary = \"获取字典项详情\", description = \"获取字典项详情\")\n\tpublic R getDictItemDetails(SysDictItem query) {\n\t\treturn R.ok(sysDictItemService.getOne(Wrappers.query(query), false));\n\t}\n\n\t/**\n\t * 新增字典项\n\t * @param sysDictItem 字典项对象\n\t * @return 操作结果\n\t */\n\t@SysLog(\"新增字典项\")\n\t@PostMapping(\"/item\")\n\t@Operation(summary = \"新增字典项\", description = \"新增字典项\")\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true)\n\tpublic R saveDictItem(@RequestBody SysDictItem sysDictItem) {\n\t\treturn R.ok(sysDictItemService.save(sysDictItem));\n\t}\n\n\t/**\n\t * 修改字典项\n\t * @param sysDictItem 要修改的字典项对象\n\t * @return 操作结果\n\t */\n\t@SysLog(\"修改字典项\")\n\t@PutMapping(\"/item\")\n\t@Operation(summary = \"修改字典项\", description = \"修改字典项\")\n\tpublic R updateDictItem(@RequestBody SysDictItem sysDictItem) {\n\t\treturn sysDictItemService.updateDictItem(sysDictItem);\n\t}\n\n\t/**\n\t * 通过id删除字典项\n\t * @param id 字典项id\n\t * @return 操作结果\n\t */\n\t@SysLog(\"通过id删除字典项\")\n\t@DeleteMapping(\"/item/{id}\")\n\t@Operation(summary = \"通过id删除字典项\", description = \"通过id删除字典项\")\n\tpublic R removeDictItemById(@PathVariable Long id) {\n\t\treturn sysDictItemService.removeDictItem(id);\n\t}\n\n\t/**\n\t * 同步字典缓存\n\t * @return 操作结果\n\t */\n\t@SysLog(\"同步字典缓存\")\n\t@PutMapping(\"/sync\")\n\t@Operation(summary = \"同步字典缓存\", description = \"同步字典缓存\")\n\tpublic R syncDict() {\n\t\treturn sysDictService.syncDictCache();\n\t}\n\n\t/**\n\t * 导出字典项数据\n\t * @param sysDictItem 字典项查询条件\n\t * @return 符合条件的字典项列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@Operation(summary = \"导出字典项数据\", description = \"导出字典项数据\")\n\tpublic List<SysDictItem> exportDictItems(SysDictItem sysDictItem) {\n\t\treturn sysDictItemService.list(Wrappers.query(sysDictItem));\n\t}\n\n\t/**\n\t * 通过字典类型查找字典\n\t * @param type 类型\n\t * @return 同类型字典\n\t */\n\t@GetMapping(\"/type/{type}\")\n\t@Operation(summary = \"通过字典类型查找字典\", description = \"通过字典类型查找字典\")\n\t@Cacheable(value = CacheConstants.DICT_DETAILS, key = \"#type\", unless = \"#result.data.isEmpty()\")\n\tpublic R<List<SysDictItem>> getDictByType(@PathVariable String type) {\n\t\treturn R.ok(sysDictItemService.list(Wrappers.<SysDictItem>query().lambda().eq(SysDictItem::getDictType, type)));\n\t}\n\n\t/**\n\t * 通过字典类型查找字典 (针对feign调用) TODO: 兼容性方案，代码重复\n\t * @param type 类型\n\t * @return 同类型字典\n\t */\n\t@Inner\n\t@GetMapping(\"/remote/type/{type}\")\n\t@Operation(summary = \"通过字典类型查找字典(针对feign调用)\", description = \"通过字典类型查找字典(针对feign调用)\", hidden = true)\n\t@Cacheable(value = CacheConstants.DICT_DETAILS, key = \"#type\", unless = \"#result.data.isEmpty()\")\n\tpublic R<List<SysDictItem>> getRemoteDictByType(@PathVariable String type) {\n\t\treturn R.ok(sysDictItemService.list(Wrappers.<SysDictItem>query().lambda().eq(SysDictItem::getDictType, type)));\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysFileController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysFile;\nimport com.pig4cloud.pig.admin.service.SysFileService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.AllArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.IOException;\n\n/**\n * 文件管理控制器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/sys-file\")\n@Tag(description = \"sys-file\", name = \"文件管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysFileController {\n\n\tprivate final SysFileService sysFileService;\n\n\t/**\n\t * 分页查询文件信息\n\t * @param page 分页参数对象\n\t * @param sysFile 文件查询条件对象\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询\", description = \"分页查询\")\n\tpublic R getFilePage(@ParameterObject Page page, @ParameterObject SysFile sysFile) {\n\t\tLambdaQueryWrapper<SysFile> wrapper = Wrappers.<SysFile>lambdaQuery()\n\t\t\t.like(StrUtil.isNotBlank(sysFile.getOriginal()), SysFile::getOriginal, sysFile.getOriginal());\n\t\treturn R.ok(sysFileService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 通过id删除文件管理\n\t * @param ids 要删除的文件id数组\n\t * @return 操作结果\n\t */\n\t@SysLog(\"删除文件管理\")\n\t@DeleteMapping\n\t@HasPermission(\"sys_file_del\")\n\t@Operation(summary = \"通过id删除文件管理\", description = \"通过id删除文件管理\")\n\tpublic R removeById(@RequestBody Long[] ids) {\n\t\tfor (Long id : ids) {\n\t\t\tsysFileService.removeFile(id);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 上传文件\n\t * @param file 上传的文件资源\n\t * @return 包含文件路径的R对象，格式为(/admin/bucketName/filename)\n\t */\n\t@PostMapping(value = \"/upload\")\n\t@Operation(summary = \"上传文件\", description = \"上传文件\")\n\tpublic R upload(@RequestPart(\"file\") MultipartFile file) {\n\t\treturn sysFileService.uploadFile(file);\n\t}\n\n\t/**\n\t * 获取文件并写入响应流\n\t * @param bucket 桶名称\n\t * @param fileName 文件路径/名称\n\t * @param response HTTP响应对象\n\t */\n\t@Inner(false)\n\t@GetMapping(\"/{bucket}/{fileName}\")\n\t@Operation(summary = \"获取文件并写入响应流\", description = \"获取文件并写入响应流\")\n\tpublic void file(@PathVariable String bucket, @PathVariable String fileName, HttpServletResponse response) {\n\t\tsysFileService.getFile(bucket, fileName, response);\n\t}\n\n\t/**\n\t * 获取本地resources目录下的文件并写入响应流\n\t * @param fileName 文件名称\n\t * @param response HTTP响应对象，用于输出文件内容\n\t * @throws IOException 文件操作异常\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/local/file/{fileName}\")\n\t@Operation(summary = \"获取本地resources目录下的文件并写入响应流\", description = \"获取本地resources目录下的文件并写入响应流\")\n\tpublic void localFile(@PathVariable String fileName, HttpServletResponse response) {\n\t\tClassPathResource resource = new ClassPathResource(\"file/\" + fileName);\n\t\tresponse.setContentType(\"application/octet-stream; charset=UTF-8\");\n\t\tIoUtil.copy(resource.getInputStream(), response.getOutputStream());\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysLogController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.dto.SysLogDTO;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.admin.service.SysLogService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 系统日志前端控制器\n *\n * @author lengleng\n * @since 2017-11-20\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/log\")\n@Tag(description = \"log\", name = \"日志管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysLogController {\n\n\tprivate final SysLogService sysLogService;\n\n\t/**\n\t * 分页查询系统日志\n\t * @param page 分页参数对象\n\t * @param sysLog 系统日志查询条件\n\t * @return 包含分页结果的响应对象\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询系统日志\", description = \"分页查询系统日志\")\n\tpublic R getLogPage(@ParameterObject Page page, @ParameterObject SysLogDTO sysLog) {\n\t\treturn R.ok(sysLogService.getLogPage(page, sysLog));\n\t}\n\n\t/**\n\t * 批量删除日志\n\t * @param ids 要删除的日志ID数组\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@DeleteMapping\n\t@HasPermission(\"sys_log_del\")\n\t@Operation(summary = \"批量删除日志\", description = \"批量删除日志\")\n\tpublic R removeByIds(@RequestBody Long[] ids) {\n\t\treturn R.ok(sysLogService.removeBatchByIds(CollUtil.toList(ids)));\n\t}\n\n\t/**\n\t * 保存日志\n\t * @param sysLog 日志实体\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@Inner\n\t@PostMapping(\"/save\")\n\t@Operation(summary = \"保存日志\", description = \"保存日志\")\n\tpublic R saveLog(@Valid @RequestBody SysLog sysLog) {\n\t\treturn R.ok(sysLogService.saveLog(sysLog));\n\t}\n\n\t/**\n\t * 导出系统日志到Excel表格\n\t * @param sysLog 系统日志查询条件DTO\n\t * @return 符合查询条件的系统日志列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"sys_log_export\")\n\t@Operation(summary = \"导出系统日志到Excel表格\", description = \"导出系统日志到Excel表格\")\n\tpublic List<SysLog> exportLogs(SysLogDTO sysLog) {\n\t\treturn sysLogService.listLogs(sysLog);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysMenuController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport io.swagger.v3.oas.annotations.Operation;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.pig4cloud.pig.admin.api.entity.SysMenu;\nimport com.pig4cloud.pig.admin.service.SysMenuService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.util.SecurityUtils;\n\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\n\n/**\n * 菜单管理控制器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/menu\")\n@Tag(description = \"menu\", name = \"菜单管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysMenuController {\n\n\tprivate final SysMenuService sysMenuService;\n\n\t/**\n\t * 获取当前用户的树形菜单集合\n\t * @param type 菜单类型\n\t * @param parentId 父菜单ID\n\t * @return 包含菜单数据的响应对象\n\t */\n\t@GetMapping\n\t@Operation(summary = \"获取当前用户的树形菜单集合\", description = \"获取当前用户的树形菜单集合\")\n\tpublic R getUserMenu(String type, Long parentId) {\n\t\t// 获取符合条件的菜单\n\t\tSet<SysMenu> all = new HashSet<>();\n\t\tSecurityUtils.getRoles().forEach(roleId -> all.addAll(sysMenuService.findMenuByRoleId(roleId)));\n\t\treturn R.ok(sysMenuService.filterMenu(all, type, parentId));\n\t}\n\n\t/**\n\t * 获取树形菜单集合\n\t * @param parentId 父节点ID\n\t * @param menuName 菜单名称\n\t * @param type 菜单类型\n\t * @return 包含树形菜单的响应结果\n\t */\n\t@GetMapping(value = \"/tree\")\n\t@Operation(summary = \"获取树形菜单集合\", description = \"获取树形菜单集合\")\n\tpublic R getMenuTree(Long parentId, String menuName, String type) {\n\t\treturn R.ok(sysMenuService.getMenuTree(parentId, menuName, type));\n\t}\n\n\t/**\n\t * 根据角色ID获取菜单树\n\t * @param roleId 角色ID\n\t * @return 包含菜单ID列表的响应结果\n\t */\n\t@GetMapping(\"/tree/{roleId}\")\n\t@Operation(summary = \"根据角色ID获取菜单树\", description = \"根据角色ID获取菜单树\")\n\tpublic R getRoleTree(@PathVariable Long roleId) {\n\t\treturn R.ok(sysMenuService.findMenuByRoleId(roleId).stream().map(SysMenu::getMenuId).toList());\n\t}\n\n\t/**\n\t * 通过ID查询菜单的详细信息\n\t * @param id 菜单ID\n\t * @return 包含菜单详细信息的响应对象\n\t */\n\t@GetMapping(\"/{id}\")\n\t@Operation(summary = \"通过ID查询菜单的详细信息\", description = \"通过ID查询菜单的详细信息\")\n\tpublic R getById(@PathVariable Long id) {\n\t\treturn R.ok(sysMenuService.getById(id));\n\t}\n\n\t/**\n\t * 新增菜单\n\t * @param sysMenu 菜单信息\n\t * @return 操作结果\n\t */\n\t@SysLog(\"新增菜单\")\n\t@PostMapping\n\t@HasPermission(\"sys_menu_add\")\n\t@Operation(summary = \"新增菜单\", description = \"新增菜单\")\n\tpublic R saveMenu(@Valid @RequestBody SysMenu sysMenu) {\n\t\tsysMenuService.save(sysMenu);\n\t\treturn R.ok(sysMenu);\n\t}\n\n\t/**\n\t * 根据菜单ID删除菜单\n\t * @param id 要删除的菜单ID\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"根据菜单ID删除菜单\")\n\t@DeleteMapping(\"/{id}\")\n\t@HasPermission(\"sys_menu_del\")\n\t@Operation(summary = \"根据菜单ID删除菜单\", description = \"根据菜单ID删除菜单\")\n\tpublic R removeById(@PathVariable Long id) {\n\t\treturn sysMenuService.removeMenuById(id);\n\t}\n\n\t/**\n\t * 更新菜单\n\t * @param sysMenu 菜单对象\n\t * @return 操作结果\n\t */\n\t@SysLog(\"更新菜单\")\n\t@PutMapping\n\t@HasPermission(\"sys_menu_edit\")\n\t@Operation(summary = \"更新菜单\", description = \"更新菜单\")\n\tpublic R updateMenu(@Valid @RequestBody SysMenu sysMenu) {\n\t\treturn R.ok(sysMenuService.updateMenuById(sysMenu));\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysMobileController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport com.pig4cloud.pig.admin.service.SysMobileService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.AllArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * 手机管理模块控制器：提供手机验证码相关服务\n *\n * @author lengleng\n * @date 2018/11/14\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/mobile\")\n@Tag(description = \"mobile\", name = \"手机管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysMobileController {\n\n\tprivate final SysMobileService mobileService;\n\n\t/**\n\t * 发送短信验证码\n\t * @param mobile 手机号码\n\t * @return 操作结果\n\t */\n\t@Inner(value = false)\n\t@GetMapping(\"/{mobile}\")\n\t@Operation(summary = \"发送短信验证码\", description = \"发送短信验证码\")\n\tpublic R sendSmsCode(@PathVariable String mobile) {\n\t\treturn mobileService.sendSmsCode(mobile);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysPostController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysPost;\nimport com.pig4cloud.pig.admin.api.vo.PostExcelVO;\nimport com.pig4cloud.pig.admin.service.SysPostService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.plugin.excel.annotation.RequestExcel;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 岗位信息表管理控制器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/post\")\n@Tag(description = \"post\", name = \"岗位信息表管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysPostController {\n\n\tprivate final SysPostService sysPostService;\n\n\t/**\n\t * 获取岗位列表\n\t * @return 包含岗位列表的响应结果\n\t */\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"获取岗位列表\", description = \"获取岗位列表\")\n\tpublic R<List<SysPost>> listPosts() {\n\t\treturn R.ok(sysPostService.list(Wrappers.emptyWrapper()));\n\t}\n\n\t/**\n\t * 分页查询岗位信息\n\t * @param page 分页参数对象\n\t * @param sysPost 岗位查询条件对象\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@HasPermission(\"sys_post_view\")\n\t@Operation(description = \"分页查询岗位信息\", summary = \"分页查询岗位信息\")\n\tpublic R getPostPage(@ParameterObject Page page, @ParameterObject SysPost sysPost) {\n\t\treturn R.ok(sysPostService.page(page, Wrappers.<SysPost>lambdaQuery()\n\t\t\t.like(StrUtil.isNotBlank(sysPost.getPostName()), SysPost::getPostName, sysPost.getPostName())));\n\t}\n\n\t/**\n\t * 通过id查询岗位信息\n\t * @param postId 岗位id\n\t * @return 包含岗位信息的响应结果\n\t */\n\t@HasPermission(\"sys_post_view\")\n\t@GetMapping(\"/details/{postId}\")\n\t@Operation(description = \"通过id查询岗位信息\", summary = \"通过id查询岗位信息\")\n\tpublic R getById(@PathVariable(\"postId\") Long postId) {\n\t\treturn R.ok(sysPostService.getById(postId));\n\t}\n\n\t/**\n\t * 查询岗位详细信息\n\t * @param query 查询条件\n\t * @return 统一响应结果R，包含查询到的岗位信息\n\t */\n\t@GetMapping(\"/details\")\n\t@HasPermission(\"sys_post_view\")\n\t@Operation(description = \"查询角色信息\", summary = \"查询角色信息\")\n\tpublic R getDetails(SysPost query) {\n\t\treturn R.ok(sysPostService.getOne(Wrappers.query(query), false));\n\t}\n\n\t/**\n\t * 新增岗位信息\n\t * @param sysPost 岗位信息对象\n\t * @return 操作结果\n\t */\n\t@PostMapping\n\t@SysLog(\"新增岗位信息表\")\n\t@HasPermission(\"sys_post_add\")\n\t@Operation(description = \"新增岗位信息表\", summary = \"新增岗位信息表\")\n\tpublic R savePost(@RequestBody SysPost sysPost) {\n\t\treturn R.ok(sysPostService.save(sysPost));\n\t}\n\n\t/**\n\t * 修改岗位信息\n\t * @param sysPost 岗位信息对象\n\t * @return 操作结果\n\t */\n\t@PutMapping\n\t@SysLog(\"修改岗位信息表\")\n\t@HasPermission(\"sys_post_edit\")\n\t@Operation(description = \"修改岗位信息表\", summary = \"修改岗位信息表\")\n\tpublic R updatePost(@RequestBody SysPost sysPost) {\n\t\treturn R.ok(sysPostService.updateById(sysPost));\n\t}\n\n\t/**\n\t * 通过id批量删除岗位信息\n\t * @param ids 岗位id数组\n\t * @return 统一返回结果\n\t */\n\t@DeleteMapping\n\t@SysLog(\"通过id删除岗位信息表\")\n\t@HasPermission(\"sys_post_del\")\n\t@Operation(description = \"通过id删除岗位信息表\", summary = \"通过id删除岗位信息表\")\n\tpublic R removeById(@RequestBody Long[] ids) {\n\t\treturn R.ok(sysPostService.removeBatchByIds(CollUtil.toList(ids)));\n\t}\n\n\t/**\n\t * 导出岗位信息到Excel表格\n\t * @return 岗位信息Excel文件流\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"sys_post_export\")\n\t@Operation(description = \"导出岗位信息到Excel表格\", summary = \"导出岗位信息到Excel表格\")\n\tpublic List<PostExcelVO> exportPosts() {\n\t\treturn sysPostService.listPosts();\n\t}\n\n\t/**\n\t * 导入岗位信息\n\t * @param excelVOList 岗位Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果\n\t */\n\t@PostMapping(\"/import\")\n\t@HasPermission(\"sys_post_export\")\n\t@Operation(description = \"导入岗位信息\", summary = \"导入岗位信息\")\n\tpublic R importRole(@RequestExcel List<PostExcelVO> excelVOList, BindingResult bindingResult) {\n\t\treturn sysPostService.importPost(excelVOList, bindingResult);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysPublicParamController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysPublicParam;\nimport com.pig4cloud.pig.admin.service.SysPublicParamService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.AllArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 公共参数控制器：提供公共参数的增删改查及同步功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/param\")\n@Tag(description = \"param\", name = \"公共参数配置管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysPublicParamController {\n\n\tprivate final SysPublicParamService sysPublicParamService;\n\n\t/**\n\t * 根据key查询公共参数值\n\t * @param publicKey 公共参数key\n\t * @return 公共参数值\n\t */\n\t@Inner(value = false)\n\t@Operation(description = \"查询公共参数值\", summary = \"根据key查询公共参数值\")\n\t@GetMapping(\"/publicValue/{publicKey}\")\n\tpublic R publicKey(@PathVariable(\"publicKey\") String publicKey) {\n\t\treturn R.ok(sysPublicParamService.getParamValue(publicKey));\n\t}\n\n\t/**\n\t * 分页查询系统公共参数\n\t * @param page 分页对象\n\t * @param sysPublicParam 公共参数查询条件\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(description = \"分页查询\", summary = \"分页查询\")\n\tpublic R getParamPage(@ParameterObject Page page, @ParameterObject SysPublicParam sysPublicParam) {\n\t\tLambdaUpdateWrapper<SysPublicParam> wrapper = Wrappers.<SysPublicParam>lambdaUpdate()\n\t\t\t.like(StrUtil.isNotBlank(sysPublicParam.getPublicName()), SysPublicParam::getPublicName,\n\t\t\t\t\tsysPublicParam.getPublicName())\n\t\t\t.like(StrUtil.isNotBlank(sysPublicParam.getPublicKey()), SysPublicParam::getPublicKey,\n\t\t\t\t\tsysPublicParam.getPublicKey())\n\t\t\t.eq(StrUtil.isNotBlank(sysPublicParam.getSystemFlag()), SysPublicParam::getSystemFlag,\n\t\t\t\t\tsysPublicParam.getSystemFlag());\n\n\t\treturn R.ok(sysPublicParamService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 通过id查询公共参数\n\t * @param publicId 公共参数id\n\t * @return 包含查询结果的响应对象\n\t */\n\t@Operation(description = \"通过id查询公共参数\", summary = \"通过id查询公共参数\")\n\t@GetMapping(\"/details/{publicId}\")\n\tpublic R getById(@PathVariable(\"publicId\") Long publicId) {\n\t\treturn R.ok(sysPublicParamService.getById(publicId));\n\t}\n\n\t/**\n\t * 获取系统公共参数详情\n\t * @param param 系统公共参数查询对象\n\t * @return 包含查询结果的响应对象\n\t */\n\t@GetMapping(\"/details\")\n\t@Operation(description = \"获取系统公共参数详情\", summary = \"获取系统公共参数详情\")\n\tpublic R getDetail(@ParameterObject SysPublicParam param) {\n\t\treturn R.ok(sysPublicParamService.getOne(Wrappers.query(param), false));\n\t}\n\n\t/**\n\t * 新增公共参数\n\t * @param sysPublicParam 公共参数对象\n\t * @return 操作结果\n\t */\n\t@PostMapping\n\t@SysLog(\"新增公共参数\")\n\t@Operation(description = \"新增公共参数\", summary = \"新增公共参数\")\n\t@HasPermission(\"sys_syspublicparam_add\")\n\tpublic R saveParam(@RequestBody SysPublicParam sysPublicParam) {\n\t\treturn R.ok(sysPublicParamService.save(sysPublicParam));\n\t}\n\n\t/**\n\t * 修改公共参数\n\t * @param sysPublicParam 公共参数对象\n\t * @return 操作结果\n\t */\n\t@PutMapping\n\t@SysLog(\"修改公共参数\")\n\t@HasPermission(\"sys_syspublicparam_edit\")\n\t@Operation(description = \"修改公共参数\", summary = \"修改公共参数\")\n\tpublic R updateParam(@RequestBody SysPublicParam sysPublicParam) {\n\t\treturn sysPublicParamService.updateParam(sysPublicParam);\n\t}\n\n\t/**\n\t * 通过id数组删除公共参数\n\t * @param ids 要删除的公共参数id数组\n\t * @return 操作结果\n\t */\n\t@DeleteMapping\n\t@SysLog(\"删除公共参数\")\n\t@HasPermission(\"sys_syspublicparam_del\")\n\t@Operation(description = \"删除公共参数\", summary = \"删除公共参数\")\n\tpublic R removeById(@RequestBody Long[] ids) {\n\t\treturn R.ok(sysPublicParamService.removeParamByIds(ids));\n\t}\n\n\t/**\n\t * 导出excel 表格\n\t * @return\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"sys_syspublicparam_edit\")\n\t@Operation(description = \"导出公共参数\", summary = \"导出公共参数\")\n\tpublic List<SysPublicParam> exportParams() {\n\t\treturn sysPublicParamService.list();\n\t}\n\n\t/**\n\t * 同步参数到缓存\n\t * @return 操作结果\n\t */\n\t@SysLog(\"同步参数\")\n\t@PutMapping(\"/sync\")\n\t@HasPermission(\"sys_syspublicparam_edit\")\n\t@Operation(description = \"同步参数到缓存\", summary = \"同步参数到缓存\")\n\tpublic R syncParam() {\n\t\treturn sysPublicParamService.syncParamCache();\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysRegisterController.java",
    "content": "package com.pig4cloud.pig.admin.controller;\n\nimport com.pig4cloud.pig.admin.api.dto.RegisterUserDTO;\nimport com.pig4cloud.pig.admin.service.SysUserService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\n\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * 用户注册控制器：提供用户注册功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@RequestMapping(\"/register\")\n@RequiredArgsConstructor\n@Tag(description = \"register\", name = \"注册用户管理模块\")\n@ConditionalOnProperty(name = \"register.user\", matchIfMissing = true)\npublic class SysRegisterController {\n\n\tprivate final SysUserService userService;\n\n\t/**\n\t * 注册用户\n\t * @param registerUserDTO 注册用户信息DTO\n\t * @return 注册结果封装对象\n\t */\n\t@Inner(value = false)\n\t@SysLog(\"注册用户\")\n\t@PostMapping(\"/user\")\n\t@Operation(summary = \"注册用户\", description = \"注册用户\")\n\tpublic R<Boolean> registerUser(@RequestBody RegisterUserDTO registerUserDTO) {\n\t\treturn userService.registerUser(registerUserDTO);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysRoleController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.entity.SysRole;\nimport com.pig4cloud.pig.admin.api.vo.RoleExcelVO;\nimport com.pig4cloud.pig.admin.api.vo.RoleVO;\nimport com.pig4cloud.pig.admin.service.SysRoleService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.plugin.excel.annotation.RequestExcel;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 角色管理控制器：提供角色相关的增删改查及权限管理功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/role\")\n@Tag(description = \"role\", name = \"角色管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysRoleController {\n\n\tprivate final SysRoleService sysRoleService;\n\n\t/**\n\t * 通过ID查询角色信息\n\t * @param id 角色ID\n\t * @return 包含角色信息的响应对象\n\t */\n\t@GetMapping(\"/details/{id}\")\n\t@Operation(summary = \"通过ID查询角色信息\", description = \"通过ID查询角色信息\")\n\tpublic R getById(@PathVariable Long id) {\n\t\treturn R.ok(sysRoleService.getById(id));\n\t}\n\n\t/**\n\t * 查询角色详细信息\n\t * @param query 角色查询条件对象\n\t * @return 包含角色信息的响应结果\n\t */\n\t@GetMapping(\"/details\")\n\t@Operation(summary = \"查询角色详细信息\", description = \"查询角色详细信息\")\n\tpublic R getDetails(@ParameterObject SysRole query) {\n\t\treturn R.ok(sysRoleService.getOne(Wrappers.query(query), false));\n\t}\n\n\t/**\n\t * 添加角色\n\t * @param sysRole 角色信息\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"添加角色\")\n\t@PostMapping\n\t@HasPermission(\"sys_role_add\")\n\t@Operation(summary = \"添加角色\", description = \"添加角色\")\n\t@CacheEvict(value = CacheConstants.ROLE_DETAILS, allEntries = true)\n\tpublic R saveRole(@Valid @RequestBody SysRole sysRole) {\n\t\treturn R.ok(sysRoleService.save(sysRole));\n\t}\n\n\t/**\n\t * 修改角色信息\n\t * @param sysRole 角色信息\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"修改角色信息\")\n\t@PutMapping\n\t@HasPermission(\"sys_role_edit\")\n\t@Operation(summary = \"修改角色信息\", description = \"修改角色信息\")\n\t@CacheEvict(value = CacheConstants.ROLE_DETAILS, allEntries = true)\n\tpublic R updateRole(@Valid @RequestBody SysRole sysRole) {\n\t\treturn R.ok(sysRoleService.updateById(sysRole));\n\t}\n\n\t/**\n\t * 根据ID数组删除角色\n\t * @param ids 角色ID数组\n\t * @return 操作结果\n\t */\n\t@SysLog(\"删除角色\")\n\t@DeleteMapping\n\t@HasPermission(\"sys_role_del\")\n\t@Operation(summary = \"根据ID数组删除角色\", description = \"根据ID数组删除角色\")\n\t@CacheEvict(value = CacheConstants.ROLE_DETAILS, allEntries = true)\n\tpublic R removeById(@RequestBody Long[] ids) {\n\t\treturn R.ok(sysRoleService.removeRoleByIds(ids));\n\t}\n\n\t/**\n\t * 获取角色列表\n\t * @return 包含角色列表的响应结果\n\t */\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"获取角色列表\", description = \"获取角色列表\")\n\tpublic R listRoles() {\n\t\treturn R.ok(sysRoleService.list(Wrappers.emptyWrapper()));\n\t}\n\n\t/**\n\t * 分页查询角色信息\n\t * @param page 分页对象\n\t * @param role 查询条件对象\n\t * @return 包含分页结果的响应对象\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询角色信息\", description = \"分页查询角色信息\")\n\tpublic R getRolePage(Page page, SysRole role) {\n\t\treturn R.ok(sysRoleService.page(page, Wrappers.<SysRole>lambdaQuery()\n\t\t\t.like(StrUtil.isNotBlank(role.getRoleName()), SysRole::getRoleName, role.getRoleName())));\n\t}\n\n\t/**\n\t * 更新角色菜单\n\t * @param roleVo 角色VO对象\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"更新角色菜单\")\n\t@PutMapping(\"/menu\")\n\t@HasPermission(\"sys_role_perm\")\n\t@Operation(summary = \"更新角色菜单\", description = \"更新角色菜单\")\n\tpublic R saveRoleMenus(@RequestBody RoleVO roleVo) {\n\t\treturn R.ok(sysRoleService.updateRoleMenus(roleVo));\n\t}\n\n\t/**\n\t * 通过角色ID列表查询角色信息\n\t * @param roleIdList 角色ID列表\n\t * @return 包含查询结果的响应对象\n\t */\n\t@PostMapping(\"/getRoleList\")\n\t@Operation(summary = \"通过角色ID列表查询角色信息\", description = \"通过角色ID列表查询角色信息\")\n\tpublic R getRoleList(@RequestBody List<Long> roleIdList) {\n\t\treturn R.ok(sysRoleService.listRolesByRoleIds(roleIdList, CollUtil.join(roleIdList, StrUtil.UNDERLINE)));\n\t}\n\n\t/**\n\t * 导出角色数据到Excel表格\n\t * @return 角色数据列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"sys_role_export\")\n\t@Operation(summary = \"导出角色数据到Excel表格\", description = \"导出角色数据到Excel表格\")\n\tpublic List<RoleExcelVO> exportRoles() {\n\t\treturn sysRoleService.listRoles();\n\t}\n\n\t/**\n\t * 导入角色\n\t * @param excelVOList 角色Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果\n\t */\n\t@PostMapping(\"/import\")\n\t@HasPermission(\"sys_role_export\")\n\t@Operation(summary = \"导入角色数据\", description = \"导入角色数据\")\n\tpublic R importRole(@RequestExcel List<RoleExcelVO> excelVOList, BindingResult bindingResult) {\n\t\treturn sysRoleService.importRole(excelVOList, bindingResult);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysSystemInfoController.java",
    "content": "package com.pig4cloud.pig.admin.controller;\n\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Strings;\nimport org.springframework.data.redis.connection.RedisServerCommands;\nimport org.springframework.data.redis.core.RedisCallback;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.*;\n\n/**\n * 系统监控控制器：提供系统监控相关接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@RequestMapping(\"/system\")\n@RequiredArgsConstructor\n@Tag(description = \"system\", name = \"系统监控管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysSystemInfoController {\n\n\t/**\n\t * 获取Redis缓存监控信息\n\t * @return 包含Redis信息、数据库大小和命令统计的响应结果\n\t */\n\t@GetMapping(\"/cache\")\n\t@Operation(summary = \"获取Redis缓存监控信息\", description = \"获取Redis缓存监控信息\")\n\tpublic R cache() {\n\t\tProperties info = RedisUtils.execute(RedisServerCommands::info);\n\t\tProperties commandStats = RedisUtils.execute(connection -> connection.serverCommands().info(\"commandstats\"));\n\t\tObject dbSize = RedisUtils.execute((RedisCallback<Object>) RedisServerCommands::dbSize);\n\n\t\tif (commandStats == null) {\n\t\t\treturn R.failed(\"获取异常\");\n\t\t}\n\n\t\tMap<String, Object> result = new HashMap<>(3);\n\t\tresult.put(\"info\", info);\n\t\tresult.put(\"dbSize\", dbSize);\n\n\t\tList<Map<String, String>> pieList = new ArrayList<>();\n\t\tcommandStats.stringPropertyNames().forEach(key -> {\n\t\t\tMap<String, String> data = new HashMap<>(2);\n\t\t\tString property = commandStats.getProperty(key);\n\t\t\tdata.put(\"name\", Strings.CS.removeStart(key, \"cmdstat_\"));\n\t\t\tdata.put(\"value\", StringUtils.substringBetween(property, \"calls=\", \",usec\"));\n\t\t\tpieList.add(data);\n\t\t});\n\n\t\tresult.put(\"commandStats\", pieList);\n\t\treturn R.ok(result);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysTokenController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport com.pig4cloud.pig.admin.api.feign.RemoteTokenService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.AllArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Map;\n\n/**\n * 令牌管理控制器：提供令牌的分页查询和删除功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/sys-token\")\n@Tag(description = \"token\", name = \"令牌管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysTokenController {\n\n\tprivate final RemoteTokenService remoteTokenService;\n\n\t/**\n\t * 获取分页token信息\n\t * @param params 请求参数集合\n\t * @return 包含token分页信息的响应结果\n\t */\n\t@PostMapping(\"/page\")\n\t@HasPermission(\"sys_token_del\")\n\t@Operation(summary = \"获取分页token信息\", description = \"获取分页token信息\")\n\tpublic R getTokenPage(@RequestBody Map<String, Object> params) {\n\t\treturn remoteTokenService.getTokenPage(params);\n\t}\n\n\t/**\n\t * 根据token数组删除token\n\t * @param tokens 需要删除的token数组\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"删除用户token\")\n\t@DeleteMapping(\"/delete\")\n\t@HasPermission(\"sys_token_del\")\n\t@Operation(summary = \"删除用户token\", description = \"删除用户token\")\n\tpublic R removeById(@RequestBody String[] tokens) {\n\t\tfor (String token : tokens) {\n\t\t\tremoteTokenService.removeTokenById(token);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysUserController.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.controller;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.admin.api.entity.SysUser;\nimport com.pig4cloud.pig.admin.api.vo.UserExcelVO;\nimport com.pig4cloud.pig.admin.service.SysUserService;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.pig.common.security.util.SecurityUtils;\nimport com.pig4cloud.plugin.excel.annotation.RequestExcel;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.AllArgsConstructor;\nimport org.springdoc.core.annotations.ParameterObject;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 用户管理控制器\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/user\")\n@Tag(description = \"user\", name = \"用户管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysUserController {\n\n\tprivate final SysUserService userService;\n\n\t/**\n\t * 查询用户信息\n\t * @param userDTO 用户信息查询参数\n\t * @return 包含用户信息的R对象\n\t */\n\t@Inner\n\t@GetMapping(value = { \"/info/query\" })\n\t@Operation(summary = \"查询用户信息\", description = \"查询用户信息\")\n\tpublic R info(UserDTO userDTO) {\n\t\treturn userService.getUserInfo(userDTO);\n\t}\n\n\t/**\n\t * 获取当前登录用户的全部信息\n\t * @return 包含用户信息的响应结果\n\t */\n\t@GetMapping(value = { \"/info\" })\n\t@Operation(summary = \"获取当前登录用户的全部信息\", description = \"获取当前登录用户的全部信息\")\n\tpublic R info() {\n\t\tString username = SecurityUtils.getUser().getUsername();\n\t\tUserDTO userDTO = new UserDTO();\n\t\tuserDTO.setUsername(username);\n\t\t// 获取用户信息，不返回数据库密码字段\n\t\tR<UserInfo> userInfoR = userService.getUserInfo(userDTO);\n\t\tif (userInfoR.getData() != null) {\n\t\t\tuserInfoR.getData().setPassword(null);\n\t\t}\n\t\treturn userInfoR;\n\t}\n\n\t/**\n\t * 通过ID查询用户信息\n\t * @param id 用户ID\n\t * @return 包含用户信息的响应对象\n\t */\n\t@GetMapping(\"/details/{id}\")\n\t@Operation(summary = \"通过ID查询用户信息\", description = \"通过ID查询用户信息\")\n\tpublic R user(@PathVariable Long id) {\n\t\treturn R.ok(userService.getUserById(id));\n\t}\n\n\t/**\n\t * 查询用户详细信息\n\t * @param query 用户查询条件对象\n\t * @return 包含查询结果的响应对象，用户不存在时返回null\n\t */\n\t@Inner(value = false)\n\t@GetMapping(\"/details\")\n\t@Operation(summary = \"查询用户详细信息\", description = \"查询用户详细信息\")\n\tpublic R getDetails(@ParameterObject SysUser query) {\n\t\tSysUser sysUser = userService.getOne(Wrappers.query(query), false);\n\t\treturn R.ok(sysUser == null ? null : CommonConstants.SUCCESS);\n\t}\n\n\t/**\n\t * 删除用户信息\n\t * @param ids 用户ID数组\n\t * @return 操作结果\n\t */\n\t@SysLog(\"删除用户信息\")\n\t@DeleteMapping\n\t@HasPermission(\"sys_user_del\")\n\t@Operation(summary = \"根据ID删除用户\", description = \"根据ID删除用户\")\n\tpublic R userDel(@RequestBody Long[] ids) {\n\t\treturn R.ok(userService.removeUserByIds(ids));\n\t}\n\n\t/**\n\t * 添加用户\n\t * @param userDto 用户信息DTO\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"添加用户\")\n\t@PostMapping\n\t@HasPermission(\"sys_user_add\")\n\t@Operation(summary = \"添加用户\", description = \"添加用户\")\n\tpublic R saveUser(@RequestBody UserDTO userDto) {\n\t\treturn R.ok(userService.saveUser(userDto));\n\t}\n\n\t/**\n\t * 更新用户信息\n\t * @param userDto 用户信息DTO对象\n\t * @return 包含操作结果的R对象\n\t */\n\t@SysLog(\"更新用户信息\")\n\t@PutMapping\n\t@HasPermission(\"sys_user_edit\")\n\t@Operation(summary = \"更新用户信息\", description = \"更新用户信息\")\n\tpublic R updateUser(@Valid @RequestBody UserDTO userDto) {\n\t\treturn R.ok(userService.updateUser(userDto));\n\t}\n\n\t/**\n\t * 分页查询用户\n\t * @param page 参数集\n\t * @param userDTO 查询参数列表\n\t * @return 用户集合\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询用户\", description = \"分页查询用户\")\n\tpublic R getUserPage(@ParameterObject Page page, @ParameterObject UserDTO userDTO) {\n\t\treturn R.ok(userService.getUsersWithRolePage(page, userDTO));\n\t}\n\n\t/**\n\t * 修改个人信息\n\t * @param userDto 用户信息传输对象\n\t * @return 操作结果，成功返回success，失败返回false\n\t */\n\t@SysLog(\"修改个人信息\")\n\t@PutMapping(\"/edit\")\n\t@Operation(summary = \"修改个人信息\", description = \"修改个人信息\")\n\tpublic R updateUserInfo(@Valid @RequestBody UserDTO userDto) {\n\t\treturn userService.updateUserInfo(userDto);\n\t}\n\n\t/**\n\t * 导出用户数据到Excel表格\n\t * @param userDTO 用户查询条件\n\t * @return 用户数据列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"sys_user_export\")\n\t@Operation(summary = \"导出用户数据到Excel表格\", description = \"导出用户数据到Excel表格\")\n\tpublic List exportUsers(UserDTO userDTO) {\n\t\treturn userService.listUsers(userDTO);\n\t}\n\n\t/**\n\t * 导入用户信息\n\t * @param excelVOList 用户Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果\n\t */\n\t@PostMapping(\"/import\")\n\t@HasPermission(\"sys_user_export\")\n\t@Operation(summary = \"导入用户信息\", description = \"导入用户信息\")\n\tpublic R importUser(@RequestExcel List<UserExcelVO> excelVOList, BindingResult bindingResult) {\n\t\treturn userService.importUsers(excelVOList, bindingResult);\n\t}\n\n\t/**\n\t * 锁定指定用户\n\t * @param username 用户名\n\t * @return 操作结果\n\t */\n\t@PutMapping(\"/lock/{username}\")\n\t@Operation(summary = \"锁定指定用户\", description = \"锁定指定用户\")\n\tpublic R lockUser(@PathVariable String username) {\n\t\treturn userService.lockUser(username);\n\t}\n\n\t/**\n\t * 修改当前用户密码\n\t * @param userDto 用户数据传输对象，包含新密码等信息\n\t * @return 操作结果\n\t */\n\t@PutMapping(\"/password\")\n\t@Operation(summary = \"修改当前用户密码\", description = \"修改当前用户密码\")\n\tpublic R password(@RequestBody UserDTO userDto) {\n\t\tString username = SecurityUtils.getUser().getUsername();\n\t\tuserDto.setUsername(username);\n\t\treturn userService.changePassword(userDto);\n\t}\n\n\t/**\n\t * 检查密码是否符合要求\n\t * @param password 待检查的密码\n\t * @return 检查结果\n\t */\n\t@PostMapping(\"/check\")\n\t@Operation(summary = \"检查密码是否符合要求\", description = \"检查密码是否符合要求\")\n\tpublic R check(String password) {\n\t\treturn userService.checkPassword(password);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysDeptMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysDept;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 部门管理 Mapper 接口\n *\n * @author lengleng\n * @since 2018-01-20\n */\n@Mapper\npublic interface SysDeptMapper extends BaseMapper<SysDept> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysDictItemMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 系统字典项数据访问接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Mapper\npublic interface SysDictItemMapper extends BaseMapper<SysDictItem> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysDictMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysDict;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 字典表 Mapper 接口\n *\n * @author lengleng\n * @date 2025/06/27\n */\n@Mapper\npublic interface SysDictMapper extends BaseMapper<SysDict> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysFileMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysFile;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 系统文件映射接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Mapper\npublic interface SysFileMapper extends BaseMapper<SysFile> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysLogMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 系统日志表 Mapper 接口\n *\n * @author lengleng\n */\n@Mapper\npublic interface SysLogMapper extends BaseMapper<SysLog> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysMenuMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysMenu;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * <p>\n * 菜单权限表 Mapper 接口\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Mapper\npublic interface SysMenuMapper extends BaseMapper<SysMenu> {\n\n\t/**\n\t * 通过角色编号查询菜单\n\t * @param roleId 角色ID\n\t * @return\n\t */\n\tList<SysMenu> listMenusByRoleId(Long roleId);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysOauthClientDetailsMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 系统OAuth客户端详情 Mapper接口\n *\n * @author lengleng\n * @date 2025/06/27\n */\n@Mapper\npublic interface SysOauthClientDetailsMapper extends BaseMapper<SysOauthClientDetails> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysPostMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysPost;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 岗位信息表 Mapper 接口\n *\n * @author lengleng\n * @date 2025/06/27\n */\n@Mapper\npublic interface SysPostMapper extends BaseMapper<SysPost> {\n\n\t/**\n\t * 通过用户ID，查询岗位信息\n\t * @param userId 用户id\n\t * @return 岗位信息\n\t */\n\tList<SysPost> listPostsByUserId(Long userId);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysPublicParamMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysPublicParam;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 公共参数配置\n *\n * @author Lucky\n * @date 2019-04-29\n */\n@Mapper\npublic interface SysPublicParamMapper extends BaseMapper<SysPublicParam> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysRoleMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysRole;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Mapper\npublic interface SysRoleMapper extends BaseMapper<SysRole> {\n\n\t/**\n\t * 通过用户ID查询角色信息\n\t * @param userId 用户ID\n\t * @return 角色信息列表\n\t */\n\tList<SysRole> listRolesByUserId(Long userId);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysRoleMenuMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysRoleMenu;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 角色菜单表 Mapper 接口\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Mapper\npublic interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysUserMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.entity.SysUser;\nimport com.pig4cloud.pig.admin.api.vo.UserVO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * <p>\n * 用户表 Mapper 接口\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Mapper\npublic interface SysUserMapper extends BaseMapper<SysUser> {\n\n\t/**\n\t * 根据用户DTO获取用户VO\n\t * @param userDTO 用户查询条件DTO\n\t * @return 用户信息VO\n\t */\n\tUserVO getUser(@Param(\"query\") UserDTO userDTO);\n\n\t/**\n\t * 分页查询用户信息（含角色）\n\t * @param page 分页参数\n\t * @param userDTO 用户查询条件\n\t * @return 分页用户信息列表\n\t */\n\tIPage<UserVO> getUsersPage(Page page, @Param(\"query\") UserDTO userDTO);\n\n\t/**\n\t * 查询用户列表\n\t * @param userDTO 查询条件\n\t * @return 用户VO列表\n\t */\n\tList<UserVO> listUsers(@Param(\"query\") UserDTO userDTO);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysUserPostMapper.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysUserPost;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * 用户岗位 Mapper 接口\n * </p>\n *\n * @author fxz\n * @since 2022/3/19\n */\n@Mapper\npublic interface SysUserPostMapper extends BaseMapper<SysUserPost> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysUserRoleMapper.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.admin.api.entity.SysUserRole;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * 用户角色表 Mapper 接口\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Mapper\npublic interface SysUserRoleMapper extends BaseMapper<SysUserRole> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysDeptService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport cn.hutool.core.lang.tree.Tree;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysDept;\nimport com.pig4cloud.pig.admin.api.vo.DeptExcelVo;\nimport com.pig4cloud.pig.common.core.util.R;\nimport org.springframework.validation.BindingResult;\n\nimport java.util.List;\n\n/**\n * 部门管理服务接口\n *\n * @author lengleng\n * @since 2018-01-20\n */\npublic interface SysDeptService extends IService<SysDept> {\n\n\t/**\n\t * 查询部门树菜单\n\t * @param deptName 部门名称\n\t * @return 部门树结构\n\t */\n\tList<Tree<Long>> getDeptTree(String deptName);\n\n\t/**\n\t * 根据部门ID删除部门\n\t * @param id 要删除的部门ID\n\t * @return 删除操作是否成功，成功返回true，失败返回false\n\t */\n\tBoolean removeDeptById(Long id);\n\n\t/**\n\t * 导出部门Excel数据列表\n\t * @return 部门Excel数据列表\n\t */\n\tList<DeptExcelVo> exportDepts();\n\n\t/**\n\t * 导入部门数据\n\t * @param excelVOList 部门Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果\n\t */\n\tR importDept(List<DeptExcelVo> excelVOList, BindingResult bindingResult);\n\n\t/**\n\t * 获取指定部门的所有后代部门列表\n\t * @param deptId 部门ID\n\t * @return 后代部门列表，如果不存在则返回空列表\n\t */\n\tList<SysDept> listDescendants(Long deptId);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysDictItemService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport com.pig4cloud.pig.common.core.util.R;\n\n/**\n * 字典项服务接口\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic interface SysDictItemService extends IService<SysDictItem> {\n\n\t/**\n\t * 删除字典项\n\t * @param id 字典项ID\n\t * @return 操作结果\n\t */\n\tR removeDictItem(Long id);\n\n\t/**\n\t * 更新字典项\n\t * @param item 需要更新的字典项\n\t * @return 操作结果\n\t */\n\tR updateDictItem(SysDictItem item);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysDictService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysDict;\nimport com.pig4cloud.pig.common.core.util.R;\n\n/**\n * 字典表服务接口 提供字典数据的增删改查及缓存同步功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic interface SysDictService extends IService<SysDict> {\n\n\t/**\n\t * 根据ID列表删除字典\n\t * @param ids 要删除的字典ID数组\n\t * @return 操作结果\n\t */\n\tR removeDictByIds(Long[] ids);\n\n\t/**\n\t * 更新字典\n\t * @param sysDict 要更新的字典对象\n\t * @return 操作结果\n\t */\n\tR updateDict(SysDict sysDict);\n\n\t/**\n\t * 同步字典缓存（清空缓存）\n\t * @return 操作结果\n\t */\n\tR syncDictCache();\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysFileService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysFile;\nimport com.pig4cloud.pig.common.core.util.R;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.multipart.MultipartFile;\n\n/**\n * 文件管理服务接口\n * <p>\n * 提供文件上传、获取、删除等操作\n * </p>\n *\n * @author Luckly\n * @date 2019-06-18 17:18:42\n */\npublic interface SysFileService extends IService<SysFile> {\n\n\t/**\n\t * 上传文件\n\t * @param file 要上传的文件\n\t * @return 包含文件信息的响应结果，失败时返回错误信息\n\t */\n\tR uploadFile(MultipartFile file);\n\n\t/**\n\t * 从指定存储桶中获取文件并写入HTTP响应流\n\t * @param bucket 存储桶名称\n\t * @param fileName 文件名\n\t * @param response HTTP响应对象\n\t */\n\tvoid getFile(String bucket, String fileName, HttpServletResponse response);\n\n\t/**\n\t * 根据ID删除文件\n\t * @param id 文件ID\n\t * @return 删除是否成功，文件不存在时返回false\n\t */\n\tBoolean removeFile(Long id);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysLogService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.dto.SysLogDTO;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\n\nimport java.util.List;\n\n/**\n * <p>\n * 日志表 服务类\n * </p>\n *\n * @author lengleng\n * @since 2017-11-20\n */\npublic interface SysLogService extends IService<SysLog> {\n\n\t/**\n\t * 分页查询系统日志\n\t * @param page 分页对象\n\t * @param sysLog 系统日志\n\t * @return 系统日志分页数据\n\t */\n\tPage getLogPage(Page page, SysLogDTO sysLog);\n\n\t/**\n\t * 保存日志\n\t * @param sysLog 日志实体\n\t * @return Boolean\n\t */\n\tBoolean saveLog(SysLog sysLog);\n\n\t/**\n\t * 查询日志列表\n\t * @param sysLog 查询条件\n\t * @return 日志列表\n\t */\n\tList<SysLog> listLogs(SysLogDTO sysLog);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysMenuService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport cn.hutool.core.lang.tree.Tree;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysMenu;\nimport com.pig4cloud.pig.common.core.util.R;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 菜单权限服务接口\n * <p>\n * 提供菜单权限相关的服务方法，包括查询、删除、更新和构建菜单树等操作\n * </p>\n *\n * @author lengleng\n * @date 2025/06/27\n */\npublic interface SysMenuService extends IService<SysMenu> {\n\n\t/**\n\t * 通过角色编号查询URL 权限\n\t * @param roleId 角色ID\n\t * @return 菜单列表\n\t */\n\tList<SysMenu> findMenuByRoleId(Long roleId);\n\n\t/**\n\t * 级联删除菜单\n\t * @param id 菜单ID\n\t * @return 成功、失败\n\t */\n\tR removeMenuById(Long id);\n\n\t/**\n\t * 更新菜单信息\n\t * @param sysMenu 菜单信息\n\t * @return 成功、失败\n\t */\n\tBoolean updateMenuById(SysMenu sysMenu);\n\n\t/**\n\t * 构建树查询\n\t * @param parentId 父级菜单ID\n\t * @param menuName 菜单名称\n\t * @param type 类型\n\t * @return 菜单树\n\t */\n\tList<Tree<Long>> getMenuTree(Long parentId, String menuName, String type);\n\n\t/**\n\t * 查询菜单\n\t * @param all 全部菜单\n\t * @param type 类型\n\t * @param parentId 父节点ID\n\t * @return\n\t */\n\tList<Tree<Long>> filterMenu(Set<SysMenu> all, String type, Long parentId);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysMobileService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.pig4cloud.pig.common.core.util.R;\n\n/**\n * 系统手机服务接口：提供手机验证码发送功能\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic interface SysMobileService {\n\n\t/**\n\t * 发送手机验证码\n\t * @param mobile 手机号码\n\t * @return 发送结果，成功返回true，失败返回false\n\t */\n\tR<Boolean> sendSmsCode(String mobile);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysOauthClientDetailsService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport com.pig4cloud.pig.common.core.util.R;\n\n/**\n * OAuth2客户端详情服务接口\n *\n * @author lengleng\n * @since 2018-05-15\n */\npublic interface SysOauthClientDetailsService extends IService<SysOauthClientDetails> {\n\n\t/**\n\t * 根据客户端信息更新客户端详情\n\t * @param clientDetails 客户端详情信息\n\t * @return 更新结果，成功返回true\n\t */\n\tBoolean updateClientById(SysOauthClientDetails clientDetails);\n\n\t/**\n\t * 保存客户端信息\n\t * @param clientDetails 客户端详细信息\n\t * @return 操作是否成功\n\t */\n\tBoolean saveClient(SysOauthClientDetails clientDetails);\n\n\t/**\n\t * 分页查询OAuth客户端详情\n\t * @param page 分页参数\n\t * @param query 查询条件\n\t * @return 分页查询结果\n\t */\n\tPage getClientPage(Page page, SysOauthClientDetails query);\n\n\t/**\n\t * 同步客户端缓存\n\t * @return 操作结果\n\t */\n\tR syncClientCache();\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysPostService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysPost;\nimport com.pig4cloud.pig.admin.api.vo.PostExcelVO;\nimport com.pig4cloud.pig.common.core.util.R;\nimport org.springframework.validation.BindingResult;\n\nimport java.util.List;\n\n/**\n * 岗位信息表\n *\n * @author fxz\n * @date 2022-03-26 12:50:43\n */\npublic interface SysPostService extends IService<SysPost> {\n\n\t/**\n\t * 获取岗位列表用于导出Excel\n\t * @return 岗位Excel数据列表\n\t */\n\tList<PostExcelVO> listPosts();\n\n\t/**\n\t * 导入岗位信息\n\t * @param excelVOList 岗位Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果(R对象)\n\t */\n\tR importPost(List<PostExcelVO> excelVOList, BindingResult bindingResult);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysPublicParamService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysPublicParam;\nimport com.pig4cloud.pig.common.core.util.R;\n\n/**\n * 系统公共参数配置表 服务类\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic interface SysPublicParamService extends IService<SysPublicParam> {\n\n\t/**\n\t * 根据公共参数key获取对应的value值\n\t * @param publicKey 公共参数key\n\t * @return 公共参数value，未找到时返回null\n\t */\n\tString getParamValue(String publicKey);\n\n\t/**\n\t * 更新系统公共参数\n\t * @param sysPublicParam 系统公共参数对象\n\t * @return 操作结果\n\t */\n\tR updateParam(SysPublicParam sysPublicParam);\n\n\t/**\n\t * 根据ID删除参数\n\t * @param publicIds 参数ID数组\n\t * @return 删除结果\n\t */\n\tR removeParamByIds(Long[] publicIds);\n\n\t/**\n\t * 同步参数缓存\n\t * @return 操作结果\n\t */\n\tR syncParamCache();\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysRoleMenuService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysRoleMenu;\n\n/**\n * 角色菜单表服务接口\n *\n * @author lengleng\n * @since 2017-10-29\n */\npublic interface SysRoleMenuService extends IService<SysRoleMenu> {\n\n\t/**\n\t * 更新角色菜单\n\t * @param roleId 角色ID\n\t * @param menuIds 菜单ID字符串，以逗号分隔\n\t * @return 更新是否成功\n\t */\n\tBoolean saveRoleMenus(Long roleId, String menuIds);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysRoleService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysRole;\nimport com.pig4cloud.pig.admin.api.vo.RoleExcelVO;\nimport com.pig4cloud.pig.admin.api.vo.RoleVO;\nimport com.pig4cloud.pig.common.core.util.R;\nimport org.springframework.validation.BindingResult;\n\nimport java.util.List;\n\n/**\n * 系统角色服务接口\n * <p>\n * 提供角色相关的业务功能，包括角色查询、删除、更新菜单及导入导出等操作\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\npublic interface SysRoleService extends IService<SysRole> {\n\n\t/**\n\t * 通过用户ID查询角色信息\n\t * @param userId 用户ID\n\t * @return 角色信息列表\n\t */\n\tList<SysRole> listRolesByUserId(Long userId);\n\n\t/**\n\t * 根据角色ID列表查询角色信息\n\t * @param roleIdList 角色ID列表，不能为空\n\t * @param key 缓存键值\n\t * @return 查询到的角色列表\n\t */\n\tList<SysRole> listRolesByRoleIds(List<Long> roleIdList, String key);\n\n\t/**\n\t * 通过角色ID数组删除角色\n\t * @param ids 要删除的角色ID数组\n\t * @return 删除是否成功\n\t */\n\tBoolean removeRoleByIds(Long[] ids);\n\n\t/**\n\t * 更新角色菜单列表\n\t * @param roleVo 包含角色和菜单列表信息的VO对象\n\t * @return 更新是否成功\n\t */\n\tBoolean updateRoleMenus(RoleVO roleVo);\n\n\t/**\n\t * 导入角色\n\t * @param excelVOList 角色列表\n\t * @param bindingResult 错误信息列表\n\t * @return 导入结果\n\t */\n\tR importRole(List<RoleExcelVO> excelVOList, BindingResult bindingResult);\n\n\t/**\n\t * 查询全部角色列表\n\t * @return 角色列表，包含角色Excel视图对象\n\t */\n\tList<RoleExcelVO> listRoles();\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserRoleService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.entity.SysUserRole;\n\n/**\n * <p>\n * 用户角色表 服务类\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\npublic interface SysUserRoleService extends IService<SysUserRole> {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserService.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.admin.api.dto.RegisterUserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.admin.api.entity.SysUser;\nimport com.pig4cloud.pig.admin.api.vo.UserExcelVO;\nimport com.pig4cloud.pig.admin.api.vo.UserVO;\nimport com.pig4cloud.pig.common.core.util.R;\nimport org.springframework.validation.BindingResult;\n\nimport java.util.List;\n\n/**\n * 系统用户服务接口\n * <p>\n * 提供用户信息查询、分页查询、增删改查等操作\n *\n * @author lengleng\n * @date 2025/05/30\n */\npublic interface SysUserService extends IService<SysUser> {\n\n\t/**\n\t * 根据用户信息查询用户详情\n\t * @param query 用户查询条件\n\t * @return 用户详细信息\n\t */\n\tR<UserInfo> getUserInfo(UserDTO query);\n\n\t/**\n\t * 分页查询用户信息（包含角色信息）\n\t * @param page 分页对象\n\t * @param userDTO 查询参数\n\t * @return 分页结果\n\t */\n\tIPage getUsersWithRolePage(Page page, UserDTO userDTO);\n\n\t/**\n\t * 删除用户\n\t * @param ids 用户\n\t * @return boolean\n\t */\n\tBoolean removeUserByIds(Long[] ids);\n\n\t/**\n\t * 更新当前用户基本信息\n\t * @param userDto 用户信息\n\t * @return Boolean\n\t */\n\tR<Boolean> updateUserInfo(UserDTO userDto);\n\n\t/**\n\t * 更新指定用户信息\n\t * @param userDto 用户信息DTO对象\n\t * @return 更新是否成功\n\t */\n\tBoolean updateUser(UserDTO userDto);\n\n\t/**\n\t * 通过ID查询用户信息\n\t * @param id 用户ID\n\t * @return 用户信息\n\t */\n\tUserVO getUserById(Long id);\n\n\t/**\n\t * 保存用户信息\n\t * @param userDto DTO 对象\n\t * @return success/fail\n\t */\n\tBoolean saveUser(UserDTO userDto);\n\n\t/**\n\t * 查询全部的用户\n\t * @param userDTO 查询条件\n\t * @return list\n\t */\n\tList<UserExcelVO> listUsers(UserDTO userDTO);\n\n\t/**\n\t * excel 导入用户\n\t * @param excelVOList excel 列表数据\n\t * @param bindingResult 错误数据\n\t * @return ok fail\n\t */\n\tR importUsers(List<UserExcelVO> excelVOList, BindingResult bindingResult);\n\n\t/**\n\t * 注册用户\n\t * @param userDto 用户信息\n\t * @return success/false\n\t */\n\tR<Boolean> registerUser(RegisterUserDTO userDto);\n\n\t/**\n\t * 锁定用户\n\t * @param username 用户名\n\t * @return 包含操作结果的R对象，true表示锁定成功\n\t */\n\tR<Boolean> lockUser(String username);\n\n\t/**\n\t * 修改用户密码\n\t * @param userDto 包含用户信息的DTO对象\n\t * @return 操作结果\n\t */\n\tR changePassword(UserDTO userDto);\n\n\t/**\n\t * 校验密码\n\t * @param password 待校验的密码明文\n\t * @return 校验结果\n\t */\n\tR checkPassword(String password);\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysDeptServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.BindingResult;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysDept;\nimport com.pig4cloud.pig.admin.api.vo.DeptExcelVo;\nimport com.pig4cloud.pig.admin.mapper.SysDeptMapper;\nimport com.pig4cloud.pig.admin.service.SysDeptService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.plugin.excel.vo.ErrorMessage;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.tree.Tree;\nimport cn.hutool.core.lang.tree.TreeNode;\nimport cn.hutool.core.lang.tree.TreeUtil;\nimport cn.hutool.core.util.StrUtil;\nimport lombok.AllArgsConstructor;\n\n/**\n * 部门管理服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n * @since 2018-01-20\n */\n@Service\n@AllArgsConstructor\npublic class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> implements SysDeptService {\n\n\tprivate final SysDeptMapper deptMapper;\n\n\t/**\n\t * 根据部门ID删除部门（包含级联删除子部门）\n\t * @param id 要删除的部门ID\n\t * @return 删除操作是否成功，始终返回true\n\t * @throws Exception 事务执行过程中可能抛出的异常\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean removeDeptById(Long id) {\n\t\t// 级联删除部门\n\t\tList<Long> idList = this.listDescendants(id).stream().map(SysDept::getDeptId).toList();\n\n\t\tOptional.ofNullable(idList).filter(CollUtil::isNotEmpty).ifPresent(this::removeByIds);\n\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 查询部门树结构\n\t * @param deptName 部门名称(模糊查询)\n\t * @return 部门树结构列表，模糊查询时返回平铺列表\n\t */\n\t@Override\n\tpublic List<Tree<Long>> getDeptTree(String deptName) {\n\t\t// 查询全部部门\n\t\tList<SysDept> deptAllList = deptMapper\n\t\t\t.selectList(Wrappers.<SysDept>lambdaQuery().like(StrUtil.isNotBlank(deptName), SysDept::getName, deptName));\n\n\t\t// 权限内部门\n\t\tList<TreeNode<Long>> collect = deptAllList.stream()\n\t\t\t.filter(dept -> dept.getDeptId().intValue() != dept.getParentId())\n\t\t\t.sorted(Comparator.comparingInt(SysDept::getSortOrder))\n\t\t\t.map(dept -> {\n\t\t\t\tTreeNode<Long> treeNode = new TreeNode();\n\t\t\t\ttreeNode.setId(dept.getDeptId());\n\t\t\t\ttreeNode.setParentId(dept.getParentId());\n\t\t\t\ttreeNode.setName(dept.getName());\n\t\t\t\ttreeNode.setWeight(dept.getSortOrder());\n\t\t\t\t// 有权限不返回标识\n\t\t\t\tMap<String, Object> extra = new HashMap<>(8);\n\t\t\t\textra.put(SysDept.Fields.createTime, dept.getCreateTime());\n\t\t\t\ttreeNode.setExtra(extra);\n\t\t\t\treturn treeNode;\n\t\t\t})\n\t\t\t.toList();\n\n\t\t// 模糊查询 不组装树结构 直接返回 表格方便编辑\n\t\tif (StrUtil.isNotBlank(deptName)) {\n\t\t\treturn collect.stream().map(node -> {\n\t\t\t\tTree<Long> tree = new Tree<>();\n\t\t\t\ttree.putAll(node.getExtra());\n\t\t\t\tBeanUtils.copyProperties(node, tree);\n\t\t\t\treturn tree;\n\t\t\t}).toList();\n\t\t}\n\n\t\treturn TreeUtil.build(collect, 0L);\n\t}\n\n\t/**\n\t * 导出部门列表为Excel视图对象列表\n\t * @return 部门Excel视图对象列表，包含部门名称、父部门名称和排序号\n\t */\n\t@Override\n\tpublic List<DeptExcelVo> exportDepts() {\n\t\tList<SysDept> list = this.list();\n\t\tList<DeptExcelVo> deptExcelVos = list.stream().map(item -> {\n\t\t\tDeptExcelVo deptExcelVo = new DeptExcelVo();\n\t\t\tdeptExcelVo.setName(item.getName());\n\t\t\tOptional<String> first = this.list()\n\t\t\t\t.stream()\n\t\t\t\t.filter(it -> item.getParentId().equals(it.getDeptId()))\n\t\t\t\t.map(SysDept::getName)\n\t\t\t\t.findFirst();\n\t\t\tdeptExcelVo.setParentName(first.orElse(\"根部门\"));\n\t\t\tdeptExcelVo.setSortOrder(item.getSortOrder());\n\t\t\treturn deptExcelVo;\n\t\t}).toList();\n\t\treturn deptExcelVos;\n\t}\n\n\t/**\n\t * 导入部门信息\n\t * @param excelVOList 部门Excel数据列表\n\t * @param bindingResult 数据校验结果\n\t * @return 导入结果，包含错误信息或成功信息\n\t */\n\t@Override\n\tpublic R importDept(List<DeptExcelVo> excelVOList, BindingResult bindingResult) {\n\t\tList<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();\n\n\t\tList<SysDept> deptList = this.list();\n\t\tfor (DeptExcelVo item : excelVOList) {\n\t\t\tSet<String> errorMsg = new HashSet<>();\n\t\t\tboolean exsitUsername = deptList.stream().anyMatch(sysDept -> item.getName().equals(sysDept.getName()));\n\t\t\tif (exsitUsername) {\n\t\t\t\terrorMsg.add(\"部门名称已经存在\");\n\t\t\t}\n\t\t\tSysDept one = this.getOne(Wrappers.<SysDept>lambdaQuery().eq(SysDept::getName, item.getParentName()));\n\t\t\tif (item.getParentName().equals(\"根部门\")) {\n\t\t\t\tone = new SysDept();\n\t\t\t\tone.setDeptId(0L);\n\t\t\t}\n\t\t\tif (one == null) {\n\t\t\t\terrorMsg.add(\"上级部门不存在\");\n\t\t\t}\n\t\t\tif (CollUtil.isEmpty(errorMsg)) {\n\t\t\t\tSysDept sysDept = new SysDept();\n\t\t\t\tsysDept.setName(item.getName());\n\t\t\t\tsysDept.setParentId(one.getDeptId());\n\t\t\t\tsysDept.setSortOrder(item.getSortOrder());\n\t\t\t\tbaseMapper.insert(sysDept);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// 数据不合法情况\n\t\t\t\terrorMessageList.add(new ErrorMessage(item.getLineNum(), errorMsg));\n\t\t\t}\n\t\t}\n\t\tif (CollUtil.isNotEmpty(errorMessageList)) {\n\t\t\treturn R.failed(errorMessageList);\n\t\t}\n\t\treturn R.ok(null, \"部门导入成功\");\n\t}\n\n\t/**\n\t * 查询部门及其所有子部门\n\t * @param deptId 目标部门ID\n\t * @return 包含目标部门及其所有子部门的列表\n\t */\n\t@Override\n\tpublic List<SysDept> listDescendants(Long deptId) {\n\t\t// 查询全部部门\n\t\tList<SysDept> allDeptList = baseMapper.selectList(Wrappers.emptyWrapper());\n\n\t\t// 递归查询所有子节点\n\t\tList<SysDept> resDeptList = new ArrayList<>();\n\t\trecursiveDept(allDeptList, deptId, resDeptList);\n\n\t\t// 添加当前节点\n\t\tresDeptList.addAll(allDeptList.stream().filter(sysDept -> deptId.equals(sysDept.getDeptId())).toList());\n\t\treturn resDeptList;\n\t}\n\n\t/**\n\t * 递归查询所有子节点\n\t * @param allDeptList 所有部门列表\n\t * @param parentId 父部门ID\n\t * @param resDeptList 结果集合\n\t */\n\tprivate void recursiveDept(List<SysDept> allDeptList, Long parentId, List<SysDept> resDeptList) {\n\t\t// 使用 Stream API 进行筛选和遍历\n\t\tallDeptList.stream().filter(sysDept -> sysDept.getParentId().equals(parentId)).forEach(sysDept -> {\n\t\t\tresDeptList.add(sysDept);\n\t\t\trecursiveDept(allDeptList, sysDept.getDeptId(), resDeptList);\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysDictItemServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysDict;\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport com.pig4cloud.pig.admin.mapper.SysDictItemMapper;\nimport com.pig4cloud.pig.admin.service.SysDictItemService;\nimport com.pig4cloud.pig.admin.service.SysDictService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.enums.DictTypeEnum;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\nimport lombok.AllArgsConstructor;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.stereotype.Service;\n\n/**\n * 字典项服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Service\n@AllArgsConstructor\npublic class SysDictItemServiceImpl extends ServiceImpl<SysDictItemMapper, SysDictItem> implements SysDictItemService {\n\n\tprivate final SysDictService dictService;\n\n\t/**\n\t * 删除字典项\n\t * @param id 字典项ID\n\t * @return 操作结果\n\t * @see R\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true)\n\tpublic R removeDictItem(Long id) {\n\t\t// 根据ID查询字典ID\n\t\tSysDictItem dictItem = this.getById(id);\n\t\tSysDict dict = dictService.getById(dictItem.getDictId());\n\t\t// 系统内置\n\t\tif (DictTypeEnum.SYSTEM.getType().equals(dict.getSystemFlag())) {\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_DICT_DELETE_SYSTEM));\n\t\t}\n\t\treturn R.ok(this.removeById(id));\n\t}\n\n\t/**\n\t * 更新字典项\n\t * @param item 需要更新的字典项\n\t * @return 操作结果，包含成功或失败信息\n\t * @see R\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, key = \"#item.dictType\")\n\tpublic R updateDictItem(SysDictItem item) {\n\t\t// 查询字典\n\t\tSysDict dict = dictService.getById(item.getDictId());\n\t\t// 系统内置\n\t\tif (DictTypeEnum.SYSTEM.getType().equals(dict.getSystemFlag())) {\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_DICT_UPDATE_SYSTEM));\n\t\t}\n\t\treturn R.ok(this.updateById(item));\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysDictServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.List;\n\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysDict;\nimport com.pig4cloud.pig.admin.api.entity.SysDictItem;\nimport com.pig4cloud.pig.admin.mapper.SysDictItemMapper;\nimport com.pig4cloud.pig.admin.mapper.SysDictMapper;\nimport com.pig4cloud.pig.admin.service.SysDictService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.enums.DictTypeEnum;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\n\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.AllArgsConstructor;\n\n/**\n * 系统字典服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Service\n@AllArgsConstructor\npublic class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {\n\n\tprivate final SysDictItemMapper dictItemMapper;\n\n\t/**\n\t * 根据ID删除字典\n\t * @param ids 字典ID数组\n\t * @return 操作结果\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true)\n\tpublic R removeDictByIds(Long[] ids) {\n\n\t\tList<Long> dictIdList = baseMapper.selectByIds(CollUtil.toList(ids))\n\t\t\t.stream()\n\t\t\t.filter(sysDict -> !sysDict.getSystemFlag().equals(DictTypeEnum.SYSTEM.getType()))// 系统内置类型不删除\n\t\t\t.map(SysDict::getId)\n\t\t\t.toList();\n\n\t\tbaseMapper.deleteByIds(dictIdList);\n\n\t\tdictItemMapper.delete(Wrappers.<SysDictItem>lambdaQuery().in(SysDictItem::getDictId, dictIdList));\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 更新字典数据\n\t * @param dict 字典对象\n\t * @return 操作结果\n\t * @see R 返回结果封装类\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, key = \"#dict.dictType\")\n\tpublic R updateDict(SysDict dict) {\n\t\tSysDict sysDict = this.getById(dict.getId());\n\t\t// 系统内置\n\t\tif (DictTypeEnum.SYSTEM.getType().equals(sysDict.getSystemFlag())) {\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_DICT_UPDATE_SYSTEM));\n\t\t}\n\t\tthis.updateById(dict);\n\t\treturn R.ok(dict);\n\t}\n\n\t/**\n\t * 同步字典缓存（清空缓存）\n\t * @return 操作结果\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true)\n\tpublic R syncDictCache() {\n\t\treturn R.ok();\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysFileServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.core.util.URLUtil;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysFile;\nimport com.pig4cloud.pig.admin.mapper.SysFileMapper;\nimport com.pig4cloud.pig.admin.service.SysFileService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.file.core.FileProperties;\nimport com.pig4cloud.pig.common.file.core.FileTemplate;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.AllArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 文件管理\n *\n * @author Luckly\n * @date 2019-06-18 17:18:42\n */\n@Slf4j\n@Service\n@AllArgsConstructor\npublic class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {\n\n\tprivate final FileTemplate fileTemplate;\n\n\tprivate final FileProperties properties;\n\n\t/**\n\t * 上传文件\n\t * @param file 要上传的文件\n\t * @return 包含文件信息的响应结果，失败时返回错误信息\n\t * @throws Exception 文件上传过程中可能出现的异常\n\t */\n\t@Override\n\tpublic R uploadFile(MultipartFile file) {\n\t\tString fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());\n\t\tMap<String, String> resultMap = new HashMap<>(4);\n\t\tresultMap.put(SysFile.Fields.bucketName, properties.getBucketName());\n\t\tresultMap.put(SysFile.Fields.fileName, fileName);\n\t\tresultMap.put(\"url\", String.format(\"/admin/sys-file/%s/%s\", properties.getBucketName(), fileName));\n\n\t\ttry (InputStream inputStream = file.getInputStream()) {\n\t\t\tfileTemplate.putObject(properties.getBucketName(), fileName, inputStream, file.getContentType());\n\t\t\t// 文件管理数据记录,收集管理追踪文件\n\t\t\tfileLog(file, fileName);\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlog.error(\"上传失败\", e);\n\t\t\treturn R.failed(e.getLocalizedMessage());\n\t\t}\n\t\treturn R.ok(resultMap);\n\t}\n\n\t/**\n\t * 从指定存储桶中获取文件并写入HTTP响应流\n\t * @param bucket 存储桶名称\n\t * @param fileName 文件名\n\t * @param response HTTP响应对象\n\t */\n\t@Override\n\tpublic void getFile(String bucket, String fileName, HttpServletResponse response) {\n\t\ttry (InputStream inputStream = (InputStream) fileTemplate.getObject(bucket, fileName)) {\n\t\t\tresponse.setContentType(\"application/octet-stream; charset=UTF-8\");\n\t\t\tresponse.addHeader(HttpHeaders.CONTENT_DISPOSITION, \"attachment; filename=\" + URLUtil.encode(fileName));\n\t\t\tIoUtil.copy(inputStream, response.getOutputStream());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlog.error(\"文件读取异常: {}\", e.getLocalizedMessage());\n\t\t}\n\t}\n\n\t/**\n\t * 根据ID删除文件\n\t * @param id 文件ID\n\t * @return 删除是否成功，文件不存在时返回false\n\t * @throws Exception 删除过程中可能抛出的异常\n\t */\n\t@Override\n\t@SneakyThrows\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean removeFile(Long id) {\n\t\tSysFile file = this.getById(id);\n\t\tif (Objects.isNull(file)) {\n\t\t\treturn Boolean.FALSE;\n\t\t}\n\t\tfileTemplate.removeObject(properties.getBucketName(), file.getFileName());\n\t\treturn this.removeById(id);\n\t}\n\n\t/**\n\t * 记录文件管理数据\n\t * @param file 上传文件\n\t * @param fileName 文件名\n\t */\n\tprivate void fileLog(MultipartFile file, String fileName) {\n\t\tSysFile sysFile = new SysFile();\n\t\tsysFile.setFileName(fileName);\n\t\tsysFile.setOriginal(file.getOriginalFilename());\n\t\tsysFile.setFileSize(file.getSize());\n\t\tsysFile.setType(FileUtil.extName(file.getOriginalFilename()));\n\t\tsysFile.setBucketName(properties.getBucketName());\n\t\tthis.save(sysFile);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysLogServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.dto.SysLogDTO;\nimport com.pig4cloud.pig.admin.api.entity.SysLog;\nimport com.pig4cloud.pig.admin.mapper.SysLogMapper;\nimport com.pig4cloud.pig.admin.service.SysLogService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.List;\n\n/**\n * 系统日志服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n * @since 2017-11-20\n */\n@Service\npublic class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements SysLogService {\n\n\t/**\n\t * 分页查询系统日志\n\t * @param page 分页参数\n\t * @param sysLog 日志查询条件\n\t * @return 分页结果\n\t */\n\t@Override\n\tpublic Page getLogPage(Page page, SysLogDTO sysLog) {\n\t\treturn baseMapper.selectPage(page, buildQuery(sysLog));\n\t}\n\n\t/**\n\t * 保存日志\n\t * @param sysLog 日志对象\n\t * @return 保存成功返回true\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean saveLog(SysLog sysLog) {\n\t\tbaseMapper.insert(sysLog);\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 查询日志列表\n\t * @param sysLog 查询条件DTO对象\n\t * @return 日志列表\n\t */\n\t@Override\n\tpublic List<SysLog> listLogs(SysLogDTO sysLog) {\n\t\treturn baseMapper.selectList(buildQuery(sysLog));\n\t}\n\n\t/**\n\t * 构建查询条件\n\t * @param sysLog 前端查询条件DTO\n\t * @return 构建好的LambdaQueryWrapper对象\n\t */\n\tprivate LambdaQueryWrapper buildQuery(SysLogDTO sysLog) {\n\t\tLambdaQueryWrapper<SysLog> wrapper = Wrappers.lambdaQuery();\n\t\tif (StrUtil.isNotBlank(sysLog.getLogType())) {\n\t\t\twrapper.eq(SysLog::getLogType, sysLog.getLogType());\n\t\t}\n\n\t\tif (ArrayUtil.isNotEmpty(sysLog.getCreateTime())) {\n\t\t\twrapper.ge(SysLog::getCreateTime, sysLog.getCreateTime()[0])\n\t\t\t\t.le(SysLog::getCreateTime, sysLog.getCreateTime()[1]);\n\t\t}\n\n\t\treturn wrapper;\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysMenuServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysMenu;\nimport com.pig4cloud.pig.admin.api.entity.SysRoleMenu;\nimport com.pig4cloud.pig.admin.mapper.SysMenuMapper;\nimport com.pig4cloud.pig.admin.mapper.SysRoleMenuMapper;\nimport com.pig4cloud.pig.admin.service.SysMenuService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.constant.enums.MenuTypeEnum;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.tree.Tree;\nimport cn.hutool.core.lang.tree.TreeNode;\nimport cn.hutool.core.lang.tree.TreeUtil;\nimport cn.hutool.core.util.BooleanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.AllArgsConstructor;\n\n/**\n * 菜单权限表服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Service\n@AllArgsConstructor\npublic class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {\n\n\tprivate final SysRoleMenuMapper sysRoleMenuMapper;\n\n\t/**\n\t * 根据角色ID查询菜单列表\n\t * @param roleId 角色ID\n\t * @return 菜单列表，如果结果为空则不会被缓存\n\t * @see CacheConstants#MENU_DETAILS\n\t */\n\t@Override\n\t@Cacheable(value = CacheConstants.MENU_DETAILS, key = \"#roleId\", unless = \"#result.isEmpty()\")\n\tpublic List<SysMenu> findMenuByRoleId(Long roleId) {\n\t\treturn baseMapper.listMenusByRoleId(roleId);\n\t}\n\n\t/**\n\t * 根据ID删除菜单\n\t * @param id 菜单ID\n\t * @return 删除结果\n\t * @throws Exception 事务回滚时抛出异常\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\t@CacheEvict(value = CacheConstants.MENU_DETAILS, allEntries = true)\n\tpublic R removeMenuById(Long id) {\n\t\t// 查询父节点为当前节点的节点\n\t\tList<SysMenu> menuList = this.list(Wrappers.<SysMenu>query().lambda().eq(SysMenu::getParentId, id));\n\t\tif (CollUtil.isNotEmpty(menuList)) {\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_MENU_DELETE_EXISTING));\n\t\t}\n\n\t\tsysRoleMenuMapper.delete(Wrappers.<SysRoleMenu>query().lambda().eq(SysRoleMenu::getMenuId, id));\n\t\t// 删除当前菜单及其子菜单\n\t\treturn R.ok(this.removeById(id));\n\t}\n\n\t/**\n\t * 根据ID更新菜单信息\n\t * @param sysMenu 菜单实体对象\n\t * @return 更新是否成功\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.MENU_DETAILS, allEntries = true)\n\tpublic Boolean updateMenuById(SysMenu sysMenu) {\n\t\treturn this.updateById(sysMenu);\n\t}\n\n\t/**\n\t * 构建菜单树结构\n\t * @param parentId 父节点ID，为空时使用默认根节点\n\t * @param menuName 菜单名称，支持模糊查询\n\t * @param type 菜单类型\n\t * @return 菜单树结构列表，模糊查询时返回平铺列表\n\t */\n\t@Override\n\tpublic List<Tree<Long>> getMenuTree(Long parentId, String menuName, String type) {\n\t\tLong parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId;\n\n\t\tList<TreeNode<Long>> collect = baseMapper\n\t\t\t.selectList(Wrappers.<SysMenu>lambdaQuery()\n\t\t\t\t.like(StrUtil.isNotBlank(menuName), SysMenu::getName, menuName)\n\t\t\t\t.eq(StrUtil.isNotBlank(type), SysMenu::getMenuType, type)\n\t\t\t\t.orderByAsc(SysMenu::getSortOrder))\n\t\t\t.stream()\n\t\t\t.map(getNodeFunction())\n\t\t\t.toList();\n\n\t\t// 模糊查询 不组装树结构 直接返回 表格方便编辑\n\t\tif (StrUtil.isNotBlank(menuName)) {\n\t\t\treturn collect.stream().map(node -> {\n\t\t\t\tTree<Long> tree = new Tree<>();\n\t\t\t\ttree.putAll(node.getExtra());\n\t\t\t\tBeanUtils.copyProperties(node, tree);\n\t\t\t\treturn tree;\n\t\t\t}).toList();\n\t\t}\n\n\t\treturn TreeUtil.build(collect, parent);\n\t}\n\n\t/**\n\t * 根据类型和父节点ID过滤菜单并构建树形结构\n\t * @param all 全部菜单集合\n\t * @param type 菜单类型\n\t * @param parentId 父节点ID，为空时使用根节点ID\n\t * @return 构建好的菜单树形结构列表\n\t */\n\t@Override\n\tpublic List<Tree<Long>> filterMenu(Set<SysMenu> all, String type, Long parentId) {\n\t\tList<TreeNode<Long>> collect = all.stream().filter(menuTypePredicate(type)).map(getNodeFunction()).toList();\n\n\t\tLong parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId;\n\t\treturn TreeUtil.build(collect, parent);\n\t}\n\n\t/**\n\t * 获取将SysMenu转换为TreeNode<Long>的函数\n\t * @return 转换函数，将SysMenu对象转换为TreeNode<Long>对象\n\t */\n\t@NotNull\n\tprivate Function<SysMenu, TreeNode<Long>> getNodeFunction() {\n\t\treturn menu -> {\n\t\t\tTreeNode<Long> node = new TreeNode<>();\n\t\t\tnode.setId(menu.getMenuId());\n\t\t\tnode.setName(menu.getName());\n\t\t\tnode.setParentId(menu.getParentId());\n\t\t\tnode.setWeight(menu.getSortOrder());\n\t\t\t// 扩展属性\n\t\t\tMap<String, Object> extra = new HashMap<>();\n\t\t\textra.put(SysMenu.Fields.path, menu.getPath());\n\t\t\textra.put(SysMenu.Fields.menuType, menu.getMenuType());\n\t\t\textra.put(SysMenu.Fields.permission, menu.getPermission());\n\t\t\textra.put(SysMenu.Fields.sortOrder, menu.getSortOrder());\n\n\t\t\t// 适配 vue3\n\t\t\tMap<String, Object> meta = new HashMap<>();\n\t\t\tmeta.put(\"title\", menu.getName());\n\t\t\tmeta.put(\"isLink\", menu.getPath() != null && menu.getPath().startsWith(\"http\") ? menu.getPath() : \"\");\n\t\t\tmeta.put(\"isHide\", !BooleanUtil.toBooleanObject(menu.getVisible()));\n\t\t\tmeta.put(\"isKeepAlive\", BooleanUtil.toBooleanObject(menu.getKeepAlive()));\n\t\t\tmeta.put(\"isAffix\", false);\n\t\t\tmeta.put(\"isIframe\", BooleanUtil.toBooleanObject(menu.getEmbedded()));\n\t\t\tmeta.put(SysMenu.Fields.icon, menu.getIcon());\n\t\t\t// 增加英文\n\t\t\tmeta.put(SysMenu.Fields.enName, menu.getEnName());\n\n\t\t\textra.put(\"meta\", meta);\n\t\t\tnode.setExtra(extra);\n\t\t\treturn node;\n\t\t};\n\t}\n\n\t/**\n\t * menu 类型断言\n\t * @param type 类型\n\t * @return Predicate\n\t */\n\tprivate Predicate<SysMenu> menuTypePredicate(String type) {\n\t\treturn vo -> {\n\t\t\tif (MenuTypeEnum.TOP_MENU.getDescription().equals(type)) {\n\t\t\t\treturn MenuTypeEnum.TOP_MENU.getType().equals(vo.getMenuType());\n\t\t\t}\n\t\t\t// 其他查询 左侧 + 顶部\n\t\t\treturn !MenuTypeEnum.BUTTON.getType().equals(vo.getMenuType());\n\t\t};\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysMobileServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.RandomUtil;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.pig4cloud.pig.admin.api.entity.SysUser;\nimport com.pig4cloud.pig.admin.mapper.SysUserMapper;\nimport com.pig4cloud.pig.admin.service.SysMobileService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.SecurityConstants;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.core.util.RedisUtils;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.dromara.sms4j.api.SmsBlend;\nimport org.dromara.sms4j.api.entity.SmsResponse;\nimport org.dromara.sms4j.core.factory.SmsFactory;\nimport org.springframework.stereotype.Service;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 手机登录相关业务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\n@Service\n@AllArgsConstructor\npublic class SysMobileServiceImpl implements SysMobileService {\n\n\tprivate final SysUserMapper userMapper;\n\n\t/**\n\t * 发送手机验证码\n\t * @param mobile 手机号码\n\t * @return 返回操作结果，包含验证码发送状态及验证码信息\n\t */\n\t@Override\n\tpublic R<Boolean> sendSmsCode(String mobile) {\n\t\tList<SysUser> userList = userMapper\n\t\t\t.selectList(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, mobile));\n\n\t\tif (CollUtil.isEmpty(userList)) {\n\t\t\tlog.info(\"手机号未注册:{}\", mobile);\n\t\t\treturn R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_APP_PHONE_UNREGISTERED, mobile));\n\t\t}\n\n\t\tString cacheKey = CacheConstants.DEFAULT_CODE_KEY + mobile;\n\t\tString codeObj = RedisUtils.get(cacheKey);\n\n\t\tif (codeObj != null) {\n\t\t\tlog.info(\"手机号验证码未过期:{}，{}\", mobile, codeObj);\n\t\t\treturn R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_APP_SMS_OFTEN));\n\t\t}\n\n\t\tString code = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));\n\t\tlog.info(\"手机号生成验证码成功:{},{}\", mobile, code);\n\t\tRedisUtils.set(cacheKey, code, SecurityConstants.CODE_TIME, TimeUnit.SECONDS);\n\n\t\t// 集成短信服务发送验证码\n\t\tSmsBlend smsBlend = SmsFactory.getSmsBlend();\n\t\tif (Objects.isNull(smsBlend)) {\n\t\t\treturn R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_SMS_BLEND_UNREGISTERED));\n\t\t}\n\n\t\tSmsResponse smsResponse = smsBlend.sendMessage(mobile, new LinkedHashMap<>(Map.of(\"code\", code)));\n\t\tlog.debug(\"调用短信服务发送验证码结果:{}\", smsResponse);\n\t\treturn R.ok(Boolean.TRUE);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysOauthClientDetailsServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;\nimport com.pig4cloud.pig.admin.mapper.SysOauthClientDetailsMapper;\nimport com.pig4cloud.pig.admin.service.SysOauthClientDetailsService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.util.R;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\n/**\n * OAuth2客户端详情服务实现类\n *\n * @author lengleng\n * @since 2018-05-15\n */\n@Service\n@RequiredArgsConstructor\npublic class SysOauthClientDetailsServiceImpl extends ServiceImpl<SysOauthClientDetailsMapper, SysOauthClientDetails>\n\t\timplements SysOauthClientDetailsService {\n\n\t/**\n\t * 根据客户端信息更新客户端详情\n\t * @param clientDetails 客户端详情信息\n\t * @return 更新结果，成功返回true\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.CLIENT_DETAILS_KEY, key = \"#clientDetails.clientId\")\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean updateClientById(SysOauthClientDetails clientDetails) {\n\t\tthis.insertOrUpdate(clientDetails);\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 保存客户端信息\n\t * @param clientDetails 客户端详细信息\n\t * @return 操作是否成功\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean saveClient(SysOauthClientDetails clientDetails) {\n\t\tthis.insertOrUpdate(clientDetails);\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 插入或更新客户端对象\n\t * @param clientDetails 客户端详情对象\n\t * @return 更新后的客户端详情对象\n\t */\n\tprivate SysOauthClientDetails insertOrUpdate(SysOauthClientDetails clientDetails) {\n\t\t// 更新数据库\n\t\tsaveOrUpdate(clientDetails);\n\t\treturn clientDetails;\n\t}\n\n\t/**\n\t * 分页查询OAuth客户端详情\n\t * @param page 分页参数\n\t * @param query 查询条件\n\t * @return 分页查询结果\n\t */\n\t@Override\n\tpublic Page getClientPage(Page page, SysOauthClientDetails query) {\n\t\treturn baseMapper.selectPage(page, Wrappers.query(query));\n\t}\n\n\t/**\n\t * 同步客户端缓存\n\t * @return 操作结果\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.CLIENT_DETAILS_KEY, allEntries = true)\n\tpublic R syncClientCache() {\n\t\treturn R.ok();\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysPostServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.BindingResult;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysPost;\nimport com.pig4cloud.pig.admin.api.vo.PostExcelVO;\nimport com.pig4cloud.pig.admin.mapper.SysPostMapper;\nimport com.pig4cloud.pig.admin.service.SysPostService;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.plugin.excel.vo.ErrorMessage;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollUtil;\n\n/**\n * 岗位信息表服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Service\npublic class SysPostServiceImpl extends ServiceImpl<SysPostMapper, SysPost> implements SysPostService {\n\n\t/**\n\t * 导入岗位\n\t * @param excelVOList 岗位列表\n\t * @param bindingResult 错误信息列表\n\t * @return ok fail\n\t */\n\t@Override\n\tpublic R importPost(List<PostExcelVO> excelVOList, BindingResult bindingResult) {\n\t\t// 通用校验获取失败的数据\n\t\tList<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();\n\n\t\t// 个性化校验逻辑\n\t\tList<SysPost> postList = this.list();\n\n\t\t// 执行数据插入操作 组装 PostDto\n\t\tfor (PostExcelVO excel : excelVOList) {\n\t\t\tSet<String> errorMsg = new HashSet<>();\n\t\t\t// 检验岗位名称或者岗位编码是否存在\n\t\t\tboolean existPost = postList.stream()\n\t\t\t\t.anyMatch(post -> excel.getPostName().equals(post.getPostName())\n\t\t\t\t\t\t|| excel.getPostCode().equals(post.getPostCode()));\n\n\t\t\tif (existPost) {\n\t\t\t\terrorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_POST_NAMEORCODE_EXISTING, excel.getPostName(),\n\t\t\t\t\t\texcel.getPostCode()));\n\t\t\t}\n\n\t\t\t// 数据合法情况\n\t\t\tif (CollUtil.isEmpty(errorMsg)) {\n\t\t\t\tinsertExcelPost(excel);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// 数据不合法\n\t\t\t\terrorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg));\n\t\t\t}\n\t\t}\n\t\tif (CollUtil.isNotEmpty(errorMessageList)) {\n\t\t\treturn R.failed(errorMessageList);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 获取岗位列表并转换为Excel导出对象\n\t * @return 岗位Excel导出对象列表\n\t */\n\t@Override\n\tpublic List<PostExcelVO> listPosts() {\n\t\tList<SysPost> postList = this.list(Wrappers.emptyWrapper());\n\t\t// 转换成execl 对象输出\n\t\treturn postList.stream().map(post -> {\n\t\t\tPostExcelVO postExcelVO = new PostExcelVO();\n\t\t\tBeanUtil.copyProperties(post, postExcelVO);\n\t\t\treturn postExcelVO;\n\t\t}).toList();\n\t}\n\n\t/**\n\t * 插入Excel中的岗位数据\n\t * @param excel 包含岗位信息的Excel数据对象\n\t */\n\tprivate void insertExcelPost(PostExcelVO excel) {\n\t\tSysPost sysPost = new SysPost();\n\t\tBeanUtil.copyProperties(excel, sysPost);\n\t\tthis.save(sysPost);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysPublicParamServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.List;\n\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysPublicParam;\nimport com.pig4cloud.pig.admin.mapper.SysPublicParamMapper;\nimport com.pig4cloud.pig.admin.service.SysPublicParamService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.enums.DictTypeEnum;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\n\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.AllArgsConstructor;\n\n/**\n * 系统公共参数服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Service\n@AllArgsConstructor\npublic class SysPublicParamServiceImpl extends ServiceImpl<SysPublicParamMapper, SysPublicParam>\n\t\timplements SysPublicParamService {\n\n\t/**\n\t * 根据公共参数key获取对应的value值\n\t * @param publicKey 公共参数key\n\t * @return 公共参数value，未找到时返回null\n\t * @Cacheable 使用缓存，缓存名称为PARAMS_DETAILS，key为publicKey，当结果为null时不缓存\n\t */\n\t@Override\n\t@Cacheable(value = CacheConstants.PARAMS_DETAILS, key = \"#publicKey\", unless = \"#result == null \")\n\tpublic String getParamValue(String publicKey) {\n\t\tSysPublicParam sysPublicParam = this.baseMapper\n\t\t\t.selectOne(Wrappers.<SysPublicParam>lambdaQuery().eq(SysPublicParam::getPublicKey, publicKey));\n\n\t\tif (sysPublicParam != null) {\n\t\t\treturn sysPublicParam.getPublicValue();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * 更新系统公共参数\n\t * @param sysPublicParam 系统公共参数对象\n\t * @return 操作结果\n\t * @see R\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.PARAMS_DETAILS, key = \"#sysPublicParam.publicKey\")\n\tpublic R updateParam(SysPublicParam sysPublicParam) {\n\t\tSysPublicParam param = this.getById(sysPublicParam.getPublicId());\n\t\t// 系统内置\n\t\tif (DictTypeEnum.SYSTEM.getType().equals(param.getSystemFlag())) {\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_PARAM_DELETE_SYSTEM));\n\t\t}\n\t\treturn R.ok(this.updateById(sysPublicParam));\n\t}\n\n\t/**\n\t * 根据ID列表删除参数\n\t * @param publicIds 参数ID数组\n\t * @return 操作结果\n\t * @see CacheConstants#PARAMS_DETAILS 缓存常量\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.PARAMS_DETAILS, allEntries = true)\n\tpublic R removeParamByIds(Long[] publicIds) {\n\t\tList<Long> idList = this.baseMapper.selectByIds(CollUtil.toList(publicIds))\n\t\t\t.stream()\n\t\t\t.filter(p -> !p.getSystemFlag().equals(DictTypeEnum.SYSTEM.getType()))// 系统内置的跳过不能删除\n\t\t\t.map(SysPublicParam::getPublicId)\n\t\t\t.toList();\n\t\treturn R.ok(this.removeBatchByIds(idList));\n\t}\n\n\t/**\n\t * 同步参数缓存\n\t * @return 操作结果\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.PARAMS_DETAILS, allEntries = true)\n\tpublic R syncParamCache() {\n\t\treturn R.ok();\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysRoleMenuServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysRoleMenu;\nimport com.pig4cloud.pig.admin.mapper.SysRoleMenuMapper;\nimport com.pig4cloud.pig.admin.service.SysRoleMenuService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.AllArgsConstructor;\n\n/**\n * 角色菜单表服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Service\n@AllArgsConstructor\npublic class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuMapper, SysRoleMenu> implements SysRoleMenuService {\n\n\tprivate final CacheManager cacheManager;\n\n\t/**\n\t * @param roleId 角色\n\t * @param menuIds 菜单ID拼成的字符串，每个id之间根据逗号分隔\n\t * @return\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\t@CacheEvict(value = CacheConstants.MENU_DETAILS, key = \"#roleId\")\n\tpublic Boolean saveRoleMenus(Long roleId, String menuIds) {\n\t\tthis.remove(Wrappers.<SysRoleMenu>query().lambda().eq(SysRoleMenu::getRoleId, roleId));\n\n\t\tif (StrUtil.isBlank(menuIds)) {\n\t\t\treturn Boolean.TRUE;\n\t\t}\n\t\tList<SysRoleMenu> roleMenuList = Arrays.stream(menuIds.split(StrUtil.COMMA)).map(menuId -> {\n\t\t\tSysRoleMenu roleMenu = new SysRoleMenu();\n\t\t\troleMenu.setRoleId(roleId);\n\t\t\troleMenu.setMenuId(Long.valueOf(menuId));\n\t\t\treturn roleMenu;\n\t\t}).toList();\n\n\t\t// 清空userinfo\n\t\tcacheManager.getCache(CacheConstants.USER_DETAILS).clear();\n\t\tthis.saveBatch(roleMenuList);\n\t\treturn Boolean.TRUE;\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysRoleServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.BindingResult;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysRole;\nimport com.pig4cloud.pig.admin.api.entity.SysRoleMenu;\nimport com.pig4cloud.pig.admin.api.vo.RoleExcelVO;\nimport com.pig4cloud.pig.admin.api.vo.RoleVO;\nimport com.pig4cloud.pig.admin.mapper.SysRoleMapper;\nimport com.pig4cloud.pig.admin.service.SysRoleMenuService;\nimport com.pig4cloud.pig.admin.service.SysRoleService;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.plugin.excel.vo.ErrorMessage;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.AllArgsConstructor;\n\n/**\n * 系统角色服务实现类\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Service\n@AllArgsConstructor\npublic class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {\n\n\tprivate SysRoleMenuService roleMenuService;\n\n\t/**\n\t * 通过用户ID查询角色信息\n\t * @param userId 用户ID\n\t * @return 角色信息列表\n\t */\n\t@Override\n\tpublic List listRolesByUserId(Long userId) {\n\t\treturn baseMapper.listRolesByUserId(userId);\n\t}\n\n\t/**\n\t * 根据角色ID查询角色列表\n\t * @param roleIdList 角色ID列表\n\t * @param key 缓存key\n\t * @return 角色列表\n\t */\n\t@Override\n\t@Cacheable(value = CacheConstants.ROLE_DETAILS, key = \"#key\", unless = \"#result.isEmpty()\")\n\tpublic List<SysRole> listRolesByRoleIds(List<Long> roleIdList, String key) {\n\t\treturn baseMapper.selectByIds(roleIdList);\n\t}\n\n\t/**\n\t * 通过角色ID删除角色并清空角色菜单缓存\n\t * @param ids 角色ID数组\n\t * @return 删除是否成功\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean removeRoleByIds(Long[] ids) {\n\t\troleMenuService\n\t\t\t.remove(Wrappers.<SysRoleMenu>update().lambda().in(SysRoleMenu::getRoleId, CollUtil.toList(ids)));\n\t\treturn this.removeBatchByIds(CollUtil.toList(ids));\n\t}\n\n\t/**\n\t * 更新角色菜单列表\n\t * @param roleVo 包含角色ID和菜单ID列表的角色对象\n\t * @return 更新是否成功\n\t */\n\t@Override\n\tpublic Boolean updateRoleMenus(RoleVO roleVo) {\n\t\treturn roleMenuService.saveRoleMenus(roleVo.getRoleId(), roleVo.getMenuIds());\n\t}\n\n\t/**\n\t * 导入角色\n\t * @param excelVOList 角色列表\n\t * @param bindingResult 错误信息列表\n\t * @return ok fail\n\t */\n\t@Override\n\tpublic R importRole(List<RoleExcelVO> excelVOList, BindingResult bindingResult) {\n\t\t// 通用校验获取失败的数据\n\t\tList<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();\n\n\t\t// 个性化校验逻辑\n\t\tList<SysRole> roleList = this.list();\n\n\t\t// 执行数据插入操作 组装 RoleDto\n\t\tfor (RoleExcelVO excel : excelVOList) {\n\t\t\tSet<String> errorMsg = new HashSet<>();\n\t\t\t// 检验角色名称或者角色编码是否存在\n\t\t\tboolean existRole = roleList.stream()\n\t\t\t\t.anyMatch(sysRole -> excel.getRoleName().equals(sysRole.getRoleName())\n\t\t\t\t\t\t|| excel.getRoleCode().equals(sysRole.getRoleCode()));\n\n\t\t\tif (existRole) {\n\t\t\t\terrorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_ROLE_NAMEORCODE_EXISTING, excel.getRoleName(),\n\t\t\t\t\t\texcel.getRoleCode()));\n\t\t\t}\n\n\t\t\t// 数据合法情况\n\t\t\tif (CollUtil.isEmpty(errorMsg)) {\n\t\t\t\tinsertExcelRole(excel);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// 数据不合法情况\n\t\t\t\terrorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg));\n\t\t\t}\n\t\t}\n\t\tif (CollUtil.isNotEmpty(errorMessageList)) {\n\t\t\treturn R.failed(errorMessageList);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 查询全部角色列表并转换为Excel视图对象\n\t * @return 角色Excel视图对象列表\n\t */\n\t@Override\n\tpublic List<RoleExcelVO> listRoles() {\n\t\tList<SysRole> roleList = this.list(Wrappers.emptyWrapper());\n\t\t// 转换成execl 对象输出\n\t\treturn roleList.stream().map(role -> {\n\t\t\tRoleExcelVO roleExcelVO = new RoleExcelVO();\n\t\t\tBeanUtil.copyProperties(role, roleExcelVO);\n\t\t\treturn roleExcelVO;\n\t\t}).toList();\n\t}\n\n\t/**\n\t * 插入Excel中的角色数据\n\t * @param excel 包含角色信息的Excel数据对象\n\t */\n\tprivate void insertExcelRole(RoleExcelVO excel) {\n\t\tSysRole sysRole = new SysRole();\n\t\tsysRole.setRoleName(excel.getRoleName());\n\t\tsysRole.setRoleDesc(excel.getRoleDesc());\n\t\tsysRole.setRoleCode(excel.getRoleCode());\n\t\tthis.save(sysRole);\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserRoleServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.entity.SysUserRole;\nimport com.pig4cloud.pig.admin.mapper.SysUserRoleMapper;\nimport com.pig4cloud.pig.admin.service.SysUserRoleService;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 用户角色表 服务实现类\n * </p>\n *\n * @author lengleng\n * @since 2017-10-29\n */\n@Service\npublic class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRole> implements SysUserRoleService {\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserServiceImpl.java",
    "content": "/*\n *\n *      Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: lengleng (wangiegie@gmail.com)\n *\n */\n\npackage com.pig4cloud.pig.admin.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.admin.api.dto.RegisterUserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserDTO;\nimport com.pig4cloud.pig.admin.api.dto.UserInfo;\nimport com.pig4cloud.pig.admin.api.entity.*;\nimport com.pig4cloud.pig.admin.api.util.ParamResolver;\nimport com.pig4cloud.pig.admin.api.vo.UserExcelVO;\nimport com.pig4cloud.pig.admin.api.vo.UserVO;\nimport com.pig4cloud.pig.admin.mapper.SysUserMapper;\nimport com.pig4cloud.pig.admin.mapper.SysUserPostMapper;\nimport com.pig4cloud.pig.admin.mapper.SysUserRoleMapper;\nimport com.pig4cloud.pig.admin.service.*;\nimport com.pig4cloud.pig.common.core.constant.CacheConstants;\nimport com.pig4cloud.pig.common.core.constant.CommonConstants;\nimport com.pig4cloud.pig.common.core.exception.ErrorCodes;\nimport com.pig4cloud.pig.common.core.util.MsgUtils;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.security.util.SecurityUtils;\nimport com.pig4cloud.plugin.excel.vo.ErrorMessage;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.BindingResult;\n\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 系统用户服务实现类\n *\n * @author lengleng\n * @date 2025/05/30\n */\n@Slf4j\n@Service\n@AllArgsConstructor\npublic class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {\n\n\tprivate static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();\n\n\tprivate final SysMenuService sysMenuService;\n\n\tprivate final SysRoleService sysRoleService;\n\n\tprivate final SysPostService sysPostService;\n\n\tprivate final SysDeptService sysDeptService;\n\n\tprivate final SysUserRoleMapper sysUserRoleMapper;\n\n\tprivate final SysUserPostMapper sysUserPostMapper;\n\n\tprivate final CacheManager cacheManager;\n\n\t/**\n\t * 保存用户信息\n\t * @param userDto 用户数据传输对象\n\t * @return 操作是否成功\n\t * @throws Exception 事务回滚时抛出异常\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean saveUser(UserDTO userDto) {\n\t\tSysUser sysUser = new SysUser();\n\t\tBeanUtils.copyProperties(userDto, sysUser);\n\t\tsysUser.setDelFlag(CommonConstants.STATUS_NORMAL);\n\t\tsysUser.setCreateBy(userDto.getUsername());\n\t\tsysUser.setPassword(ENCODER.encode(userDto.getPassword()));\n\t\tbaseMapper.insert(sysUser);\n\t\t// 保存用户岗位信息\n\t\tOptional.ofNullable(userDto.getPost()).ifPresent(posts -> posts.forEach(postId -> {\n\t\t\tSysUserPost userPost = new SysUserPost();\n\t\t\tuserPost.setUserId(sysUser.getUserId());\n\t\t\tuserPost.setPostId(postId);\n\t\t\tsysUserPostMapper.insert(userPost);\n\t\t}));\n\n\t\t// 如果角色为空，赋默认角色\n\t\tif (CollUtil.isEmpty(userDto.getRole())) {\n\t\t\t// 获取默认角色编码\n\t\t\tString defaultRole = ParamResolver.getStr(\"USER_DEFAULT_ROLE\");\n\t\t\t// 默认角色\n\t\t\tSysRole sysRole = sysRoleService\n\t\t\t\t.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleCode, defaultRole));\n\t\t\tuserDto.setRole(Collections.singletonList(sysRole.getRoleId()));\n\t\t}\n\n\t\t// 插入用户角色关系表\n\t\tuserDto.getRole().forEach(roleId -> {\n\t\t\tSysUserRole userRole = new SysUserRole();\n\t\t\tuserRole.setUserId(sysUser.getUserId());\n\t\t\tuserRole.setRoleId(roleId);\n\t\t\tsysUserRoleMapper.insert(userRole);\n\t\t});\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 查询用户全部信息，包括角色和权限\n\t * @param query 用户查询条件\n\t * @return 包含用户角色和权限的用户信息对象\n\t */\n\t@Override\n\tpublic R<UserInfo> getUserInfo(UserDTO query) {\n\t\tUserVO dbUser = baseMapper.getUser(query);\n\n\t\tif (dbUser == null) {\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERINFO_EMPTY, query.getUsername()));\n\t\t}\n\n\t\tUserInfo userInfo = new UserInfo();\n\t\tBeanUtils.copyProperties(dbUser, userInfo);\n\t\t// 设置权限列表（menu.permission）\n\t\tList<String> permissions = dbUser.getRoleList()\n\t\t\t.stream()\n\t\t\t.map(SysRole::getRoleId)\n\t\t\t.flatMap(roleId -> sysMenuService.findMenuByRoleId(roleId).stream())\n\t\t\t.filter(menu -> StrUtil.isNotEmpty(menu.getPermission()))\n\t\t\t.map(SysMenu::getPermission)\n\t\t\t.toList();\n\t\tuserInfo.setPermissions(permissions);\n\t\treturn R.ok(userInfo);\n\t}\n\n\t/**\n\t * 分页查询用户信息（包含角色信息）\n\t * @param page 分页对象\n\t * @param userDTO 查询参数\n\t * @return 包含用户和角色信息的分页结果\n\t */\n\t@Override\n\tpublic IPage getUsersWithRolePage(Page page, UserDTO userDTO) {\n\t\treturn baseMapper.getUsersPage(page, userDTO);\n\t}\n\n\t/**\n\t * 通过ID查询用户信息\n\t * @param id 用户ID\n\t * @return 用户信息VO对象\n\t */\n\t@Override\n\tpublic UserVO getUserById(Long id) {\n\t\tUserDTO query = new UserDTO();\n\t\tquery.setUserId(id);\n\t\treturn baseMapper.getUser(query);\n\t}\n\n\t/**\n\t * 根据用户ID列表删除用户及相关缓存\n\t * @param ids 用户ID数组\n\t * @return 删除成功返回true\n\t * @throws Exception 事务回滚时抛出异常\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic Boolean removeUserByIds(Long[] ids) {\n\t\tList<Long> idList = CollUtil.toList(ids);\n\t\t// 删除 spring cache\n\t\tCache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);\n\t\tbaseMapper.selectByIds(idList).forEach(user -> cache.evictIfPresent(user.getUsername()));\n\n\t\tsysUserRoleMapper.delete(Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getUserId, idList));\n\t\tthis.removeBatchByIds(idList);\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 更新用户信息\n\t * @param userDto 用户数据传输对象\n\t * @return 操作结果，包含更新是否成功\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.USER_DETAILS, key = \"#userDto.username\")\n\tpublic R<Boolean> updateUserInfo(UserDTO userDto) {\n\t\tSysUser sysUser = new SysUser();\n\t\tsysUser.setPhone(userDto.getPhone());\n\t\tsysUser.setUserId(SecurityUtils.getUser().getId());\n\t\tsysUser.setAvatar(userDto.getAvatar());\n\t\tsysUser.setNickname(userDto.getNickname());\n\t\tsysUser.setName(userDto.getName());\n\t\tsysUser.setEmail(userDto.getEmail());\n\t\treturn R.ok(this.updateById(sysUser));\n\t}\n\n\t/**\n\t * 更新用户信息\n\t * @param userDto 用户数据传输对象，包含需要更新的用户信息\n\t * @return 更新成功返回true\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\t@CacheEvict(value = CacheConstants.USER_DETAILS, key = \"#userDto.username\")\n\tpublic Boolean updateUser(UserDTO userDto) {\n\t\t// 更新用户表信息\n\t\tSysUser sysUser = new SysUser();\n\t\tBeanUtils.copyProperties(userDto, sysUser);\n\t\tsysUser.setUpdateTime(LocalDateTime.now());\n\t\tif (StrUtil.isNotBlank(userDto.getPassword())) {\n\t\t\tsysUser.setPassword(ENCODER.encode(userDto.getPassword()));\n\t\t}\n\t\tthis.updateById(sysUser);\n\n\t\t// 更新用户角色表\n\t\tif (Objects.nonNull(userDto.getRole())) {\n\t\t\t// 删除用户角色关系\n\t\t\tsysUserRoleMapper\n\t\t\t\t.delete(Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getUserId, userDto.getUserId()));\n\t\t\tuserDto.getRole().forEach(roleId -> {\n\t\t\t\tSysUserRole userRole = new SysUserRole();\n\t\t\t\tuserRole.setUserId(sysUser.getUserId());\n\t\t\t\tuserRole.setRoleId(roleId);\n\t\t\t\tsysUserRoleMapper.insert(userRole);\n\t\t\t});\n\t\t}\n\n\t\tif (Objects.nonNull(userDto.getPost())) {\n\t\t\t// 删除用户岗位关系\n\t\t\tsysUserPostMapper\n\t\t\t\t.delete(Wrappers.<SysUserPost>lambdaQuery().eq(SysUserPost::getUserId, userDto.getUserId()));\n\t\t\tuserDto.getPost().forEach(postId -> {\n\t\t\t\tSysUserPost userPost = new SysUserPost();\n\t\t\t\tuserPost.setUserId(sysUser.getUserId());\n\t\t\t\tuserPost.setPostId(postId);\n\t\t\t\tsysUserPostMapper.insert(userPost);\n\t\t\t});\n\t\t}\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 查询用户列表并转换为Excel导出格式\n\t * @param userDTO 用户查询条件\n\t * @return 用户Excel视图对象列表\n\t */\n\t@Override\n\tpublic List<UserExcelVO> listUsers(UserDTO userDTO) {\n\t\t// 根据数据权限查询全部的用户信息\n\t\tList<UserVO> voList = baseMapper.listUsers(userDTO);\n\t\t// 转换成execl 对象输出\n\t\treturn voList.stream().map(userVO -> {\n\t\t\tUserExcelVO excelVO = new UserExcelVO();\n\t\t\tBeanUtils.copyProperties(userVO, excelVO);\n\t\t\texcelVO.setRoleNameList(\n\t\t\t\t\tuserVO.getRoleList().stream().map(SysRole::getRoleName).collect(Collectors.joining(StrUtil.COMMA)));\n\t\t\texcelVO.setPostNameList(\n\t\t\t\t\tuserVO.getPostList().stream().map(SysPost::getPostName).collect(Collectors.joining(StrUtil.COMMA)));\n\n\t\t\tif (Objects.nonNull(userVO.getDept())) {\n\t\t\t\texcelVO.setDeptName(userVO.getDept().getName());\n\t\t\t}\n\t\t\treturn excelVO;\n\t\t}).toList();\n\t}\n\n\t/**\n\t * 导入用户数据\n\t * @param excelVOList Excel数据列表\n\t * @param bindingResult 校验结果\n\t * @return 导入结果，包含成功或失败信息\n\t */\n\t@Override\n\tpublic R importUsers(List<UserExcelVO> excelVOList, BindingResult bindingResult) {\n\t\t// 通用校验获取失败的数据\n\t\tList<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();\n\t\tList<SysDept> deptList = sysDeptService.list();\n\t\tList<SysRole> roleList = sysRoleService.list();\n\t\tList<SysPost> postList = sysPostService.list();\n\n\t\t// 执行数据插入操作 组装 UserDto\n\t\tfor (UserExcelVO excel : excelVOList) {\n\t\t\t// 个性化校验逻辑\n\t\t\tList<SysUser> userList = this.list();\n\n\t\t\tSet<String> errorMsg = new HashSet<>();\n\t\t\t// 校验用户名是否存在\n\t\t\tif (userList.stream().anyMatch(sysUser -> excel.getUsername().equals(sysUser.getUsername()))) {\n\t\t\t\terrorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, excel.getUsername()));\n\t\t\t}\n\n\t\t\t// 判断输入的部门名称列表是否合法\n\t\t\tOptional<SysDept> deptOptional = deptList.stream()\n\t\t\t\t.filter(dept -> excel.getDeptName().equals(dept.getName()))\n\t\t\t\t.findFirst();\n\t\t\tif (deptOptional.isEmpty()) {\n\t\t\t\terrorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_DEPT_DEPTNAME_INEXISTENCE, excel.getDeptName()));\n\t\t\t}\n\n\t\t\t// 判断输入的角色名称列表是否合法\n\t\t\tList<String> roleNameList = StrUtil.split(excel.getRoleNameList(), StrUtil.COMMA);\n\t\t\tList<SysRole> roleCollList = roleList.stream()\n\t\t\t\t.filter(role -> roleNameList.stream().anyMatch(name -> role.getRoleName().equals(name)))\n\t\t\t\t.toList();\n\n\t\t\tif (roleCollList.size() != roleNameList.size()) {\n\t\t\t\terrorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_ROLE_ROLENAME_INEXISTENCE, excel.getRoleNameList()));\n\t\t\t}\n\n\t\t\t// 判断输入的部门名称列表是否合法\n\t\t\tList<String> postNameList = StrUtil.split(excel.getPostNameList(), StrUtil.COMMA);\n\t\t\tList<SysPost> postCollList = postList.stream()\n\t\t\t\t.filter(post -> postNameList.stream().anyMatch(name -> post.getPostName().equals(name)))\n\t\t\t\t.toList();\n\n\t\t\tif (postCollList.size() != postNameList.size()) {\n\t\t\t\terrorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_POST_POSTNAME_INEXISTENCE, excel.getPostNameList()));\n\t\t\t}\n\n\t\t\t// 数据合法情况\n\t\t\tif (CollUtil.isEmpty(errorMsg)) {\n\t\t\t\tinsertExcelUser(excel, deptOptional, roleCollList, postCollList);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// 数据不合法情况\n\t\t\t\terrorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg));\n\t\t\t}\n\n\t\t}\n\n\t\tif (CollUtil.isNotEmpty(errorMessageList)) {\n\t\t\treturn R.failed(errorMessageList);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 插入Excel导入的用户信息\n\t * @param excel Excel用户数据对象\n\t * @param deptOptional 部门信息Optional对象\n\t * @param roleCollList 角色列表\n\t * @param postCollList 岗位列表\n\t */\n\tprivate void insertExcelUser(UserExcelVO excel, Optional<SysDept> deptOptional, List<SysRole> roleCollList,\n\t\t\tList<SysPost> postCollList) {\n\t\tUserDTO userDTO = new UserDTO();\n\t\tuserDTO.setUsername(excel.getUsername());\n\t\tuserDTO.setPhone(excel.getPhone());\n\t\tuserDTO.setNickname(excel.getNickname());\n\t\tuserDTO.setName(excel.getName());\n\t\tuserDTO.setEmail(excel.getEmail());\n\t\t// 批量导入初始密码为手机号\n\t\tuserDTO.setPassword(userDTO.getPhone());\n\t\t// 根据部门名称查询部门ID\n\t\tuserDTO.setDeptId(deptOptional.get().getDeptId());\n\t\t// 插入岗位名称\n\t\tList<Long> postIdList = postCollList.stream().map(SysPost::getPostId).toList();\n\t\tuserDTO.setPost(postIdList);\n\t\t// 根据角色名称查询角色ID\n\t\tList<Long> roleIdList = roleCollList.stream().map(SysRole::getRoleId).toList();\n\t\tuserDTO.setRole(roleIdList);\n\t\t// 插入用户\n\t\tthis.saveUser(userDTO);\n\t}\n\n\t/**\n\t * 注册用户并赋予默认角色\n\t * @param userDto 用户注册信息DTO\n\t * @return 注册结果，包含成功或失败状态\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic R<Boolean> registerUser(RegisterUserDTO userDto) {\n\t\t// 判断用户名是否存在\n\t\tSysUser sysUser = this.getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, userDto.getUsername()));\n\t\tif (sysUser != null) {\n\t\t\tString message = MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, userDto.getUsername());\n\t\t\treturn R.failed(message);\n\t\t}\n\n\t\tUserDTO user = new UserDTO();\n\t\tBeanUtils.copyProperties(userDto, user);\n\t\treturn R.ok(saveUser(user));\n\t}\n\n\t/**\n\t * 锁定用户\n\t * @param username 用户名\n\t * @return 操作结果，包含是否成功的信息\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.USER_DETAILS, key = \"#username\")\n\tpublic R<Boolean> lockUser(String username) {\n\t\tSysUser sysUser = baseMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));\n\n\t\tif (Objects.nonNull(sysUser)) {\n\t\t\tsysUser.setLockFlag(CommonConstants.STATUS_LOCK);\n\t\t\tbaseMapper.updateById(sysUser);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 修改用户密码\n\t * @param userDto 用户信息传输对象，包含用户名、原密码和新密码\n\t * @return 操作结果，成功返回R.ok()，失败返回错误信息\n\t * @CacheEvict 清除用户详情缓存\n\t */\n\t@Override\n\t@CacheEvict(value = CacheConstants.USER_DETAILS, key = \"#userDto.username\")\n\tpublic R changePassword(UserDTO userDto) {\n\t\tSysUser sysUser = baseMapper.selectById(SecurityUtils.getUser().getId());\n\t\tif (Objects.isNull(sysUser)) {\n\t\t\treturn R.failed(\"用户不存在\");\n\t\t}\n\n\t\tif (StrUtil.isEmpty(userDto.getPassword())) {\n\t\t\treturn R.failed(\"原密码不能为空\");\n\t\t}\n\n\t\tif (!ENCODER.matches(userDto.getPassword(), sysUser.getPassword())) {\n\t\t\tlog.info(\"原密码错误，修改个人信息失败:{}\", userDto.getUsername());\n\t\t\treturn R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_UPDATE_PASSWORDERROR));\n\t\t}\n\n\t\tif (StrUtil.isEmpty(userDto.getNewpassword1())) {\n\t\t\treturn R.failed(\"新密码不能为空\");\n\t\t}\n\t\tString password = ENCODER.encode(userDto.getNewpassword1());\n\n\t\tthis.update(Wrappers.<SysUser>lambdaUpdate()\n\t\t\t.set(SysUser::getPassword, password)\n\t\t\t.eq(SysUser::getUserId, sysUser.getUserId()));\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 校验用户密码是否正确\n\t * @param password 待校验的密码\n\t * @return 校验结果，成功返回R.ok()，失败返回R.failed()\n\t */\n\t@Override\n\tpublic R checkPassword(String password) {\n\t\tSysUser sysUser = baseMapper.selectById(SecurityUtils.getUser().getId());\n\n\t\tif (!ENCODER.matches(password, sysUser.getPassword())) {\n\t\t\tlog.info(\"原密码错误\");\n\t\t\treturn R.failed(\"密码输入错误\");\n\t\t}\n\t\telse {\n\t\t\treturn R.ok();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/application.yml",
    "content": "server:\n  port: 4000\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      username: @nacos.username@\n      password: @nacos.password@\n      discovery:\n        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}\n      config:\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n  config:\n    import:\n      - nacos:application-@profiles.active@.yml\n      - nacos:${spring.application.name}-@profiles.active@.yml\n\n\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n    小技巧: 在根pom里面设置统一存放路径，统一管理方便维护\n    <properties>\n        <log-path>/Users/lengleng</log-path>\n    </properties>\n    1. 其他模块加日志输出，直接copy本文件放在resources 目录即可\n    2. 注意修改 <property name=\"${log-path}/log.path\" value=\"\"/> 的value模块\n-->\n<configuration debug=\"false\" scan=\"false\">\n    <property name=\"log.path\" value=\"logs/${project.artifactId}\"/>\n    <!-- 彩色日志格式 -->\n    <property name=\"CONSOLE_LOG_PATTERN\"\n              value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n    <conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n    <conversionRule conversionWord=\"wex\"\n                    class=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n    <conversionRule conversionWord=\"wEx\"\n                    class=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n    <!-- Console log output -->\n    <appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>${CONSOLE_LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Log file debug output -->\n    <appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/debug.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n            <maxFileSize>50MB</maxFileSize>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Log file error output -->\n    <appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/error.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n            <maxFileSize>50MB</maxFileSize>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n        </encoder>\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>ERROR</level>\n        </filter>\n    </appender>\n\n    <!--nacos 心跳 INFO 屏蔽-->\n    <logger name=\"com.alibaba.nacos\" level=\"OFF\">\n        <appender-ref ref=\"error\"/>\n    </logger>\n\n    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n    <root level=\"debug\">\n        <appender-ref ref=\"console\"/>\n        <appender-ref ref=\"debug\"/>\n        <appender-ref ref=\"error\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/mapper/SysDeptMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.pig4cloud.pig.admin.mapper.SysDeptMapper\">\n</mapper>\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/mapper/SysMenuMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.pig4cloud.pig.admin.mapper.SysMenuMapper\">\n\t<!-- 通用查询映射结果 -->\n\t<resultMap id=\"BaseResultMap\" type=\"com.pig4cloud.pig.admin.api.entity.SysMenu\">\n\t\t<id column=\"menu_id\" property=\"menuId\"/>\n\t\t<result column=\"name\" property=\"name\"/>\n\t\t<result column=\"en_name\" property=\"enName\"/>\n\t\t<result column=\"permission\" property=\"permission\"/>\n\t\t<result column=\"path\" property=\"path\"/>\n\t\t<result column=\"parent_id\" property=\"parentId\"/>\n\t\t<result column=\"icon\" property=\"icon\"/>\n\t\t<result column=\"sort_order\" property=\"sortOrder\"/>\n\t\t<result column=\"menu_type\" property=\"menuType\"/>\n\t\t<result column=\"keep_alive\" property=\"keepAlive\"/>\n\t\t<result column=\"visible\" property=\"visible\"/>\n\t\t<result column=\"create_time\" property=\"createTime\"/>\n\t\t<result column=\"update_time\" property=\"updateTime\"/>\n\t\t<result column=\"del_flag\" property=\"delFlag\"/>\n\t\t<result column=\"embedded\" property=\"embedded\"/>\n\t\t<result column=\"visible\" property=\"visible\"/>\n\t</resultMap>\n\n\t<!--通过角色查询菜单信息-->\n\t<select id=\"listMenusByRoleId\" resultMap=\"BaseResultMap\">\n\t\tSELECT sys_menu.menu_id,\n\t\t\t   sys_menu.name,\n\t\t\t   sys_menu.en_name,\n\t\t\t   sys_menu.permission,\n\t\t\t   sys_menu.path,\n\t\t\t   sys_menu.parent_id,\n\t\t\t   sys_menu.icon,\n\t\t\t   sys_menu.sort_order,\n\t\t\t   sys_menu.keep_alive,\n\t\t\t   sys_menu.menu_type,\n\t\t\t   sys_menu.create_time,\n\t\t\t   sys_menu.update_time,\n\t\t\t   sys_menu.del_flag,\n\t\t\t   sys_menu.embedded,\n\t\t\t   sys_menu.visible\n\t\tFROM sys_menu\n\t\t\t\t\t LEFT JOIN sys_role_menu ON sys_menu.menu_id = sys_role_menu.menu_id\n\t\tWHERE sys_menu.del_flag = '0'\n\t\t  AND sys_role_menu.role_id = #{roleId}\n\t\tORDER BY sys_menu.sort_order DESC\n\t</select>\n\n\t<!--通过角色ID 查询权限-->\n\t<select id=\"listPermissionsByRoleIds\" resultType=\"java.lang.String\">\n\t\tSELECT m.permission\n\t\tFROM sys_menu m,\n\t\t\t sys_role_menu rm\n\t\tWHERE m.menu_id = rm.menu_id\n\t\t  AND m.del_flag = '0'\n\t\t  AND rm.role_id IN (#{roleIds})\n\t</select>\n</mapper>\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/mapper/SysPostMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"com.pig4cloud.pig.admin.mapper.SysPostMapper\">\n\n\t<resultMap id=\"sysPostMap\" type=\"com.pig4cloud.pig.admin.api.entity.SysPost\">\n\t\t<id property=\"postId\" column=\"post_id\"/>\n\t\t<result property=\"postCode\" column=\"post_code\"/>\n\t\t<result property=\"postName\" column=\"post_name\"/>\n\t\t<result property=\"postSort\" column=\"post_sort\"/>\n\t\t<result property=\"delFlag\" column=\"del_flag\"/>\n\t\t<result property=\"createTime\" column=\"create_time\"/>\n\t\t<result property=\"updateTime\" column=\"update_time\"/>\n\t\t<result property=\"remark\" column=\"remark\"/>\n\t</resultMap>\n\n\t<!-- 通过用户ID，查询岗位信息-->\n\t<select id=\"listPostsByUserId\" resultType=\"com.pig4cloud.pig.admin.api.entity.SysPost\">\n\t\tSELECT p.post_id,\n\t\t\t   p.post_name,\n\t\t\t   p.post_code,\n\t\t\t   p.post_sort,\n\t\t\t   p.del_flag,\n\t\t\t   p.create_time,\n\t\t\t   p.update_time,\n\t\t\t   p.remark\n\t\tFROM sys_post p,\n\t\t\t sys_user_post up\n\t\tWHERE p.post_id = up.post_id\n\t\t  AND p.del_flag = '0'\n\t\t  and up.user_id = #{userId}\n\t</select>\n\n</mapper>\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/mapper/SysRoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.pig4cloud.pig.admin.mapper.SysRoleMapper\">\n\t<!-- 通用查询映射结果 -->\n\t<resultMap id=\"BaseResultMap\" type=\"com.pig4cloud.pig.admin.api.entity.SysRole\">\n\t\t<id column=\"role_id\" property=\"roleId\"/>\n\t\t<result column=\"role_name\" property=\"roleName\"/>\n\t\t<result column=\"role_code\" property=\"roleCode\"/>\n\t\t<result column=\"role_desc\" property=\"roleDesc\"/>\n\t\t<result column=\"create_time\" property=\"createTime\"/>\n\t\t<result column=\"update_time\" property=\"updateTime\"/>\n\t\t<result column=\"del_flag\" property=\"delFlag\"/>\n\t</resultMap>\n\n\t<!-- 通过用户ID，查询角色信息-->\n\t<select id=\"listRolesByUserId\" resultMap=\"BaseResultMap\">\n\t\tSELECT sys_role.role_id,\n\t\t\t   sys_role.role_name,\n\t\t\t   sys_role.role_code,\n\t\t\t   sys_role.role_desc,\n\t\t\t   sys_role.create_time,\n\t\t\t   sys_role.update_time,\n\t\t\t   sys_role.del_flag\n\t\tFROM sys_role,\n\t\t\t sys_user_role\n\t\tWHERE sys_role.role_id = sys_user_role.role_id\n\t\t  AND sys_role.del_flag = '0'\n\t\t  and sys_user_role.user_id = #{userId}\n\t</select>\n</mapper>\n"
  },
  {
    "path": "pig-upms/pig-upms-biz/src/main/resources/mapper/SysUserMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.pig4cloud.pig.admin.mapper.SysUserMapper\">\n\t<!-- 通用查询映射结果 -->\n\t<resultMap id=\"baseResultMap\" type=\"com.pig4cloud.pig.admin.api.vo.UserVO\">\n\t\t<id column=\"user_id\" property=\"userId\"/>\n\t\t<result column=\"username\" property=\"username\"/>\n\t\t<result column=\"password\" property=\"password\"/>\n\t\t<result column=\"salt\" property=\"salt\"/>\n\t\t<result column=\"phone\" property=\"phone\"/>\n\t\t<result column=\"avatar\" property=\"avatar\"/>\n\t\t<result column=\"wx_openid\" property=\"wxOpenid\"/>\n\t\t<result column=\"qq_openid\" property=\"qqOpenid\"/>\n\t\t<result column=\"gitee_login\" property=\"giteeOpenId\"/>\n\t\t<result column=\"osc_id\" property=\"oscOpenId\"/>\n\t\t<result column=\"create_time\" property=\"createTime\"/>\n\t\t<result column=\"update_time\" property=\"updateTime\"/>\n\t\t<result column=\"lock_flag\" property=\"lockFlag\"/>\n\t\t<result column=\"del_flag\" property=\"delFlag\"/>\n\t\t<result column=\"nickname\" property=\"nickname\"/>\n\t\t<result column=\"name\" property=\"name\"/>\n\t\t<result column=\"email\" property=\"email\"/>\n\t\t<association property=\"dept\" javaType=\"com.pig4cloud.pig.admin.api.entity.SysDept\" column=\"dept_id\"\n\t\t\t\t\t select=\"com.pig4cloud.pig.admin.mapper.SysDeptMapper.selectById\">\n\t\t</association>\n\t\t<collection property=\"roleList\" ofType=\"com.pig4cloud.pig.admin.api.entity.SysRole\"\n\t\t\t\t\tselect=\"com.pig4cloud.pig.admin.mapper.SysRoleMapper.listRolesByUserId\" column=\"user_id\">\n\t\t</collection>\n\t\t<collection property=\"postList\" ofType=\"com.pig4cloud.pig.admin.api.entity.SysPost\"\n\t\t\t\t\tselect=\"com.pig4cloud.pig.admin.mapper.SysPostMapper.listPostsByUserId\" column=\"user_id\">\n\t\t</collection>\n\t</resultMap>\n\n\t<!-- 用户查询SQL -->\n\t<sql id=\"userQuerySql\">\n\t\tSELECT * FROM sys_user u\n\t\t<where>\n\t\t\tu.del_flag = '0'\n\t\t\t<if test=\"query.userId != null and query.userId != ''\">\n\t\t\t\tAND u.user_id = #{query.userId}\n\t\t\t</if>\n\t\t\t<if test=\"query.username != null and query.username != ''\">\n\t\t\t\t<bind name=\"usernameLike\" value=\"'%'+query.username+'%'\"/>\n\t\t\t\tAND u.username LIKE #{usernameLike}\n\t\t\t</if>\n\t\t\t<if test=\"query.deptId != null and query.deptId != ''\">\n\t\t\t\tAND u.dept_id = #{query.deptId}\n\t\t\t</if>\n\t\t\t<if test=\"query.phone != null and query.phone != ''\">\n\t\t\t\t<bind name=\"phoneLike\" value=\"'%'+query.phone+'%'\"/>\n\t\t\t\tAND u.phone LIKE #{phoneLike}\n\t\t\t</if>\n\t\t</where>\n\t\tORDER BY u.create_time DESC\n\t</sql>\n\n\t<!-- 分页查询 -->\n\t<select id=\"getUsersPage\" resultMap=\"baseResultMap\">\n\t\t<include refid=\"userQuerySql\"/>\n\t</select>\n\n\t<!-- 用户列表  -->\n\t<select id=\"listUsers\" resultMap=\"baseResultMap\">\n\t\t<include refid=\"userQuerySql\"/>\n\t</select>\n\n\t<!-- 查询用户信息  （单个）-->\n\t<select id=\"getUser\" resultMap=\"baseResultMap\">\n\t\tSELECT * FROM sys_user u\n\t\t<where>\n\t\t\tu.del_flag = '0'\n\t\t\t<if test=\"query.userId != null and query.userId != ''\">\n\t\t\t\tAND u.user_id = #{query.userId}\n\t\t\t</if>\n\t\t\t<if test=\"query.username != null and query.username != ''\">\n\t\t\t\tAND u.username = #{query.username}\n\t\t\t</if>\n\t\t\t<if test=\"query.deptId != null and query.deptId != ''\">\n\t\t\t\tAND u.dept_id = #{query.deptId}\n\t\t\t</if>\n\t\t\t<if test=\"query.phone != null and query.phone != ''\">\n\t\t\t\tAND u.phone = #{query.phone}\n\t\t\t</if>\n\t\t</where>\n\t</select>\n</mapper>\n"
  },
  {
    "path": "pig-upms/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n\t\t xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>com.pig4cloud</groupId>\n\t\t<artifactId>pig</artifactId>\n\t\t<version>${revision}</version>\n\t</parent>\n\n\t<artifactId>pig-upms</artifactId>\n\n\t<description>pig 通用用户权限管理聚合模块</description>\n\t<packaging>pom</packaging>\n\n\t<modules>\n\t\t<module>pig-upms-api</module>\n\t\t<module>pig-upms-biz</module>\n\t</modules>\n</project>\n"
  },
  {
    "path": "pig-visual/pig-codegen/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-codegen\n\nARG JAR_FILE=target/pig-codegen.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 5002\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-visual/pig-codegen/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-visual</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-codegen</artifactId>\n    <packaging>jar</packaging>\n\n    <description>代码生成模块</description>\n\n    <properties>\n        <screw.version>0.0.6</screw.version>\n        <anyline.version>8.7.2-jdk17-20240808</anyline.version>\n    </properties>\n\n    <dependencies>\n        <!--注册中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n        </dependency>\n        <!--配置中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n        </dependency>\n        <!--数据操作-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-mybatis</artifactId>\n        </dependency>\n        <!--动态数据源 数据操作-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-datasource</artifactId>\n        </dependency>\n        <!--mybatis-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.mysql</groupId>\n            <artifactId>mysql-connector-j</artifactId>\n        </dependency>\n        <!--anyline-->\n        <dependency>\n            <groupId>org.anyline</groupId>\n            <artifactId>anyline-environment-spring-data-jdbc</artifactId>\n            <version>${anyline.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.anyline</groupId>\n            <artifactId>anyline-data-jdbc-mysql</artifactId>\n            <version>${anyline.version}</version>\n        </dependency>\n        <!--common-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-json</artifactId>\n        </dependency>\n        <!--swagger-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-swagger</artifactId>\n        </dependency>\n        <!--安全模块-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-xss</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-log</artifactId>\n        </dependency>\n        <!--代码生成模板引擎-->\n        <dependency>\n            <groupId>org.apache.velocity</groupId>\n            <artifactId>velocity-engine-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.velocity.tools</groupId>\n            <artifactId>velocity-tools-generic</artifactId>\n        </dependency>\n        <!--生成文档-->\n        <dependency>\n            <groupId>group.springframework.plugin</groupId>\n            <artifactId>screw-spring-boot-starter</artifactId>\n            <version>${screw.version}</version>\n        </dependency>\n        <!--web 模块-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <!--undertow容器-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-undertow</artifactId>\n        </dependency>\n    </dependencies>\n\n    <profiles>\n        <profile>\n            <id>cloud</id>\n            <activation>\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-maven-plugin</artifactId>\n                        <executions>\n                            <execution>\n                                <goals>\n                                    <goal>repackage</goal>\n                                </goals>\n                                <configuration>\n                                    <loaderImplementation>CLASSIC</loaderImplementation>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>io.fabric8</groupId>\n                        <artifactId>docker-maven-plugin</artifactId>\n                        <configuration>\n                            <skip>false</skip>\n                        </configuration>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n        <profile>\n            <id>boot</id>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/PigCodeGenApplication.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen;\n\nimport com.pig4cloud.pig.common.datasource.annotation.EnableDynamicDataSource;\nimport com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;\nimport com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;\nimport com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n/**\n * 代码生成模块应用启动类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@EnableDynamicDataSource\n@EnablePigFeignClients\n@EnablePigDoc(\"gen\")\n@EnableDiscoveryClient\n@EnablePigResourceServer\n@SpringBootApplication\npublic class PigCodeGenApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigCodeGenApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/config/PigCodeGenDefaultProperties.java",
    "content": "package com.pig4cloud.pig.codegen.config;\n\nimport cn.smallbun.screw.core.constant.DefaultConstants;\nimport lombok.Data;\nimport org.anyline.util.ConfigTable;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 代码生成默认配置类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Data\n@Configuration(proxyBeanMethods = false)\n@ConfigurationProperties(prefix = PigCodeGenDefaultProperties.PREFIX)\npublic class PigCodeGenDefaultProperties implements InitializingBean {\n\n\tpublic static final String PREFIX = \"codegen\";\n\n\t/**\n\t * 是否开启在线更新\n\t */\n\tprivate boolean autoCheckVersion = true;\n\n\t/**\n\t * 模板项目地址\n\t */\n\tprivate String onlineUrl = DefaultConstants.CGTM_URL;\n\n\t/**\n\t * 生成代码的包名\n\t */\n\tprivate String packageName = \"com.pig4cloud.pig\";\n\n\t/**\n\t * 生成代码的版本\n\t */\n\tprivate String version = \"1.0.0\";\n\n\t/**\n\t * 生成代码的模块名\n\t */\n\tprivate String moduleName = \"admin\";\n\n\t/**\n\t * 生成代码的后端路径\n\t */\n\tprivate String backendPath = \"pig\";\n\n\t/**\n\t * 生成代码的前端路径\n\t */\n\tprivate String frontendPath = \"pig-ui\";\n\n\t/**\n\t * 生成代码的作者\n\t */\n\tprivate String author = \"pig\";\n\n\t/**\n\t * 生成代码的邮箱\n\t */\n\tprivate String email = \"sw@pigx.vip\";\n\n\t/**\n\t * 表单布局（一列、两列）\n\t */\n\tprivate Integer formLayout = 2;\n\n\t/**\n\t * 下载方式 （0 文件下载、1写入目录）\n\t */\n\tprivate String generatorType = \"0\";\n\n\t@Override\n\tpublic void afterPropertiesSet() throws Exception {\n\t\tConfigTable.KEEP_ADAPTER = 0;\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenDsConfController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.controller;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.smallbun.screw.boot.config.Screw;\nimport cn.smallbun.screw.boot.properties.ScrewProperties;\nimport com.baomidou.dynamic.datasource.DynamicRoutingDataSource;\nimport com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.codegen.entity.GenDatasourceConf;\nimport com.pig4cloud.pig.codegen.service.GenDatasourceConfService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport com.pig4cloud.pig.common.xss.core.XssCleanIgnore;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.sql.DataSource;\n\n/**\n * 数据源管理控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/dsconf\")\n@Tag(description = \"dsconf\", name = \"数据源管理模块\")\npublic class GenDsConfController {\n\n\tprivate final GenDatasourceConfService datasourceConfService;\n\n\tprivate final Screw screw;\n\n\t/**\n\t * 分页查询数据源配置\n\t * @param page 分页参数对象\n\t * @param datasourceConf 数据源配置查询条件\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询数据源配置\", description = \"分页查询数据源配置\")\n\tpublic R getDsConfPage(Page page, GenDatasourceConf datasourceConf) {\n\t\treturn R.ok(datasourceConfService.page(page,\n\t\t\t\tWrappers.<GenDatasourceConf>lambdaQuery()\n\t\t\t\t\t.like(StrUtil.isNotBlank(datasourceConf.getDsName()), GenDatasourceConf::getDsName,\n\t\t\t\t\t\t\tdatasourceConf.getDsName())));\n\t}\n\n\t/**\n\t * 查询全部数据源列表\n\t * @return 包含全部数据源列表的响应结果\n\t */\n\t@Inner(value = false)\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"查询全部数据源列表\", description = \"查询全部数据源列表\")\n\tpublic R listDsConfs() {\n\t\treturn R.ok(datasourceConfService.list());\n\t}\n\n\t/**\n\t * 根据ID查询数据源表\n\t * @param id 数据源ID\n\t * @return 包含查询结果的响应对象\n\t */\n\t@GetMapping(\"/{id}\")\n\t@Operation(summary = \"根据ID查询数据源表\", description = \"根据ID查询数据源表\")\n\tpublic R getDsConfById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(datasourceConfService.getById(id));\n\t}\n\n\t/**\n\t * 新增数据源表\n\t * @param datasourceConf 数据源配置信息\n\t * @return 操作结果\n\t */\n\t@PostMapping\n\t@XssCleanIgnore\n\t@Operation(summary = \"新增数据源表\", description = \"新增数据源表\")\n\tpublic R saveDsConf(@RequestBody GenDatasourceConf datasourceConf) {\n\t\treturn R.ok(datasourceConfService.saveDsByEnc(datasourceConf));\n\t}\n\n\t/**\n\t * 修改数据源表\n\t * @param conf 数据源表配置信息\n\t * @return 操作结果\n\t */\n\t@PutMapping\n\t@XssCleanIgnore\n\t@Operation(summary = \"修改数据源表\", description = \"修改数据源表\")\n\tpublic R updateDsConf(@RequestBody GenDatasourceConf conf) {\n\t\treturn R.ok(datasourceConfService.updateDsByEnc(conf));\n\t}\n\n\t/**\n\t * 通过id数组删除数据源表\n\t * @param ids 要删除的数据源id数组\n\t * @return 包含操作结果的R对象\n\t */\n\t@DeleteMapping\n\t@Operation(summary = \"通过id数组删除数据源表\", description = \"通过id数组删除数据源表\")\n\tpublic R removeDsConfByIds(@RequestBody Long[] ids) {\n\t\treturn R.ok(datasourceConfService.removeByDsId(ids));\n\t}\n\n\t/**\n\t * 生成指定数据源的数据库文档并输出到响应流\n\t * @param dsName 数据源名称\n\t * @param response HTTP响应对象\n\t * @throws Exception 生成文档或IO操作过程中可能抛出的异常\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/doc\")\n\t@Operation(summary = \"生成指定数据源的数据库文档并输出到响应流\", description = \"生成指定数据源的数据库文档并输出到响应流\")\n\tpublic void generatorDoc(String dsName, HttpServletResponse response) {\n\t\t// 设置指定的数据源\n\t\tDynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);\n\t\tDynamicDataSourceContextHolder.push(dsName);\n\t\tDataSource dataSource = dynamicRoutingDataSource.determineDataSource();\n\n\t\t// 设置指定的目标表\n\t\tScrewProperties screwProperties = SpringContextHolder.getBean(ScrewProperties.class);\n\n\t\t// 生成\n\t\tbyte[] data = screw.documentGeneration(dsName, dataSource, screwProperties).toByteArray();\n\t\tresponse.reset();\n\t\tresponse.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length));\n\t\tresponse.setContentType(\"application/octet-stream\");\n\t\tIoUtil.write(response.getOutputStream(), Boolean.FALSE, data);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenFieldTypeController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.codegen.entity.GenFieldType;\nimport com.pig4cloud.pig.codegen.service.GenFieldTypeService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 列属性管理控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/fieldtype\")\n@Tag(description = \"fieldtype\", name = \"列属性管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class GenFieldTypeController {\n\n\tprivate final GenFieldTypeService fieldTypeService;\n\n\t/**\n\t * 分页查询字段类型\n\t * @param page 分页对象\n\t * @param fieldType 字段类型查询条件\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询\", description = \"分页查询\")\n\tpublic R getFieldTypePage(Page page, GenFieldType fieldType) {\n\t\treturn R.ok(fieldTypeService.page(page,\n\t\t\t\tWrappers.<GenFieldType>lambdaQuery()\n\t\t\t\t\t.like(StrUtil.isNotBlank(fieldType.getColumnType()), GenFieldType::getColumnType,\n\t\t\t\t\t\t\tfieldType.getColumnType())));\n\t}\n\n\t/**\n\t * 查询列表\n\t * @param fieldType 查询条件\n\t * @return 包含查询结果的响应对象\n\t */\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"查询列表\", description = \"查询列表\")\n\tpublic R listFieldTypes(GenFieldType fieldType) {\n\t\treturn R.ok(fieldTypeService.list(Wrappers.query(fieldType)));\n\t}\n\n\t/**\n\t * 通过id查询列属性\n\t * @param id 列属性id\n\t * @return 包含查询结果的响应对象\n\t */\n\t@GetMapping(\"/details/{id}\")\n\t@Operation(summary = \"通过id查询\", description = \"通过id查询\")\n\tpublic R getFieldTypeById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(fieldTypeService.getById(id));\n\t}\n\n\t/**\n\t * 根据查询条件获取字段类型详情\n\t * @param query 字段类型查询条件\n\t * @return 包含查询结果的响应对象\n\t */\n\t@GetMapping(\"/details\")\n\t@Operation(summary = \"根据查询条件获取字段类型详情\", description = \"根据查询条件获取字段类型详情\")\n\tpublic R getFieldTypeDetails(GenFieldType query) {\n\t\treturn R.ok(fieldTypeService.getOne(Wrappers.query(query), false));\n\t}\n\n\t/**\n\t * 新增列属性\n\t * @param fieldType 列属性对象\n\t * @return 操作结果\n\t */\n\t@PostMapping\n\t@SysLog(\"新增列属性\")\n\t@Operation(summary = \"新增列属性\", description = \"新增列属性\")\n\tpublic R saveFieldType(@RequestBody GenFieldType fieldType) {\n\t\treturn R.ok(fieldTypeService.save(fieldType));\n\t}\n\n\t/**\n\t * 修改列属性\n\t * @param fieldType 列属性对象\n\t * @return 操作结果\n\t */\n\t@PutMapping\n\t@SysLog(\"修改列属性\")\n\t@Operation(summary = \"修改列属性\", description = \"修改列属性\")\n\tpublic R updateFieldType(@RequestBody GenFieldType fieldType) {\n\t\treturn R.ok(fieldTypeService.updateById(fieldType));\n\t}\n\n\t/**\n\t * 通过id批量删除列属性\n\t * @param ids 要删除的列属性id数组\n\t * @return 操作结果\n\t */\n\t@DeleteMapping\n\t@SysLog(\"通过id删除列属性\")\n\t@Operation(summary = \"通过id删除列属性\", description = \"通过id删除列属性\")\n\tpublic R removeFieldTypeByIds(@RequestBody Long[] ids) {\n\t\treturn R.ok(fieldTypeService.removeBatchByIds(CollUtil.toList(ids)));\n\t}\n\n\t/**\n\t * 导出excel表格\n\t * @param fieldType 查询条件\n\t * @return excel文件数据列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@Operation(summary = \"导出excel表格\", description = \"导出excel表格\")\n\tpublic List<GenFieldType> exportFieldTypes(GenFieldType fieldType) {\n\t\treturn fieldTypeService.list(Wrappers.query(fieldType));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenGroupController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.controller;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\nimport com.pig4cloud.pig.codegen.service.GenGroupService;\nimport com.pig4cloud.pig.codegen.util.vo.GroupVO;\nimport com.pig4cloud.pig.codegen.util.vo.TemplateGroupDTO;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 模板分组管理控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/group\")\n@Tag(description = \"group\", name = \"模板分组管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class GenGroupController {\n\n\tprivate final GenGroupService genGroupService;\n\n\t/**\n\t * 分页查询模板分组\n\t * @param page 分页对象\n\t * @param genGroup 模板分组查询条件\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@HasPermission(\"codegen_group_view\")\n\t@Operation(summary = \"分页查询模板分组\", description = \"分页查询模板分组\")\n\tpublic R getGroupPage(Page page, GenGroupEntity genGroup) {\n\t\tLambdaQueryWrapper<GenGroupEntity> wrapper = Wrappers.<GenGroupEntity>lambdaQuery()\n\t\t\t.like(genGroup.getId() != null, GenGroupEntity::getId, genGroup.getId())\n\t\t\t.like(StrUtil.isNotEmpty(genGroup.getGroupName()), GenGroupEntity::getGroupName, genGroup.getGroupName());\n\t\treturn R.ok(genGroupService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 通过id查询模板分组\n\t * @param id id\n\t * @return R\n\t */\n\t@GetMapping(\"/{id}\")\n\t@HasPermission(\"codegen_group_view\")\n\t@Operation(summary = \"通过id查询模板分组\", description = \"通过id查询模板分组\")\n\tpublic R getGroupById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(genGroupService.getGroupVoById(id));\n\t}\n\n\t/**\n\t * 新增模板分组\n\t * @param genTemplateGroup 模板分组\n\t * @return R\n\t */\n\t@PostMapping\n\t@SysLog(\"新增模板分组\")\n\t@HasPermission(\"codegen_group_add\")\n\t@Operation(summary = \"新增模板分组\", description = \"新增模板分组\")\n\tpublic R saveGroup(@RequestBody TemplateGroupDTO genTemplateGroup) {\n\t\tgenGroupService.saveGenGroup(genTemplateGroup);\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 修改模板分组\n\t * @param groupVo 模板分组\n\t * @return R\n\t */\n\t@PutMapping\n\t@SysLog(\"修改模板分组\")\n\t@HasPermission(\"codegen_group_edit\")\n\t@Operation(summary = \"修改模板分组\", description = \"修改模板分组\")\n\tpublic R updateGroup(@RequestBody GroupVO groupVo) {\n\t\tgenGroupService.updateGroupAndTemplateById(groupVo);\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 通过id删除模板分组\n\t * @param ids id列表\n\t * @return R\n\t */\n\t@DeleteMapping\n\t@SysLog(\"通过id删除模板分组\")\n\t@HasPermission(\"codegen_group_del\")\n\t@Operation(summary = \"通过id删除模板分组\", description = \"通过id删除模板分组\")\n\tpublic R removeGroupByIds(@RequestBody Long[] ids) {\n\t\tgenGroupService.delGroupAndTemplate(ids);\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 导出excel 表格\n\t * @param genGroup 查询条件\n\t * @return excel 文件流\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"codegen_group_export\")\n\t@Operation(summary = \"导出模板分组\", description = \"导出模板分组\")\n\tpublic List<GenGroupEntity> exportGroups(GenGroupEntity genGroup) {\n\t\treturn genGroupService.list(Wrappers.query(genGroup));\n\t}\n\n\t/**\n\t * 查询列表\n\t * @return 包含列表数据的响应信息\n\t */\n\t@GetMapping(\"/list\")\n\t@Operation(summary = \"查询列表\", description = \"查询列表\")\n\tpublic R listGroups() {\n\t\tList<GenGroupEntity> list = genGroupService\n\t\t\t.list(Wrappers.<GenGroupEntity>lambdaQuery().orderByDesc(GenGroupEntity::getCreateTime));\n\t\treturn R.ok(list);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenTableController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.controller;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.codegen.entity.GenTable;\nimport com.pig4cloud.pig.codegen.entity.GenTableColumnEntity;\nimport com.pig4cloud.pig.codegen.service.GenTableColumnService;\nimport com.pig4cloud.pig.codegen.service.GenTableService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 代码表管理控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/table\")\n@Tag(description = \"table\", name = \"代码表管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class GenTableController {\n\n\tprivate final GenTableColumnService tableColumnService;\n\n\t/**\n\t * 表服务\n\t */\n\tprivate final GenTableService tableService;\n\n\t/**\n\t * 分页查询\n\t * @param page 分页对象\n\t * @param table 列属性\n\t * @return\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页查询\", description = \"分页查询\")\n\tpublic R getTablePage(Page page, GenTable table) {\n\t\treturn R.ok(tableService.queryTablePage(page, table));\n\t}\n\n\t/**\n\t * 通过id查询表信息（代码生成设置 + 表 + 字段设置）\n\t * @param id id\n\t * @return R\n\t */\n\t@GetMapping(\"/{id}\")\n\t@Operation(summary = \"通过id查询\", description = \"通过id查询\")\n\tpublic R getTableById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(tableService.getById(id));\n\t}\n\n\t/**\n\t * 查询数据源所有表\n\t * @param dsName 数据源名称\n\t * @return 包含表列表的响应结果\n\t */\n\t@GetMapping(\"/list/{dsName}\")\n\t@Operation(summary = \"查询数据源所有表\", description = \"查询数据源所有表\")\n\tpublic R listTables(@PathVariable(\"dsName\") String dsName) {\n\t\treturn R.ok(tableService.queryTableList(dsName));\n\t}\n\n\t/**\n\t * 获取表信息\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t */\n\t@GetMapping(\"/{dsName}/{tableName}\")\n\t@Operation(summary = \"获取表信息\", description = \"获取表信息\")\n\tpublic R<GenTable> getTable(@PathVariable(\"dsName\") String dsName, @PathVariable String tableName) {\n\t\treturn R.ok(tableService.queryOrBuildTable(dsName, tableName));\n\t}\n\n\t/**\n\t * 查询表DDL语句\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t */\n\t@GetMapping(\"/column/{dsName}/{tableName}\")\n\t@Operation(summary = \"查询表Column的DDL语句\", description = \"查询表Column的DDL语句\")\n\tpublic R getTableColumn(@PathVariable(\"dsName\") String dsName, @PathVariable String tableName) throws Exception {\n\t\treturn R.ok(tableService.queryTableColumn(dsName, tableName));\n\t}\n\n\t/**\n\t * 查询表DDL语句\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t */\n\t@GetMapping(\"/ddl/{dsName}/{tableName}\")\n\t@Operation(summary = \"查询表DDL语句\", description = \"查询表DDL语句\")\n\tpublic R getTableDdl(@PathVariable(\"dsName\") String dsName, @PathVariable String tableName) throws Exception {\n\t\treturn R.ok(tableService.queryTableDdl(dsName, tableName));\n\t}\n\n\t/**\n\t * 同步表信息\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t */\n\t@GetMapping(\"/sync/{dsName}/{tableName}\")\n\t@Operation(summary = \"同步表信息\", description = \"同步表信息\")\n\tpublic R<GenTable> syncTable(@PathVariable(\"dsName\") String dsName, @PathVariable String tableName) {\n\t\t// 表配置删除\n\t\ttableService.remove(\n\t\t\t\tWrappers.<GenTable>lambdaQuery().eq(GenTable::getDsName, dsName).eq(GenTable::getTableName, tableName));\n\t\t// 字段配置删除\n\t\ttableColumnService.remove(Wrappers.<GenTableColumnEntity>lambdaQuery()\n\t\t\t.eq(GenTableColumnEntity::getDsName, dsName)\n\t\t\t.eq(GenTableColumnEntity::getTableName, tableName));\n\t\treturn R.ok(tableService.queryOrBuildTable(dsName, tableName));\n\t}\n\n\t/**\n\t * 修改列属性\n\t * @param table 列属性\n\t * @return R\n\t */\n\t@PutMapping\n\t@SysLog(\"修改列属性\")\n\t@Operation(summary = \"修改列属性\", description = \"修改列属性\")\n\tpublic R updateTable(@RequestBody GenTable table) {\n\t\treturn R.ok(tableService.updateById(table));\n\t}\n\n\t/**\n\t * 修改表字段数据\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t * @param tableFieldList 字段列表\n\t */\n\t@PutMapping(\"/field/{dsName}/{tableName}\")\n\t@Operation(summary = \"修改表字段数据\", description = \"修改表字段数据\")\n\tpublic R<String> updateTableField(@PathVariable(\"dsName\") String dsName, @PathVariable String tableName,\n\t\t\t@RequestBody List<GenTableColumnEntity> tableFieldList) {\n\t\ttableColumnService.updateTableField(dsName, tableName, tableFieldList);\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 导出excel 表格\n\t * @param table 查询条件\n\t * @return excel 文件流\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@Operation(summary = \"导出字段数据\", description = \"导出字段数据\")\n\tpublic List<GenTable> exportTables(GenTable table) {\n\t\treturn tableService.list(Wrappers.query(table));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenTemplateController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateEntity;\nimport com.pig4cloud.pig.codegen.service.GenTemplateService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.xss.core.XssCleanIgnore;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 模板管理控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/template\")\n@Tag(description = \"template\", name = \"模板管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class GenTemplateController {\n\n\tprivate final GenTemplateService genTemplateService;\n\n\t/**\n\t * 分页查询模板信息\n\t * @param page 分页参数对象\n\t * @param genTemplate 模板查询条件\n\t * @return 分页查询结果\n\t */\n\t@Operation(summary = \"分页查询\", description = \"分页查询\")\n\t@GetMapping(\"/page\")\n\t@HasPermission(\"codegen_template_view\")\n\tpublic R getTemplatePage(Page page, GenTemplateEntity genTemplate) {\n\t\tLambdaQueryWrapper<GenTemplateEntity> wrapper = Wrappers.<GenTemplateEntity>lambdaQuery()\n\t\t\t.like(genTemplate.getId() != null, GenTemplateEntity::getId, genTemplate.getId())\n\t\t\t.like(StrUtil.isNotEmpty(genTemplate.getTemplateName()), GenTemplateEntity::getTemplateName,\n\t\t\t\t\tgenTemplate.getTemplateName());\n\t\treturn R.ok(genTemplateService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 查询全部模板\n\t * @return\n\t */\n\t@Operation(summary = \"查询全部\", description = \"查询全部\")\n\t@GetMapping(\"/list\")\n\t@HasPermission(\"codegen_template_view\")\n\tpublic R listTemplates() {\n\t\treturn R.ok(genTemplateService\n\t\t\t.list(Wrappers.<GenTemplateEntity>lambdaQuery().orderByDesc(GenTemplateEntity::getCreateTime)));\n\t}\n\n\t/**\n\t * 通过id查询模板\n\t * @param id id\n\t * @return R\n\t */\n\t@Operation(summary = \"通过id查询\", description = \"通过id查询\")\n\t@GetMapping(\"/{id}\")\n\t@HasPermission(\"codegen_template_view\")\n\tpublic R getTemplateById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(genTemplateService.getById(id));\n\t}\n\n\t/**\n\t * 新增模板\n\t * @param genTemplate 模板\n\t * @return R\n\t */\n\t@XssCleanIgnore\n\t@Operation(summary = \"新增模板\", description = \"新增模板\")\n\t@SysLog(\"新增模板\")\n\t@PostMapping\n\t@HasPermission(\"codegen_template_add\")\n\tpublic R saveTemplate(@RequestBody GenTemplateEntity genTemplate) {\n\t\treturn R.ok(genTemplateService.save(genTemplate));\n\t}\n\n\t/**\n\t * 修改模板\n\t * @param genTemplate 模板\n\t * @return R\n\t */\n\t@XssCleanIgnore\n\t@Operation(summary = \"修改模板\", description = \"修改模板\")\n\t@SysLog(\"修改模板\")\n\t@PutMapping\n\t@HasPermission(\"codegen_template_edit\")\n\tpublic R updateTemplate(@RequestBody GenTemplateEntity genTemplate) {\n\t\treturn R.ok(genTemplateService.updateById(genTemplate));\n\t}\n\n\t/**\n\t * 通过id删除模板\n\t * @param ids id列表\n\t * @return R\n\t */\n\t@Operation(summary = \"通过id删除模板\", description = \"通过id删除模板\")\n\t@SysLog(\"通过id删除模板\")\n\t@DeleteMapping\n\t@HasPermission(\"codegen_template_del\")\n\tpublic R removeTemplateByIds(@RequestBody Long[] ids) {\n\t\treturn R.ok(genTemplateService.removeBatchByIds(CollUtil.toList(ids)));\n\t}\n\n\t/**\n\t * 导出excel 表格\n\t * @param genTemplate 查询条件\n\t * @return excel 文件流\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"codegen_template_export\")\n\t@Operation(summary = \"导出模板\", description = \"导出模板\")\n\tpublic List<GenTemplateEntity> exportTemplates(GenTemplateEntity genTemplate) {\n\t\treturn genTemplateService.list(Wrappers.query(genTemplate));\n\t}\n\n\t/**\n\t * 在线更新模板\n\t * @return R\n\t */\n\t@Operation(summary = \"在线更新模板\", description = \"在线更新模板\")\n\t@GetMapping(\"/online\")\n\t@HasPermission(\"codegen_template_view\")\n\tpublic R online() {\n\t\treturn genTemplateService.onlineUpdate();\n\t}\n\n\t/**\n\t * 检查版本\n\t * @return {@link R }\n\t */\n\t@Operation(summary = \"在线检查模板\", description = \"在线检查模板\")\n\t@GetMapping(\"/checkVersion\")\n\t@HasPermission(\"codegen_template_view\")\n\tpublic R checkVersion() {\n\t\treturn genTemplateService.checkVersion();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenTemplateGroupController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity;\nimport com.pig4cloud.pig.codegen.service.GenTemplateGroupService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 模板分组关联表\n *\n * @author PIG\n * @date 2023-02-22 09:25:15\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/templateGroup\")\n@Tag(description = \"templateGroup\", name = \"模板分组关联表管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class GenTemplateGroupController {\n\n\tprivate final GenTemplateGroupService genTemplateGroupService;\n\n\t/**\n\t * 分页查询\n\t * @param page 分页对象\n\t * @param genTemplateGroup 模板分组关联表\n\t * @return\n\t */\n\t@Operation(summary = \"分页查询\", description = \"分页查询\")\n\t@GetMapping(\"/page\")\n\t@HasPermission(\"codegen_templateGroup_view\")\n\tpublic R getTemplateGroupPage(Page page, GenTemplateGroupEntity genTemplateGroup) {\n\t\tLambdaQueryWrapper<GenTemplateGroupEntity> wrapper = Wrappers.lambdaQuery();\n\t\treturn R.ok(genTemplateGroupService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 通过id查询模板分组关联表\n\t * @param groupId id\n\t * @return R\n\t */\n\t@Operation(summary = \"通过id查询\", description = \"通过id查询\")\n\t@GetMapping(\"/{groupId}\")\n\t@HasPermission(\"codegen_templateGroup_view\")\n\tpublic R getTemplateGroupById(@PathVariable(\"groupId\") Long groupId) {\n\t\treturn R.ok(genTemplateGroupService.getById(groupId));\n\t}\n\n\t/**\n\t * 新增模板分组关联表\n\t * @param genTemplateGroup 模板分组关联表\n\t * @return R\n\t */\n\t@Operation(summary = \"新增模板分组关联表\", description = \"新增模板分组关联表\")\n\t@SysLog(\"新增模板分组关联表\")\n\t@PostMapping\n\t@HasPermission(\"codegen_templateGroup_add\")\n\tpublic R saveTemplateGroup(@RequestBody GenTemplateGroupEntity genTemplateGroup) {\n\t\treturn R.ok(genTemplateGroupService.save(genTemplateGroup));\n\t}\n\n\t/**\n\t * 修改模板分组关联表\n\t * @param genTemplateGroup 模板分组关联表\n\t * @return R\n\t */\n\t@Operation(summary = \"修改模板分组关联表\", description = \"修改模板分组关联表\")\n\t@SysLog(\"修改模板分组关联表\")\n\t@PutMapping\n\t@HasPermission(\"codegen_templateGroup_edit\")\n\tpublic R updateTemplateGroup(@RequestBody GenTemplateGroupEntity genTemplateGroup) {\n\t\treturn R.ok(genTemplateGroupService.updateById(genTemplateGroup));\n\t}\n\n\t/**\n\t * 通过id删除模板分组关联表\n\t * @param ids groupId列表\n\t * @return R\n\t */\n\t@Operation(summary = \"通过id删除模板分组关联表\", description = \"通过id删除模板分组关联表\")\n\t@SysLog(\"通过id删除模板分组关联表\")\n\t@DeleteMapping\n\t@HasPermission(\"codegen_templateGroup_del\")\n\tpublic R removeTemplateGroupByIds(@RequestBody Long[] ids) {\n\t\treturn R.ok(genTemplateGroupService.removeBatchByIds(CollUtil.toList(ids)));\n\t}\n\n\t/**\n\t * 导出excel 表格\n\t * @param genTemplateGroup 查询条件\n\t * @return excel 文件流\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@HasPermission(\"codegen_templateGroup_export\")\n\t@Operation(summary = \"导出excel模板分组\", description = \"导出excel模板分组\")\n\tpublic List<GenTemplateGroupEntity> exportTemplateGroups(GenTemplateGroupEntity genTemplateGroup) {\n\t\treturn genTemplateGroupService.list(Wrappers.query(genTemplateGroup));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GeneratorController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.controller;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.codegen.service.GeneratorService;\nimport com.pig4cloud.pig.common.core.util.R;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.io.ByteArrayOutputStream;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * 代码生成器控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/generator\")\n@Tag(description = \"generator\", name = \"代码生成器控制器管理模块\")\npublic class GeneratorController {\n\n\tprivate final GeneratorService generatorService;\n\n\t/**\n\t * ZIP 下载生成代码\n\t * @param tableIds 数据表ID\n\t * @param response 流输出对象\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/download\")\n\t@Operation(summary = \"ZIP下载生成代码\", description = \"ZIP下载生成代码\")\n\tpublic void download(String tableIds, HttpServletResponse response) {\n\t\tByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n\t\tZipOutputStream zip = new ZipOutputStream(outputStream);\n\n\t\t// 生成代码\n\t\tfor (String tableId : tableIds.split(StrUtil.COMMA)) {\n\t\t\tgeneratorService.downloadCode(Long.parseLong(tableId), zip);\n\t\t}\n\n\t\tIoUtil.close(zip);\n\n\t\t// zip压缩包数据\n\t\tbyte[] data = outputStream.toByteArray();\n\n\t\tresponse.reset();\n\t\tresponse.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format(\"attachment; filename=%s.zip\", tableIds));\n\t\tresponse.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length));\n\t\tresponse.setContentType(\"application/octet-stream; charset=UTF-8\");\n\t\tIoUtil.write(response.getOutputStream(), false, data);\n\t}\n\n\t/**\n\t * 生成代码\n\t * @param tableIds 表ID列表，多个ID用逗号分隔\n\t * @return 操作结果\n\t * @throws Exception 生成代码过程中可能抛出的异常\n\t */\n\t@ResponseBody\n\t@GetMapping(\"/code\")\n\t@Operation(summary = \"生成代码\", description = \"生成代码\")\n\tpublic R<String> code(String tableIds) throws Exception {\n\t\t// 生成代码\n\t\tfor (String tableId : tableIds.split(StrUtil.COMMA)) {\n\t\t\tgeneratorService.generatorCode(Long.valueOf(tableId));\n\t\t}\n\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 预览代码\n\t * @param tableId 表ID\n\t * @return 代码预览结果列表\n\t */\n\t@SneakyThrows\n\t@GetMapping(\"/preview\")\n\t@Operation(summary = \"预览代码\", description = \"预览代码\")\n\tpublic List<Map<String, String>> preview(Long tableId) {\n\t\treturn generatorService.preview(tableId);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/ColumnEntity.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport lombok.Data;\n\n/**\n * @author lengleng\n * @date 2018/07/29 列属性： https://blog.csdn.net/lkforce/article/details/79557482\n */\n@Data\npublic class ColumnEntity {\n\n\t/**\n\t * 列表\n\t */\n\tprivate String columnName;\n\n\t/**\n\t * 数据类型\n\t */\n\tprivate String dataType;\n\n\t/**\n\t * JAVA 数据类型\n\t */\n\tprivate String javaType;\n\n\t/**\n\t * 备注\n\t */\n\tprivate String comments;\n\n\t/**\n\t * 驼峰属性\n\t */\n\tprivate String caseAttrName;\n\n\t/**\n\t * 普通属性\n\t */\n\tprivate String lowerAttrName;\n\n\t/**\n\t * 属性类型\n\t */\n\tprivate String attrType;\n\n\t/**\n\t * 其他信息\n\t */\n\tprivate String extra;\n\n\t/**\n\t * 字段类型\n\t */\n\tprivate String columnType;\n\n\t/**\n\t * 是否可以为空\n\t */\n\tprivate Boolean nullable;\n\n\t/**\n\t * 是否隐藏\n\t */\n\tprivate Boolean hidden;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenConfig.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport lombok.Data;\n\n/**\n * @author lengleng\n * @date 2018/8/2 生成配置\n */\n@Data\npublic class GenConfig {\n\n\t/**\n\t * 数据源name\n\t */\n\tprivate String dsName;\n\n\t/**\n\t * 包名\n\t */\n\tprivate String packageName;\n\n\t/**\n\t * 作者\n\t */\n\tprivate String author;\n\n\t/**\n\t * 模块名称\n\t */\n\tprivate String moduleName;\n\n\t/**\n\t * 表前缀\n\t */\n\tprivate String tablePrefix;\n\n\t/**\n\t * 表名称\n\t */\n\tprivate String tableName;\n\n\t/**\n\t * 表备注\n\t */\n\tprivate String comments;\n\n\t/**\n\t * 代码风格 0 - avue 1 - element 2 - uview\n\t */\n\tprivate String style;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenDatasourceConf.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 数据源表\n *\n * @author lengleng\n * @date 2019-03-31 16:00:20\n */\n@Data\n@TableName(\"gen_datasource_conf\")\n@EqualsAndHashCode(callSuper = true)\npublic class GenDatasourceConf extends Model<GenDatasourceConf> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 主键\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\tprivate Long id;\n\n\t/**\n\t * 名称\n\t */\n\tprivate String name;\n\n\t/**\n\t * 数据库类型\n\t */\n\tprivate String dsType;\n\n\t/**\n\t * 配置类型 （0 主机形式 | 1 url形式）\n\t */\n\tprivate Integer confType;\n\n\t/**\n\t * 主机地址\n\t */\n\tprivate String host;\n\n\t/**\n\t * 端口\n\t */\n\tprivate Integer port;\n\n\t/**\n\t * jdbc-url\n\t */\n\tprivate String url;\n\n\t/**\n\t * 实例\n\t */\n\tprivate String instance;\n\n\t/**\n\t * 数据库名称\n\t */\n\tprivate String dsName;\n\n\t/**\n\t * 用户名\n\t */\n\tprivate String username;\n\n\t/**\n\t * 密码\n\t */\n\tprivate String password;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 0-正常，1-删除\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenFieldType.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 列属性\n *\n * @author pigx code generator\n * @date 2023-02-06 20:16:01\n */\n@Data\n@TableName(\"gen_field_type\")\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"列属性\")\npublic class GenFieldType extends Model<GenFieldType> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * id\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"id\")\n\tprivate Long id;\n\n\t/**\n\t * 字段类型\n\t */\n\t@Schema(description = \"字段类型\")\n\tprivate String columnType;\n\n\t/**\n\t * 属性类型\n\t */\n\t@Schema(description = \"属性类型\")\n\tprivate String attrType;\n\n\t/**\n\t * 属性包名\n\t */\n\t@Schema(description = \"属性包名\")\n\tprivate String packageName;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@Schema(description = \"修改时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 删除标识（0-正常,1-删除）\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenGroupEntity.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 模板分组\n *\n * @author PIG\n * @date 2023-02-21 20:01:53\n */\n@Data\n@TableName(\"gen_group\")\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"模板分组\")\npublic class GenGroupEntity extends Model<GenGroupEntity> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * id\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"id\")\n\tprivate Long id;\n\n\t/**\n\t * 分组名称\n\t */\n\t@Schema(description = \"分组名称\")\n\tprivate String groupName;\n\n\t/**\n\t * 分组描述\n\t */\n\t@Schema(description = \"分组描述\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate String groupDesc;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@Schema(description = \"修改时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 删除标识（0-正常,1-删除）\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTable.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 列属性\n *\n * @author pigx code generator\n * @date 2023-02-06 20:34:55\n */\n@Data\n@TableName(\"gen_table\")\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"列属性\")\npublic class GenTable extends Model<GenTable> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * id\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"id\")\n\tprivate Long id;\n\n\t/**\n\t * 数据源名称\n\t */\n\t@Schema(description = \"数据源名称\")\n\tprivate String dsName;\n\n\t/**\n\t * 数据源类型\n\t */\n\t@Schema(description = \"数据源类型\")\n\tprivate String dbType;\n\n\t/**\n\t * 表名\n\t */\n\t@Schema(description = \"表名\")\n\tprivate String tableName;\n\n\t/**\n\t * 类名\n\t */\n\t@Schema(description = \"类名\")\n\tprivate String className;\n\n\t/**\n\t * 说明\n\t */\n\t@Schema(description = \"说明\")\n\tprivate String tableComment;\n\n\t/**\n\t * 作者\n\t */\n\t@Schema(description = \"作者\")\n\tprivate String author;\n\n\t/**\n\t * 邮箱\n\t */\n\t@Schema(description = \"邮箱\")\n\tprivate String email;\n\n\t/**\n\t * 项目包名\n\t */\n\t@Schema(description = \"项目包名\")\n\tprivate String packageName;\n\n\t/**\n\t * 项目版本号\n\t */\n\t@Schema(description = \"项目版本号\")\n\tprivate String version;\n\n\t/**\n\t * 生成方式 0：zip压缩包 1：自定义目录\n\t */\n\t@Schema(description = \"生成方式  0：zip压缩包   1：自定义目录\")\n\tprivate String generatorType;\n\n\t/**\n\t * 后端生成路径\n\t */\n\t@Schema(description = \"后端生成路径\")\n\tprivate String backendPath;\n\n\t/**\n\t * 前端生成路径\n\t */\n\t@Schema(description = \"前端生成路径\")\n\tprivate String frontendPath;\n\n\t/**\n\t * 模块名\n\t */\n\t@Schema(description = \"模块名\")\n\tprivate String moduleName;\n\n\t/**\n\t * 功能名\n\t */\n\t@Schema(description = \"功能名\")\n\tprivate String functionName;\n\n\t/**\n\t * 表单布局 1：一列 2：两列\n\t */\n\t@Schema(description = \"表单布局  1：一列   2：两列\")\n\tprivate Integer formLayout;\n\n\t/**\n\t * 基类ID\n\t */\n\t@Schema(description = \"基类ID\")\n\tprivate Long baseclassId;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 代码生成风格\n\t */\n\tprivate Long style;\n\n\t/**\n\t * 子表名称\n\t */\n\tprivate String childTableName;\n\n\t/**\n\t * 主表关联键\n\t */\n\tprivate String mainField;\n\n\t/**\n\t * 子表关联键\n\t */\n\tprivate String childField;\n\n\t/**\n\t * 字段列表\n\t */\n\t@TableField(exist = false)\n\tprivate List<GenTableColumnEntity> fieldList;\n\n\t/**\n\t * 子表字段列表\n\t */\n\t@TableField(exist = false)\n\tprivate List<GenTableColumnEntity> childFieldList;\n\n\t/**\n\t * 代码风格（模版分组信息）\n\t */\n\t@TableField(exist = false)\n\tprivate List<GenGroupEntity> groupList;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTableColumnEntity.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * @author lengleng\n * @date 2023-02-06\n *\n * 记录表字段的配置信息\n */\n@Data\n@TableName(\"gen_table_column\")\n@EqualsAndHashCode(callSuper = true)\npublic class GenTableColumnEntity extends Model<GenDatasourceConf> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 主键\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\tprivate Long id;\n\n\t/**\n\t * 数据源名\n\t */\n\tprivate String dsName;\n\n\t/**\n\t * 表名称\n\t */\n\tprivate String tableName;\n\n\t/**\n\t * 字段名称\n\t */\n\tprivate String fieldName;\n\n\t/**\n\t * 排序\n\t */\n\tprivate Integer sort;\n\n\t/**\n\t * 字段类型\n\t */\n\tprivate String fieldType;\n\n\t/**\n\t * 字段说明\n\t */\n\tprivate String fieldComment;\n\n\t/**\n\t * 属性名\n\t */\n\tprivate String attrName;\n\n\t/**\n\t * 属性类型\n\t */\n\tprivate String attrType;\n\n\t/**\n\t * 属性包名\n\t */\n\tprivate String packageName;\n\n\t/**\n\t * 自动填充\n\t */\n\tprivate String autoFill;\n\n\t/**\n\t * 主键 0：否 1：是\n\t */\n\tprivate String primaryPk;\n\n\t/**\n\t * 基类字段 0：否 1：是\n\t */\n\tprivate String baseField;\n\n\t/**\n\t * 表单项 0：否 1：是\n\t */\n\tprivate String formItem;\n\n\t/**\n\t * 表单必填 0：否 1：是\n\t */\n\tprivate String formRequired;\n\n\t/**\n\t * 表单类型\n\t */\n\tprivate String formType;\n\n\t/**\n\t * 表单效验\n\t */\n\tprivate String formValidator;\n\n\t/**\n\t * 列表项 0：否 1：是\n\t */\n\tprivate String gridItem;\n\n\t/**\n\t * 列表排序 0：否 1：是\n\t */\n\tprivate String gridSort;\n\n\t/**\n\t * 查询项 0：否 1：是\n\t */\n\tprivate String queryItem;\n\n\t/**\n\t * 查询方式\n\t */\n\tprivate String queryType;\n\n\t/**\n\t * 查询表单类型\n\t */\n\tprivate String queryFormType;\n\n\t/**\n\t * 字段字典类型\n\t */\n\t@TableField(updateStrategy = FieldStrategy.ALWAYS)\n\tprivate String fieldDict;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTemplateEntity.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * 模板\n *\n * @author PIG\n * @date 2023-02-21 17:15:44\n */\n@Data\n@TableName(\"gen_template\")\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"模板\")\npublic class GenTemplateEntity extends Model<GenTemplateEntity> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 主键\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"主键\")\n\tprivate Long id;\n\n\t/**\n\t * 模板名称\n\t */\n\t@Schema(description = \"模板名称\")\n\tprivate String templateName;\n\n\t/**\n\t * 模板路径\n\t */\n\t@Schema(description = \"模板路径\")\n\tprivate String generatorPath;\n\n\t/**\n\t * 模板描述\n\t */\n\t@Schema(description = \"模板描述\")\n\tprivate String templateDesc;\n\n\t/**\n\t * 模板代码\n\t */\n\t@Schema(description = \"模板代码\")\n\tprivate String templateCode;\n\n\t/**\n\t * 创建人\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"创建人\")\n\tprivate String createBy;\n\n\t/**\n\t * 修改人\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\t@Schema(description = \"修改人\")\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@Schema(description = \"修改时间\")\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 删除标识（0-正常,1-删除）\n\t */\n\t@TableLogic\n\t@TableField(fill = FieldFill.INSERT)\n\t@Schema(description = \"删除标记,1:已删除,0:正常\")\n\tprivate String delFlag;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTemplateGroupEntity.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\n/**\n * 模板分组关联表\n *\n * @author PIG\n * @date 2023-02-22 09:25:15\n */\n@Data\n@TableName(\"gen_template_group\")\n@Accessors(chain = true)\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"模板分组关联表\")\npublic class GenTemplateGroupEntity extends Model<GenTemplateGroupEntity> {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 分组id\n\t */\n\t@Schema(description = \"分组id\")\n\tprivate Long groupId;\n\n\t/**\n\t * 模板id\n\t */\n\t@Schema(description = \"模板id\")\n\tprivate Long templateId;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/TableEntity.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.entity;\n\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * @author lengleng\n * @date 2018/07/29 表属性： https://blog.csdn.net/lkforce/article/details/79557482\n */\n@Data\npublic class TableEntity {\n\n\t/**\n\t * 名称\n\t */\n\tprivate String tableName;\n\n\t/**\n\t * 备注\n\t */\n\tprivate String comments;\n\n\t/**\n\t * 主键\n\t */\n\tprivate ColumnEntity pk;\n\n\t/**\n\t * 列名\n\t */\n\tprivate List<ColumnEntity> columns;\n\n\t/**\n\t * 驼峰类型\n\t */\n\tprivate String caseClassName;\n\n\t/**\n\t * 普通类型\n\t */\n\tprivate String lowerClassName;\n\n\t/**\n\t * 数据库类型 （用于根据数据库个性化）\n\t */\n\tprivate String dbType;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenDatasourceConfMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenDatasourceConf;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 数据源表 Mapper 接口\n *\n * @author lengleng\n * @date 2019-03-31 16:00:20\n */\n@Mapper\npublic interface GenDatasourceConfMapper extends BaseMapper<GenDatasourceConf> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenDynamicMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.annotation.InterceptorIgnore;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\n\n/**\n * 动态查询Mapper接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenDynamicMapper {\n\n\t/**\n\t * 动态SQL查询\n\t * @param sq SQL查询语句\n\t * @return 查询结果列表，每个结果以LinkedHashMap形式存储\n\t */\n\t@InterceptorIgnore(tenantLine = \"true\")\n\tList<LinkedHashMap<String, Object>> dynamicQuerySql(@Param(\"value\") String sq);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenFieldTypeMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenFieldType;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Set;\n\n/**\n * 字段类型映射器接口：用于操作字段类型相关数据库操作\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenFieldTypeMapper extends BaseMapper<GenFieldType> {\n\n\t/**\n\t * 根据tableId，获取包列表\n\t * @param dsName 数据源名称\n\t * @param tableName 表名称\n\t * @return 返回包列表\n\t */\n\tSet<String> getPackageByTableId(@Param(\"dsName\") String dsName, @Param(\"tableName\") String tableName);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenGroupMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\nimport com.pig4cloud.pig.codegen.util.vo.GroupVO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n/**\n * 模板分组 Mapper 接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenGroupMapper extends BaseMapper<GenGroupEntity> {\n\n\t/**\n\t * 根据ID获取分组VO对象\n\t * @param id 分组ID\n\t * @return 分组VO对象\n\t */\n\tGroupVO getGroupVoById(@Param(\"id\") Long id);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTableColumnMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenTableColumnEntity;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 代码生成表列属性Mapper接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenTableColumnMapper extends BaseMapper<GenTableColumnEntity> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTableMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenTable;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 代码生成表 Mapper 接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenTableMapper extends BaseMapper<GenTable> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTemplateGroupMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 模板分组关联表 Mapper 接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenTemplateGroupMapper extends BaseMapper<GenTemplateGroupEntity> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTemplateMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateEntity;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 代码生成模板Mapper接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface GenTemplateMapper extends BaseMapper<GenTemplateEntity> {\n\n\t/**\n\t * 根据模板组ID查询模板列表\n\t * @param groupId 模板组ID\n\t * @return 模板实体列表\n\t */\n\tList<GenTemplateEntity> listTemplateById(Long groupId);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenDatasourceConfService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenDatasourceConf;\n\n/**\n * 数据源配置服务接口 提供数据源的增删改查及校验等功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenDatasourceConfService extends IService<GenDatasourceConf> {\n\n\t/**\n\t * 保存数据源并加密\n\t * @param genDatasourceConf 数据源配置信息\n\t * @return 保存是否成功\n\t */\n\tBoolean saveDsByEnc(GenDatasourceConf genDatasourceConf);\n\n\t/**\n\t * 更新数据源\n\t * @param genDatasourceConf 数据源配置信息\n\t * @return 更新是否成功\n\t */\n\tBoolean updateDsByEnc(GenDatasourceConf genDatasourceConf);\n\n\t/**\n\t * 添加动态数据源\n\t * @param datasourceConf 数据源配置信息\n\t */\n\tvoid addDynamicDataSource(GenDatasourceConf datasourceConf);\n\n\t/**\n\t * 校验数据源配置是否有效\n\t * @param datasourceConf 数据源配置信息\n\t * @return true表示有效，false表示无效\n\t */\n\tBoolean checkDataSource(GenDatasourceConf datasourceConf);\n\n\t/**\n\t * 通过数据源ID删除数据源\n\t * @param dsIds 数据源ID数组\n\t * @return 删除是否成功\n\t */\n\tBoolean removeByDsId(Long[] dsIds);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenFieldTypeService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenFieldType;\n\nimport java.util.Set;\n\n/**\n * 列属性服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenFieldTypeService extends IService<GenFieldType> {\n\n\t/**\n\t * 根据tableId，获取包列表\n\t * @param dsName 数据源名称\n\t * @param tableName 表名称\n\t * @return 返回包列表\n\t */\n\tSet<String> getPackageByTableId(String dsName, String tableName);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenGroupService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\nimport com.pig4cloud.pig.codegen.util.vo.GroupVO;\nimport com.pig4cloud.pig.codegen.util.vo.TemplateGroupDTO;\n\n/**\n * 模板分组服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenGroupService extends IService<GenGroupEntity> {\n\n\t/**\n\t * 保存生成模板组\n\t * @param genTemplateGroup 模板组DTO对象\n\t */\n\tvoid saveGenGroup(TemplateGroupDTO genTemplateGroup);\n\n\t/**\n\t * 删除分组极其关系\n\t * @param ids\n\t */\n\tvoid delGroupAndTemplate(Long[] ids);\n\n\t/**\n\t * 查询group数据\n\t * @param id\n\t */\n\tGroupVO getGroupVoById(Long id);\n\n\t/**\n\t * 更新group数据\n\t * @param GroupVo\n\t */\n\tvoid updateGroupAndTemplateById(GroupVO GroupVo);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTableColumnService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenTableColumnEntity;\n\nimport java.util.List;\n\n/**\n * 代码生成表列服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenTableColumnService extends IService<GenTableColumnEntity> {\n\n\t/**\n\t * 初始化字段列表\n\t * @param tableFieldList 表字段实体列表\n\t */\n\tvoid initFieldList(List<GenTableColumnEntity> tableFieldList);\n\n\t/**\n\t * 更新表字段信息\n\t * @param dsName 数据源名称\n\t * @param tableName 表名\n\t * @param tableFieldList 表字段列表\n\t */\n\tvoid updateTableField(String dsName, String tableName, List<GenTableColumnEntity> tableFieldList);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTableService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenTable;\nimport org.anyline.metadata.Table;\n\nimport java.util.List;\n\n/**\n * 代码生成表服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenTableService extends IService<GenTable> {\n\n\t/**\n\t * 查询对应数据源的表\n\t * @param page 分页信息\n\t * @param table 查询条件\n\t * @return 表\n\t */\n\tIPage queryTablePage(Page<Table> page, GenTable table);\n\n\t/**\n\t * 查询表信息（列），然后插入到中间表中\n\t * @param dsName 数据源\n\t * @param tableName 表名\n\t * @return GenTable\n\t */\n\tGenTable queryOrBuildTable(String dsName, String tableName);\n\n\t/**\n\t * 查询表ddl 语句\n\t * @param dsName 数据源名称\n\t * @param tableName 表名称\n\t * @return ddl 语句\n\t * @throws Exception\n\t */\n\tString queryTableDdl(String dsName, String tableName) throws Exception;\n\n\t/**\n\t * 查询数据源里面的全部表\n\t * @param dsName 数据源名称\n\t * @return table\n\t */\n\tList<String> queryTableList(String dsName);\n\n\t/**\n\t * 查询表的全部字段\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t * @return column\n\t */\n\tList<String> queryTableColumn(String dsName, String tableName);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTemplateGroupService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity;\n\n/**\n * 模板分组关联服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenTemplateGroupService extends IService<GenTemplateGroupEntity> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTemplateService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateEntity;\nimport com.pig4cloud.pig.common.core.util.R;\n\n/**\n * 代码生成模板服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GenTemplateService extends IService<GenTemplateEntity> {\n\n\t/**\n\t * 检查版本信息\n\t * @return 返回检查结果，包含版本信息\n\t */\n\tR checkVersion();\n\n\t/**\n\t * 在线更新\n\t * @return 更新结果\n\t */\n\tR onlineUpdate();\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GeneratorService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * 代码生成服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface GeneratorService {\n\n\t/**\n\t * 生成代码zip写出\n\t * @param tableId 表\n\t * @param zip 输出流\n\t */\n\tvoid downloadCode(Long tableId, ZipOutputStream zip);\n\n\t/**\n\t * 预览代码\n\t * @param tableId 表\n\t * @return [{模板名称:渲染结果}]\n\t */\n\tList<Map<String, String>> preview(Long tableId);\n\n\t/**\n\t * 目标目录写入渲染结果\n\t * @param tableId 表\n\t */\n\tvoid generatorCode(Long tableId);\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenDatasourceConfServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.dynamic.datasource.DynamicRoutingDataSource;\nimport com.baomidou.dynamic.datasource.creator.DataSourceCreator;\nimport com.baomidou.dynamic.datasource.creator.DataSourceProperty;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.entity.GenDatasourceConf;\nimport com.pig4cloud.pig.codegen.mapper.GenDatasourceConfMapper;\nimport com.pig4cloud.pig.codegen.service.GenDatasourceConfService;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.common.datasource.util.DsConfTypeEnum;\nimport com.pig4cloud.pig.common.datasource.util.DsJdbcUrlEnum;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jasypt.encryption.StringEncryptor;\nimport org.springframework.stereotype.Service;\n\nimport javax.sql.DataSource;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\n\n/**\n * 数据源配置服务实现类\n *\n * <p>\n * 提供数据源的增删改查及校验功能，支持数据源密码加密存储\n * </p>\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class GenDatasourceConfServiceImpl extends ServiceImpl<GenDatasourceConfMapper, GenDatasourceConf>\n\t\timplements GenDatasourceConfService {\n\n\tprivate final StringEncryptor stringEncryptor;\n\n\tprivate final DataSourceCreator hikariDataSourceCreator;\n\n\t/**\n\t * 保存数据源配置并进行加密处理\n\t * @param conf 数据源配置信息\n\t * @return 保存成功返回true，失败返回false\n\t */\n\t@Override\n\tpublic Boolean saveDsByEnc(GenDatasourceConf conf) {\n\t\t// 校验配置合法性\n\t\tif (!checkDataSource(conf)) {\n\t\t\treturn Boolean.FALSE;\n\t\t}\n\n\t\t// 添加动态数据源\n\t\taddDynamicDataSource(conf);\n\n\t\t// 更新数据库配置\n\t\tconf.setPassword(stringEncryptor.encrypt(conf.getPassword()));\n\t\tthis.baseMapper.insert(conf);\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 更新加密数据源\n\t * @param conf 数据源配置信息\n\t * @return 更新成功返回true，失败返回false\n\t */\n\t@Override\n\tpublic Boolean updateDsByEnc(GenDatasourceConf conf) {\n\t\tif (!checkDataSource(conf)) {\n\t\t\treturn Boolean.FALSE;\n\t\t}\n\t\t// 先移除\n\t\tDynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);\n\t\tdynamicRoutingDataSource.removeDataSource(baseMapper.selectById(conf.getId()).getName());\n\n\t\t// 再添加\n\t\taddDynamicDataSource(conf);\n\n\t\t// 更新数据库配置\n\t\tif (StrUtil.isNotBlank(conf.getPassword())) {\n\t\t\tconf.setPassword(stringEncryptor.encrypt(conf.getPassword()));\n\t\t}\n\t\tthis.baseMapper.updateById(conf);\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 通过数据源ID删除数据源\n\t * @param dsIds 数据源ID数组\n\t * @return 删除是否成功\n\t */\n\t@Override\n\tpublic Boolean removeByDsId(Long[] dsIds) {\n\t\tDynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);\n\t\tthis.baseMapper.selectByIds(CollUtil.toList(dsIds))\n\t\t\t.forEach(ds -> dynamicRoutingDataSource.removeDataSource(ds.getName()));\n\t\tthis.baseMapper.deleteByIds(CollUtil.toList(dsIds));\n\t\treturn Boolean.TRUE;\n\t}\n\n\t/**\n\t * 添加动态数据源\n\t * @param conf 数据源配置信息\n\t */\n\t@Override\n\tpublic void addDynamicDataSource(GenDatasourceConf conf) {\n\t\tDataSourceProperty dataSourceProperty = new DataSourceProperty();\n\t\tdataSourceProperty.setPoolName(conf.getName());\n\t\tdataSourceProperty.setUrl(conf.getUrl());\n\t\tdataSourceProperty.setUsername(conf.getUsername());\n\t\tdataSourceProperty.setPassword(conf.getPassword());\n\t\tDataSource dataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);\n\n\t\tDynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);\n\t\tdynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource);\n\t}\n\n\t/**\n\t * 校验数据源配置是否有效\n\t * @param conf 数据源配置信息\n\t * @return 数据源配置是否有效，true表示有效\n\t * @throws RuntimeException 数据库连接失败时抛出异常\n\t */\n\t@Override\n\tpublic Boolean checkDataSource(GenDatasourceConf conf) {\n\t\tString url;\n\t\t// JDBC 配置形式\n\t\tif (DsConfTypeEnum.JDBC.getType().equals(conf.getConfType())) {\n\t\t\turl = conf.getUrl();\n\t\t}\n\t\telse if (DsJdbcUrlEnum.MSSQL.getDbName().equals(conf.getDsType())) {\n\t\t\t// 主机形式 sql server 特殊处理\n\t\t\tDsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(conf.getDsType());\n\t\t\turl = String.format(urlEnum.getUrl(), conf.getHost(), conf.getPort(), conf.getDsName());\n\t\t}\n\t\telse {\n\t\t\tDsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(conf.getDsType());\n\t\t\turl = String.format(urlEnum.getUrl(), conf.getHost(), conf.getPort(), conf.getDsName());\n\t\t}\n\n\t\tconf.setUrl(url);\n\n\t\ttry (Connection connection = DriverManager.getConnection(url, conf.getUsername(), conf.getPassword())) {\n\t\t}\n\t\tcatch (SQLException e) {\n\t\t\tlog.error(\"数据源配置 {} , 获取链接失败\", conf.getName(), e);\n\t\t\tthrow new RuntimeException(\"数据库配置错误，链接失败\");\n\t\t}\n\t\treturn Boolean.TRUE;\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenFieldTypeServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.entity.GenFieldType;\nimport com.pig4cloud.pig.codegen.mapper.GenFieldTypeMapper;\nimport com.pig4cloud.pig.codegen.service.GenFieldTypeService;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * 列属性\n *\n * @author pigx code generator\n * @date 2023-02-06 20:16:01\n */\n@Service\npublic class GenFieldTypeServiceImpl extends ServiceImpl<GenFieldTypeMapper, GenFieldType>\n\t\timplements GenFieldTypeService {\n\n\t/**\n\t * 根据tableId，获取包列表\n\t * @param dsName 数据源名称\n\t * @param tableName 表名称\n\t * @return 返回包列表\n\t */\n\t@Override\n\tpublic Set<String> getPackageByTableId(String dsName, String tableName) {\n\t\tSet<String> importList = baseMapper.getPackageByTableId(dsName, tableName);\n\t\treturn importList.stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet());\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenGroupServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport org.springframework.stereotype.Service;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity;\nimport com.pig4cloud.pig.codegen.mapper.GenGroupMapper;\nimport com.pig4cloud.pig.codegen.service.GenGroupService;\nimport com.pig4cloud.pig.codegen.service.GenTemplateGroupService;\nimport com.pig4cloud.pig.codegen.util.vo.GroupVO;\nimport com.pig4cloud.pig.codegen.util.vo.TemplateGroupDTO;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.AllArgsConstructor;\n\n/**\n * 模板分组服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@AllArgsConstructor\npublic class GenGroupServiceImpl extends ServiceImpl<GenGroupMapper, GenGroupEntity> implements GenGroupService {\n\n\tprivate final GenTemplateGroupService genTemplateGroupService;\n\n\t/**\n\t * 保存模板分组信息\n\t * @param genTemplateGroup 模板分组DTO对象，包含分组信息及关联模板ID列表\n\t */\n\t@Override\n\tpublic void saveGenGroup(TemplateGroupDTO genTemplateGroup) {\n\t\t// 1.保存group\n\t\tGenGroupEntity groupEntity = new GenGroupEntity();\n\t\tBeanUtil.copyProperties(genTemplateGroup, groupEntity);\n\t\tbaseMapper.insert(groupEntity);\n\t\t// 2.保存关系\n\t\tList<GenTemplateGroupEntity> goals = new LinkedList<>();\n\t\tfor (Long TemplateId : genTemplateGroup.getTemplateId()) {\n\t\t\tGenTemplateGroupEntity templateGroup = new GenTemplateGroupEntity();\n\t\t\ttemplateGroup.setTemplateId(TemplateId).setGroupId(groupEntity.getId());\n\t\t\tgoals.add(templateGroup);\n\t\t}\n\t\tgenTemplateGroupService.saveBatch(goals);\n\n\t}\n\n\t/**\n\t * 按照分组ID数组删除分组及其关联模板\n\t * @param ids 分组ID数组\n\t */\n\t@Override\n\tpublic void delGroupAndTemplate(Long[] ids) {\n\t\t// 删除分组\n\t\tthis.removeBatchByIds(CollUtil.toList(ids));\n\t\t// 删除关系\n\t\tgenTemplateGroupService.remove(Wrappers.<GenTemplateGroupEntity>lambdaQuery()\n\t\t\t.in(GenTemplateGroupEntity::getGroupId, CollUtil.toList(ids)));\n\t}\n\n\t/**\n\t * 根据ID查询组信息\n\t * @param id 组ID\n\t * @return 组信息视图对象\n\t */\n\t@Override\n\tpublic GroupVO getGroupVoById(Long id) {\n\t\treturn baseMapper.getGroupVoById(id);\n\t}\n\n\t/**\n\t * 根据ID更新分组及其关联模板\n\t * @param groupVo 分组VO对象，包含分组ID和模板ID列表\n\t */\n\t@Override\n\tpublic void updateGroupAndTemplateById(GroupVO groupVo) {\n\t\t// 1.更新自身\n\t\tGenGroupEntity groupEntity = new GenGroupEntity();\n\t\tBeanUtil.copyProperties(groupVo, groupEntity);\n\t\tthis.updateById(groupEntity);\n\t\t// 2.更新模板\n\t\t// 2.1根据id删除之前的模板\n\t\tgenTemplateGroupService.remove(\n\t\t\t\tWrappers.<GenTemplateGroupEntity>lambdaQuery().eq(GenTemplateGroupEntity::getGroupId, groupVo.getId()));\n\t\t// 2.2根据ids创建新的模板分组赋值\n\t\tList<GenTemplateGroupEntity> goals = new LinkedList<>();\n\t\tfor (Long templateId : groupVo.getTemplateId()) {\n\t\t\tgoals.add(new GenTemplateGroupEntity().setGroupId(groupVo.getId()).setTemplateId(templateId));\n\t\t}\n\t\tgenTemplateGroupService.saveBatch(goals);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTableColumnServiceImpl.java",
    "content": "package com.pig4cloud.pig.codegen.service.impl;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.springframework.stereotype.Service;\n\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.entity.GenFieldType;\nimport com.pig4cloud.pig.codegen.entity.GenTableColumnEntity;\nimport com.pig4cloud.pig.codegen.mapper.GenFieldTypeMapper;\nimport com.pig4cloud.pig.codegen.mapper.GenTableColumnMapper;\nimport com.pig4cloud.pig.codegen.service.GenTableColumnService;\n\nimport cn.hutool.core.text.NamingCase;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 表字段信息管理服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@RequiredArgsConstructor\npublic class GenTableColumnServiceImpl extends ServiceImpl<GenTableColumnMapper, GenTableColumnEntity>\n\t\timplements GenTableColumnService {\n\n\tprivate final GenFieldTypeMapper fieldTypeMapper;\n\n\t/**\n\t * 初始化表单字段列表，主要是将数据库表中的字段转化为表单需要的字段数据格式，并为审计字段排序\n\t * @param tableFieldList 表单字段列表\n\t */\n\tpublic void initFieldList(List<GenTableColumnEntity> tableFieldList) {\n\t\t// 字段类型、属性类型映射\n\t\tList<GenFieldType> list = fieldTypeMapper.selectList(Wrappers.emptyWrapper());\n\t\tMap<String, GenFieldType> fieldTypeMap = new LinkedHashMap<>(list.size());\n\t\tlist.forEach(\n\t\t\t\tfieldTypeMapping -> fieldTypeMap.put(fieldTypeMapping.getColumnType().toLowerCase(), fieldTypeMapping));\n\n\t\t// 索引计数器\n\t\tAtomicInteger index = new AtomicInteger(0);\n\t\ttableFieldList.forEach(field -> {\n\t\t\t// 将字段名转化为驼峰格式\n\t\t\tfield.setAttrName(NamingCase.toCamelCase(field.getFieldName()));\n\n\t\t\t// 获取字段对应的类型\n\t\t\tGenFieldType fieldTypeMapping = fieldTypeMap.getOrDefault(field.getFieldType().toLowerCase(), null);\n\t\t\tif (fieldTypeMapping == null) {\n\t\t\t\t// 没找到对应的类型，则为Object类型\n\t\t\t\tfield.setAttrType(\"Object\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfield.setAttrType(fieldTypeMapping.getAttrType());\n\t\t\t\tfield.setPackageName(fieldTypeMapping.getPackageName());\n\t\t\t}\n\n\t\t\t// 设置查询类型和表单查询类型都为“=”\n\t\t\tfield.setQueryType(\"=\");\n\t\t\tfield.setQueryFormType(\"text\");\n\n\t\t\t// 设置表单类型为文本框类型\n\t\t\tfield.setFormType(\"text\");\n\n\t\t\t// 保证审计字段最后显示\n\t\t\tfield.setSort(Objects.isNull(field.getSort()) ? index.getAndIncrement() : field.getSort());\n\t\t});\n\t}\n\n\t/**\n\t * 更新指定数据源和表名的表单字段信息\n\t * @param dsName 数据源名称\n\t * @param tableName 表名\n\t * @param tableFieldList 表单字段列表\n\t */\n\t@Override\n\tpublic void updateTableField(String dsName, String tableName, List<GenTableColumnEntity> tableFieldList) {\n\t\tAtomicInteger sort = new AtomicInteger();\n\t\tthis.updateBatchById(tableFieldList.stream().peek(field -> field.setSort(sort.getAndIncrement())).toList());\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTableServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.text.NamingCase;\nimport cn.hutool.core.util.EnumUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.config.PigCodeGenDefaultProperties;\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\nimport com.pig4cloud.pig.codegen.entity.GenTable;\nimport com.pig4cloud.pig.codegen.entity.GenTableColumnEntity;\nimport com.pig4cloud.pig.codegen.mapper.GenTableMapper;\nimport com.pig4cloud.pig.codegen.service.GenGroupService;\nimport com.pig4cloud.pig.codegen.service.GenTableColumnService;\nimport com.pig4cloud.pig.codegen.service.GenTableService;\nimport com.pig4cloud.pig.codegen.util.AutoFillEnum;\nimport com.pig4cloud.pig.codegen.util.BoolFillEnum;\nimport com.pig4cloud.pig.codegen.util.CommonColumnFiledEnum;\nimport com.pig4cloud.pig.codegen.util.GenKit;\nimport lombok.RequiredArgsConstructor;\nimport org.anyline.metadata.Column;\nimport org.anyline.metadata.Database;\nimport org.anyline.metadata.Table;\nimport org.anyline.proxy.CacheProxy;\nimport org.anyline.proxy.ServiceProxy;\nimport org.anyline.service.AnylineService;\nimport org.jetbrains.annotations.NotNull;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 代码生成表服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@RequiredArgsConstructor\npublic class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTable> implements GenTableService {\n\n\tprivate final PigCodeGenDefaultProperties configurationProperties;\n\n\tprivate final GenTableColumnService columnService;\n\n\tprivate final GenGroupService genGroupService;\n\n\t/**\n\t * 查询表ddl 语句\n\t * @param dsName 数据源名称\n\t * @param tableName 表名称\n\t * @return ddl 语句\n\t * @throws Exception\n\t */\n\t@Override\n\tpublic String queryTableDdl(String dsName, String tableName) throws Exception {\n\t\t// 手动切换数据源\n\t\tDynamicDataSourceContextHolder.push(dsName);\n\t\tTable table = ServiceProxy.metadata().table(tableName); // 获取表结构\n\t\ttable.execute(false);// 不执行SQL\n\t\tServiceProxy.ddl().create(table);\n\t\treturn table.getDdl();// 返回创建表的DDL\n\t}\n\n\t/**\n\t * 查询表的全部字段\n\t * @param dsName 数据源\n\t * @param tableName 表名称\n\t * @return column\n\t */\n\t@Override\n\tpublic List<String> queryTableColumn(String dsName, String tableName) {\n\t\t// 手动切换数据源\n\t\tDynamicDataSourceContextHolder.push(dsName);\n\t\tCacheProxy.clear();\n\t\treturn ServiceProxy.metadata().columns(tableName).values().stream().map(Column::getName).toList();\n\t}\n\n\t/**\n\t * 查询对应数据源的表\n\t * @param page 分页信息\n\t * @param table 查询条件\n\t * @return 表\n\t */\n\t@Override\n\tpublic IPage queryTablePage(Page<Table> page, GenTable table) {\n\t\t// 手动切换数据源\n\t\tDynamicDataSourceContextHolder.push(table.getDsName());\n\t\tCacheProxy.clear();\n\t\tList<Table> tableList = ServiceProxy.metadata().tables().values().stream().filter(t -> {\n\t\t\tif (StrUtil.isBlank(table.getTableName())) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn StrUtil.containsIgnoreCase(t.getName(false), table.getTableName());\n\t\t}).toList();\n\n\t\t// 根据 page 进行分页\n\t\tList<Table> records = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tableList);\n\t\tpage.setTotal(tableList.size());\n\t\tpage.setRecords(records);\n\t\treturn page;\n\t}\n\n\t/**\n\t * 查询数据源里面的全部表\n\t * @param dsName 数据源名称\n\t * @return table\n\t */\n\t@Override\n\tpublic List<String> queryTableList(String dsName) {\n\t\t// 手动切换数据源\n\t\tDynamicDataSourceContextHolder.push(dsName);\n\t\tCacheProxy.clear();\n\t\treturn ServiceProxy.metadata().tables().values().stream().map(Table::getName).toList();\n\t}\n\n\t/**\n\t * 查询表信息（列），然后插入到中间表中\n\t * @param dsName 数据源\n\t * @param tableName 表名\n\t * @return GenTable\n\t */\n\t@Override\n\tpublic GenTable queryOrBuildTable(String dsName, String tableName) {\n\t\tGenTable genTable = baseMapper.selectOne(\n\t\t\t\tWrappers.<GenTable>lambdaQuery().eq(GenTable::getTableName, tableName).eq(GenTable::getDsName, dsName));\n\t\t// 如果 genTable 为空， 执行导入\n\t\tif (Objects.isNull(genTable)) {\n\t\t\tgenTable = this.tableImport(dsName, tableName);\n\t\t}\n\n\t\tList<GenTableColumnEntity> fieldList = columnService.list(Wrappers.<GenTableColumnEntity>lambdaQuery()\n\t\t\t.eq(GenTableColumnEntity::getDsName, dsName)\n\t\t\t.eq(GenTableColumnEntity::getTableName, tableName)\n\t\t\t.orderByAsc(GenTableColumnEntity::getSort));\n\t\tgenTable.setFieldList(fieldList);\n\n\t\t// 查询模板分组信息\n\t\tList<GenGroupEntity> groupEntities = genGroupService\n\t\t\t.list(Wrappers.<GenGroupEntity>lambdaQuery().orderByDesc(GenGroupEntity::getCreateTime));\n\t\tgenTable.setGroupList(groupEntities);\n\t\treturn genTable;\n\t}\n\n\t/**\n\t * 导入表结构并生成代码配置\n\t * @param dsName 数据源名称\n\t * @param tableName 表名\n\t * @return 生成的表配置信息\n\t * @Transactional 启用事务，遇到异常时回滚\n\t */\n\t@Transactional(rollbackFor = Exception.class)\n\tprotected GenTable tableImport(String dsName, String tableName) {\n\t\t// 手动切换数据源\n\t\tDynamicDataSourceContextHolder.push(dsName);\n\n\t\t// 查询表是否存在\n\t\tGenTable table = new GenTable();\n\t\t// 从数据库获取表信息\n\t\tCacheProxy.clear();\n\t\tAnylineService service = ServiceProxy.service();\n\t\tTable tableMetadata = service.metadata().table(tableName);\n\t\tDatabase database = service.metadata().database();\n\t\t// 获取默认表配置信息 （）\n\n\t\ttable.setPackageName(configurationProperties.getPackageName());\n\t\ttable.setVersion(configurationProperties.getVersion());\n\t\ttable.setBackendPath(configurationProperties.getBackendPath());\n\t\ttable.setFrontendPath(configurationProperties.getFrontendPath());\n\t\ttable.setAuthor(configurationProperties.getAuthor());\n\t\ttable.setEmail(configurationProperties.getEmail());\n\t\ttable.setTableName(tableName);\n\t\ttable.setDsName(dsName);\n\t\ttable.setTableComment(tableMetadata.getComment());\n\n\t\ttable.setDbType(database.getDatabase().title());\n\t\ttable.setFormLayout(configurationProperties.getFormLayout());\n\t\ttable.setGeneratorType(configurationProperties.getGeneratorType());\n\t\ttable.setClassName(NamingCase.toPascalCase(tableName));\n\t\t// 模块名称默认为 admin\n\t\ttable.setModuleName(configurationProperties.getModuleName());\n\t\ttable.setFunctionName(GenKit.getFunctionName(tableName));\n\t\ttable.setCreateTime(LocalDateTime.now());\n\n\t\t// 使用默认数据源\n\t\tDynamicDataSourceContextHolder.clear();\n\t\tthis.save(table);\n\n\t\t// 获取原生字段数据\n\t\tList<GenTableColumnEntity> tableFieldList = getGenTableColumnEntities(dsName, tableName, tableMetadata);\n\n\t\t// 初始化字段数据\n\t\tcolumnService.initFieldList(tableFieldList);\n\t\t// 保存列数据\n\t\tcolumnService.saveOrUpdateBatch(tableFieldList);\n\n\t\ttable.setFieldList(tableFieldList);\n\t\treturn table;\n\t}\n\n\t/**\n\t * 获取表字段信息\n\t * @param dsName 数据源信息\n\t * @param tableName 表名称\n\t * @param tableMetadata 表的元数据\n\t * @return list\n\t */\n\tprivate static @NotNull List<GenTableColumnEntity> getGenTableColumnEntities(String dsName, String tableName,\n\t\t\tTable tableMetadata) {\n\t\tList<GenTableColumnEntity> tableFieldList = new ArrayList<>();\n\t\tLinkedHashMap<String, Column> columns = tableMetadata.getColumns();\n\t\tcolumns.forEach((columnName, column) -> {\n\t\t\tGenTableColumnEntity genTableColumnEntity = new GenTableColumnEntity();\n\t\t\tgenTableColumnEntity.setTableName(tableName);\n\t\t\tgenTableColumnEntity.setDsName(dsName);\n\t\t\tgenTableColumnEntity.setFieldName(column.getName());\n\t\t\tgenTableColumnEntity.setFieldComment(column.getComment());\n\t\t\tgenTableColumnEntity.setFieldType(column.getTypeName());\n\t\t\tgenTableColumnEntity.setPrimaryPk(\n\t\t\t\t\tcolumn.isPrimaryKey() == 1 ? BoolFillEnum.TRUE.getValue() : BoolFillEnum.FALSE.getValue());\n\t\t\tgenTableColumnEntity.setAutoFill(AutoFillEnum.DEFAULT.name());\n\t\t\tgenTableColumnEntity.setFormItem(BoolFillEnum.TRUE.getValue());\n\t\t\tgenTableColumnEntity.setGridItem(BoolFillEnum.TRUE.getValue());\n\n\t\t\t// 审计字段处理\n\t\t\tif (EnumUtil.contains(CommonColumnFiledEnum.class, column.getName())) {\n\t\t\t\tCommonColumnFiledEnum commonColumnFiledEnum = CommonColumnFiledEnum.valueOf(column.getName());\n\t\t\t\tgenTableColumnEntity.setFormItem(commonColumnFiledEnum.getFormItem());\n\t\t\t\tgenTableColumnEntity.setGridItem(commonColumnFiledEnum.getGridItem());\n\t\t\t\tgenTableColumnEntity.setAutoFill(commonColumnFiledEnum.getAutoFill());\n\t\t\t\tgenTableColumnEntity.setSort(commonColumnFiledEnum.getSort());\n\t\t\t}\n\t\t\ttableFieldList.add(genTableColumnEntity);\n\t\t});\n\t\treturn tableFieldList;\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTemplateGroupServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity;\nimport com.pig4cloud.pig.codegen.mapper.GenTemplateGroupMapper;\nimport com.pig4cloud.pig.codegen.service.GenTemplateGroupService;\nimport org.springframework.stereotype.Service;\n\n/**\n * 模板分组关联表服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\npublic class GenTemplateGroupServiceImpl extends ServiceImpl<GenTemplateGroupMapper, GenTemplateGroupEntity>\n\t\timplements GenTemplateGroupService {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTemplateServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpResponse;\nimport cn.hutool.http.HttpStatus;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport cn.smallbun.screw.core.constant.DefaultConstants;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.codegen.config.PigCodeGenDefaultProperties;\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateEntity;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity;\nimport com.pig4cloud.pig.codegen.mapper.GenGroupMapper;\nimport com.pig4cloud.pig.codegen.mapper.GenTemplateGroupMapper;\nimport com.pig4cloud.pig.codegen.mapper.GenTemplateMapper;\nimport com.pig4cloud.pig.codegen.service.GenTemplateService;\nimport com.pig4cloud.pig.codegen.util.vo.GenTemplateFileVO;\nimport com.pig4cloud.pig.common.core.exception.CheckedException;\nimport com.pig4cloud.pig.common.core.util.R;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * 代码生成模板服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class GenTemplateServiceImpl extends ServiceImpl<GenTemplateMapper, GenTemplateEntity>\n\t\timplements GenTemplateService {\n\n\tprivate final GenTemplateGroupMapper genTemplateGroupMapper;\n\n\tprivate final GenGroupMapper genGroupMapper;\n\n\tprivate final PigCodeGenDefaultProperties defaultProperties;\n\n\t/**\n\t * 在线更新模板组\n\t * @return 更新结果，包含成功或失败信息\n\t * @throws Exception 事务执行过程中发生异常时抛出\n\t */\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic R onlineUpdate() {\n\t\t// 获取 config.json 和 version 文件\n\t\tMap<String, Object> configAndVersion = getConfigAndVersion();\n\t\tJSONObject configJsonObj = (JSONObject) configAndVersion.get(\"configJsonObj\");\n\t\tString versionFile = (String) configAndVersion.get(\"versionFile\");\n\n\t\t// 查询出全部的模板组名称\n\t\tSet<String> cgtmConfigGroupNames = configJsonObj.keySet();\n\n\t\tString cgtmConfigGroupName = cgtmConfigGroupNames.iterator().next();\n\t\t// 根据模板组名称+version 查询是否存在，不存在则新增，存在跳过\n\t\tboolean exists = genGroupMapper.exists(Wrappers.<GenGroupEntity>lambdaQuery()\n\t\t\t.eq(GenGroupEntity::getGroupName, cgtmConfigGroupName + versionFile));\n\n\t\tif (exists) {\n\t\t\treturn R.failed(\"已是最新版本，无需更新！\");\n\t\t}\n\n\t\t// 插入新的模板组（名称 + VERSION）, 再解析 config.json group 里面的所有模板\n\t\tinsertTemplateFiles(versionFile, configJsonObj, cgtmConfigGroupName);\n\t\treturn R.ok(\"更新成功，版本号:\" + versionFile);\n\t}\n\n\t/**\n\t * 检查版本\n\t * @return 返回检查结果，包含版本是否存在信息\n\t */\n\tpublic R checkVersion() {\n\t\t// 关闭在线更新提示\n\t\tif (!defaultProperties.isAutoCheckVersion()) {\n\t\t\treturn R.ok(true);\n\t\t}\n\n\t\t// 获取 config.json 和 version 文件\n\t\tMap<String, Object> configAndVersion = getConfigAndVersion();\n\t\tJSONObject configJsonObj = (JSONObject) configAndVersion.get(\"configJsonObj\");\n\t\tString versionFile = (String) configAndVersion.get(\"versionFile\");\n\n\t\t// 查询出全部的模板组名称\n\t\tSet<String> cgtmConfigGroupNames = configJsonObj.keySet();\n\n\t\tString cgtmConfigGroupName = cgtmConfigGroupNames.iterator().next();\n\t\t// 根据模板组名称+version 查询是否存在，不存在则新增，存在跳过\n\t\tboolean exists = genGroupMapper.exists(Wrappers.<GenGroupEntity>lambdaQuery()\n\t\t\t.eq(GenGroupEntity::getGroupName, cgtmConfigGroupName + versionFile));\n\n\t\treturn R.ok(exists);\n\t}\n\n\t/**\n\t * 获取配置和版本\n\t * @return {@link Map }<{@link String }, {@link Object }>\n\t */\n\tprivate Map<String, Object> getConfigAndVersion() {\n\t\t// 获取 config.json 和 version 文件\n\t\tString configFile = getCGTMFile(\"config.json\");\n\t\tString versionFile = getCGTMFile(\"VERSION\");\n\n\t\t// 解析 config.json\n\t\tJSONObject configJsonObj = JSONUtil.parseObj(configFile);\n\n\t\t// 将 configJsonObj 和 versionFile 放入 Map 中\n\t\tMap<String, Object> configAndVersion = new HashMap<>();\n\t\tconfigAndVersion.put(\"configJsonObj\", configJsonObj);\n\t\tconfigAndVersion.put(\"versionFile\", versionFile);\n\n\t\treturn configAndVersion;\n\t}\n\n\t/**\n\t * 插入模板文件\n\t * @param version 版本\n\t * @param configJsonObj config.json\n\t * @param groupName 组名称\n\t */\n\tprivate void insertTemplateFiles(String version, JSONObject configJsonObj, String groupName) {\n\t\t// 创建新的 group\n\t\tGenGroupEntity genGroupEntity = new GenGroupEntity();\n\t\tgenGroupEntity.setGroupName(groupName + version);\n\t\tgenGroupMapper.insert(genGroupEntity);\n\n\t\t// 解析json配置文件\n\t\tList<GenTemplateFileVO> templateFileVOList = configJsonObj.getBeanList(groupName, GenTemplateFileVO.class);\n\t\tfor (GenTemplateFileVO genTemplateFileVO : templateFileVOList) {\n\t\t\t// 1. 获取模板文件\n\t\t\tString templateFile = getCGTMFile(genTemplateFileVO.getTemplateFile());\n\n\t\t\t// 2. 插入模板文件\n\t\t\tGenTemplateEntity genTemplateEntity = new GenTemplateEntity();\n\t\t\tgenTemplateEntity.setTemplateName(genTemplateFileVO.getTemplateName() + version);\n\t\t\tgenTemplateEntity.setTemplateDesc(genTemplateFileVO.getTemplateName() + version);\n\t\t\tgenTemplateEntity.setTemplateCode(templateFile);\n\t\t\tgenTemplateEntity.setGeneratorPath(genTemplateFileVO.getGeneratorPath());\n\t\t\tbaseMapper.insert(genTemplateEntity);\n\n\t\t\t// 3. 插入模板组关联\n\t\t\tGenTemplateGroupEntity genTemplateGroupEntity = new GenTemplateGroupEntity();\n\t\t\tgenTemplateGroupEntity.setTemplateId(genTemplateEntity.getId());\n\t\t\tgenTemplateGroupEntity.setGroupId(genGroupEntity.getId());\n\t\t\tgenTemplateGroupMapper.insert(genTemplateGroupEntity);\n\t\t}\n\t}\n\n\t/**\n\t * 获取 cgtmfile\n\t * @param fileName 文件名\n\t * @return {@link String }\n\t */\n\tprivate String getCGTMFile(String fileName) {\n\t\tHttpResponse response = HttpRequest\n\t\t\t.get(String.format(\"%s/CGTM/raw/next/%s\", DefaultConstants.CGTM_URL, fileName))\n\t\t\t.execute();\n\n\t\tif (response.getStatus() == HttpStatus.HTTP_OK || StrUtil.isNotBlank(response.body())) {\n\t\t\treturn response.body();\n\t\t}\n\t\telse {\n\t\t\tlog.warn(\"在线更新模板失败:{} ，Http Code:{}\", fileName, response.getStatus());\n\t\t\tthrow new CheckedException(\"在线更新模板失败，任务终止！\");\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GeneratorServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.codegen.service.impl;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\nimport org.springframework.boot.SpringBootVersion;\nimport org.springframework.stereotype.Service;\n\nimport com.pig4cloud.pig.codegen.config.PigCodeGenDefaultProperties;\nimport com.pig4cloud.pig.codegen.entity.GenTable;\nimport com.pig4cloud.pig.codegen.entity.GenTableColumnEntity;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateEntity;\nimport com.pig4cloud.pig.codegen.service.GenFieldTypeService;\nimport com.pig4cloud.pig.codegen.service.GenGroupService;\nimport com.pig4cloud.pig.codegen.service.GenTableColumnService;\nimport com.pig4cloud.pig.codegen.service.GenTableService;\nimport com.pig4cloud.pig.codegen.service.GeneratorService;\nimport com.pig4cloud.pig.codegen.util.VelocityKit;\nimport com.pig4cloud.pig.codegen.util.vo.GroupVO;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.text.NamingCase;\nimport cn.hutool.core.util.BooleanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\n/**\n * 代码生成器服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@RequiredArgsConstructor\npublic class GeneratorServiceImpl implements GeneratorService {\n\n\tprivate final PigCodeGenDefaultProperties configurationProperties;\n\n\tprivate final GenTableColumnService columnService;\n\n\tprivate final GenFieldTypeService fieldTypeService;\n\n\tprivate final GenTableService tableService;\n\n\tprivate final GenGroupService genGroupService;\n\n\t/**\n\t * 生成代码zip写出\n\t * @param tableId 表\n\t * @param zip 输出流\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic void downloadCode(Long tableId, ZipOutputStream zip) {\n\t\t// 数据模型\n\t\tMap<String, Object> dataModel = getDataModel(tableId);\n\n\t\tLong style = (Long) dataModel.get(\"style\");\n\n\t\tGroupVO groupVo = genGroupService.getGroupVoById(style);\n\t\tList<GenTemplateEntity> templateList = groupVo.getTemplateList();\n\n\t\tString frontendPath = configurationProperties.getFrontendPath();\n\t\tString backendPath = configurationProperties.getBackendPath();\n\n\t\tfor (GenTemplateEntity template : templateList) {\n\t\t\tString templateCode = template.getTemplateCode();\n\t\t\tString generatorPath = template.getGeneratorPath();\n\n\t\t\tdataModel.put(\"frontendPath\", frontendPath);\n\t\t\tdataModel.put(\"backendPath\", backendPath);\n\t\t\tString content = VelocityKit.renderStr(templateCode, dataModel);\n\t\t\tString path = VelocityKit.renderStr(generatorPath, dataModel);\n\n\t\t\t// 添加到zip\n\t\t\tzip.putNextEntry(new ZipEntry(path));\n\t\t\tIoUtil.writeUtf8(zip, false, content);\n\t\t\tzip.flush();\n\t\t\tzip.closeEntry();\n\t\t}\n\n\t}\n\n\t/**\n\t * 表达式优化的预览代码方法\n\t * @param tableId 表\n\t * @return [{模板名称:渲染结果}]\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic List<Map<String, String>> preview(Long tableId) {\n\t\t// 数据模型\n\t\tMap<String, Object> dataModel = getDataModel(tableId);\n\n\t\tLong style = (Long) dataModel.get(\"style\");\n\n\t\t// 获取模板列表，Lambda 表达式简化代码\n\t\tList<GenTemplateEntity> templateList = genGroupService.getGroupVoById(style).getTemplateList();\n\n\t\tString frontendPath = configurationProperties.getFrontendPath();\n\t\tString backendPath = configurationProperties.getBackendPath();\n\n\t\treturn templateList.stream().map(template -> {\n\t\t\tString templateCode = template.getTemplateCode();\n\t\t\tString generatorPath = template.getGeneratorPath();\n\n\t\t\t// 预览模式下, 使用相对路径展示\n\t\t\tdataModel.put(\"frontendPath\", frontendPath);\n\t\t\tdataModel.put(\"backendPath\", backendPath);\n\t\t\tString content = VelocityKit.renderStr(templateCode, dataModel);\n\t\t\tString path = VelocityKit.renderStr(generatorPath, dataModel);\n\n\t\t\t// 使用 map 简化代码\n\t\t\treturn new HashMap<String, String>(4) {\n\t\t\t\tprivate static final long serialVersionUID = 1L;\n\n\t\t\t\t{\n\t\t\t\t\tput(\"code\", content);\n\t\t\t\t\tput(\"codePath\", path);\n\t\t\t\t}\n\t\t\t};\n\t\t}).collect(Collectors.toList());\n\t}\n\n\t/**\n\t * 目标目录写入渲染结果方法\n\t * @param tableId 表\n\t */\n\t@Override\n\tpublic void generatorCode(Long tableId) {\n\t\t// 数据模型\n\t\tMap<String, Object> dataModel = getDataModel(tableId);\n\t\tLong style = (Long) dataModel.get(\"style\");\n\n\t\t// 获取模板列表，Lambda 表达式简化代码\n\t\tList<GenTemplateEntity> templateList = genGroupService.getGroupVoById(style).getTemplateList();\n\n\t\ttemplateList.forEach(template -> {\n\t\t\tString templateCode = template.getTemplateCode();\n\t\t\tString generatorPath = template.getGeneratorPath();\n\t\t\tString content = VelocityKit.renderStr(templateCode, dataModel);\n\t\t\tString path = VelocityKit.renderStr(generatorPath, dataModel);\n\t\t\tFileUtil.writeUtf8String(content, path);\n\t\t});\n\t}\n\n\t/**\n\t * 通过 Lambda 表达式优化的获取数据模型方法\n\t * @param tableId 表格 ID\n\t * @return 数据模型 Map 对象\n\t */\n\tprivate Map<String, Object> getDataModel(Long tableId) {\n\t\t// 获取表格信息\n\t\tGenTable table = tableService.getById(tableId);\n\t\t// 获取字段列表\n\t\tList<GenTableColumnEntity> fieldList = columnService.lambdaQuery()\n\t\t\t.eq(GenTableColumnEntity::getDsName, table.getDsName())\n\t\t\t.eq(GenTableColumnEntity::getTableName, table.getTableName())\n\t\t\t.orderByAsc(GenTableColumnEntity::getSort)\n\t\t\t.list();\n\n\t\ttable.setFieldList(fieldList);\n\n\t\t// 创建数据模型对象\n\t\tMap<String, Object> dataModel = new HashMap<>();\n\n\t\t// 填充数据模型\n\t\tdataModel.put(\"opensource\", true);\n\t\tdataModel.put(\"isSpringBoot3\", isSpringBoot3());\n\t\tdataModel.put(\"dbType\", table.getDbType());\n\t\tdataModel.put(\"package\", table.getPackageName());\n\t\tdataModel.put(\"packagePath\", table.getPackageName().replace(\".\", \"/\"));\n\t\tdataModel.put(\"version\", table.getVersion());\n\t\tdataModel.put(\"moduleName\", table.getModuleName());\n\t\tdataModel.put(\"ModuleName\", StrUtil.upperFirst(table.getModuleName()));\n\t\tdataModel.put(\"functionName\", table.getFunctionName());\n\t\tdataModel.put(\"FunctionName\", StrUtil.upperFirst(table.getFunctionName()));\n\t\tdataModel.put(\"formLayout\", table.getFormLayout());\n\t\tdataModel.put(\"style\", table.getStyle());\n\t\tdataModel.put(\"author\", table.getAuthor());\n\t\tdataModel.put(\"datetime\", DateUtil.now());\n\t\tdataModel.put(\"date\", DateUtil.today());\n\t\tsetFieldTypeList(dataModel, table);\n\n\t\t// 获取导入的包列表\n\t\tSet<String> importList = fieldTypeService.getPackageByTableId(table.getDsName(), table.getTableName());\n\t\tdataModel.put(\"importList\", importList);\n\t\tdataModel.put(\"tableName\", table.getTableName());\n\t\tdataModel.put(\"tableComment\", table.getTableComment());\n\t\tdataModel.put(\"className\", StrUtil.lowerFirst(table.getClassName()));\n\t\tdataModel.put(\"ClassName\", table.getClassName());\n\t\tdataModel.put(\"fieldList\", table.getFieldList());\n\n\t\tdataModel.put(\"backendPath\", table.getBackendPath());\n\t\tdataModel.put(\"frontendPath\", table.getFrontendPath());\n\n\t\t// 设置子表\n\t\tString childTableName = table.getChildTableName();\n\t\tif (StrUtil.isNotBlank(childTableName)) {\n\t\t\tList<GenTableColumnEntity> childFieldList = columnService.lambdaQuery()\n\t\t\t\t.eq(GenTableColumnEntity::getDsName, table.getDsName())\n\t\t\t\t.eq(GenTableColumnEntity::getTableName, table.getChildTableName())\n\t\t\t\t.list();\n\t\t\tdataModel.put(\"childFieldList\", childFieldList);\n\t\t\tdataModel.put(\"childTableName\", childTableName);\n\t\t\tdataModel.put(\"mainField\", NamingCase.toCamelCase(table.getMainField()));\n\t\t\tdataModel.put(\"childField\", NamingCase.toCamelCase(table.getChildField()));\n\t\t\tdataModel.put(\"ChildClassName\", NamingCase.toPascalCase(childTableName));\n\t\t\tdataModel.put(\"childClassName\", StrUtil.lowerFirst(NamingCase.toPascalCase(childTableName)));\n\t\t\t// 设置是否是多租户模式 (判断字段列表中是否包含 tenant_id 字段)\n\t\t\tchildFieldList.stream()\n\t\t\t\t.filter(genTableColumnEntity -> genTableColumnEntity.getFieldName().equals(\"tenant_id\"))\n\t\t\t\t.findFirst()\n\t\t\t\t.ifPresent(columnEntity -> dataModel.put(\"isChildTenant\", true));\n\t\t}\n\n\t\t// 设置是否是多租户模式 (判断字段列表中是否包含 tenant_id 字段)\n\t\ttable.getFieldList()\n\t\t\t.stream()\n\t\t\t.filter(genTableColumnEntity -> genTableColumnEntity.getFieldName().equals(\"tenant_id\"))\n\t\t\t.findFirst()\n\t\t\t.ifPresent(columnEntity -> dataModel.put(\"isTenant\", true));\n\n\t\treturn dataModel;\n\t}\n\n\t/**\n\t * 判断当前是否是 SpringBoot3 版本\n\t * @return true/fasle\n\t */\n\tprivate boolean isSpringBoot3() {\n\t\treturn StrUtil.startWith(SpringBootVersion.getVersion(), \"3\");\n\t}\n\n\t/**\n\t * 将表字段按照类型分组并存储到数据模型中\n\t * @param dataModel 存储数据的 Map 对象\n\t * @param table 表信息对象\n\t */\n\tprivate void setFieldTypeList(Map<String, Object> dataModel, GenTable table) {\n\t\t// 按字段类型分组，使用 Map 存储不同类型的字段列表\n\t\tMap<Boolean, List<GenTableColumnEntity>> typeMap = table.getFieldList()\n\t\t\t.stream()\n\t\t\t.collect(Collectors.partitioningBy(columnEntity -> BooleanUtil.toBoolean(columnEntity.getPrimaryPk())));\n\n\t\t// 从分组后的 Map 中获取不同类型的字段列表\n\t\tList<GenTableColumnEntity> primaryList = typeMap.get(true);\n\t\tList<GenTableColumnEntity> formList = typeMap.get(false)\n\t\t\t.stream()\n\t\t\t.filter(columnEntity -> BooleanUtil.toBoolean(columnEntity.getFormItem()))\n\t\t\t.toList();\n\t\tList<GenTableColumnEntity> gridList = typeMap.get(false)\n\t\t\t.stream()\n\t\t\t.filter(columnEntity -> BooleanUtil.toBoolean(columnEntity.getGridItem()))\n\t\t\t.toList();\n\t\tList<GenTableColumnEntity> queryList = typeMap.get(false)\n\t\t\t.stream()\n\t\t\t.filter(columnEntity -> BooleanUtil.toBoolean(columnEntity.getQueryItem()))\n\t\t\t.toList();\n\n\t\tif (CollUtil.isNotEmpty(primaryList)) {\n\t\t\tdataModel.put(\"pk\", primaryList.get(0));\n\t\t}\n\t\tdataModel.put(\"primaryList\", primaryList);\n\t\tdataModel.put(\"formList\", formList);\n\t\tdataModel.put(\"gridList\", gridList);\n\t\tdataModel.put(\"queryList\", queryList);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/AutoFillEnum.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\n/**\n * 字段自动填充 枚举\n *\n * @author 阿沐 babamu@126.com\n */\npublic enum AutoFillEnum {\n\n\tDEFAULT, INSERT, UPDATE, INSERT_UPDATE, CREATE;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/BoolFillEnum.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * boolean 类型枚举\n *\n */\n\n@Getter\n@RequiredArgsConstructor\npublic enum BoolFillEnum {\n\n\t/**\n\t * true\n\t */\n\tTRUE(\"1\"),\n\t/**\n\t * false\n\t */\n\tFALSE(\"0\");\n\n\tprivate final String value;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/CommonColumnFiledEnum.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author lengleng\n * @date 2023/3/12\n * <p>\n * 通用字段的填充策略和显示策略\n */\n@Getter\n@AllArgsConstructor\npublic enum CommonColumnFiledEnum {\n\n\t/**\n\t * create_by 字段\n\t */\n\tcreate_by(\"0\", \"0\", AutoFillEnum.INSERT.name(), 100),\n\n\t/**\n\t * create_time 字段\n\t */\n\tcreate_time(\"0\", \"0\", AutoFillEnum.INSERT.name(), 101),\n\t/**\n\t * update_by 字段\n\t */\n\tupdate_by(\"0\", \"0\", AutoFillEnum.INSERT_UPDATE.name(), 102),\n\t/**\n\t * update_time 字段\n\t */\n\tupdate_time(\"0\", \"0\", AutoFillEnum.INSERT_UPDATE.name(), 103),\n\t/**\n\t * del_flag 字段\n\t */\n\tdel_flag(\"0\", \"0\", AutoFillEnum.DEFAULT.name(), 104),\n\t/**\n\t * tenant_id 字段\n\t */\n\ttenant_id(\"0\", \"0\", AutoFillEnum.DEFAULT.name(), 105);\n\n\t/**\n\t * 表单是否默认显示 1/0\n\t */\n\tprivate String formItem;\n\n\t/**\n\t * 表格是否默认显示 1/0\n\t */\n\tprivate String gridItem;\n\n\t/**\n\t * 自动填充策略\n\t */\n\tprivate String autoFill;\n\n\t/**\n\t * 排序值\n\t */\n\tprivate Integer sort;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/DictTool.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\n\nimport java.util.HashSet;\nimport java.util.List;\n\n/**\n * 字典工具类：提供字典相关操作的工具方法\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class DictTool {\n\n\t/**\n\t * 将字段列表转换为带有双引号的逗号分隔的字符串\n\t * @return 带有双引号的逗号分隔的字符串\n\t */\n\tpublic static String quotation(List<String> fields) {\n\n\t\treturn CollUtil.join(new HashSet<>(fields), StrUtil.COMMA, s -> String.format(\"'%s'\", s));\n\t}\n\n\t/**\n\t * 将字段列表转换为逗号分隔的字符串\n\t * @return 逗号分隔的字符串\n\t */\n\tpublic static String format(List<String> fields) {\n\t\treturn CollUtil.join(new HashSet<>(fields), StrUtil.COMMA);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/GenKit.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 代码生成工具类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@UtilityClass\npublic class GenKit {\n\n\t/**\n\t * 获取功能名 sys_a_b sysAb\n\t * @param tableName 表名\n\t * @return 功能名\n\t */\n\tpublic String getFunctionName(String tableName) {\n\t\treturn StrUtil.toCamelCase(tableName);\n\t}\n\n\t/**\n\t * 获取模块名称\n\t * @param packageName 包名\n\t * @return 功能名\n\t */\n\tpublic String getModuleName(String packageName) {\n\t\treturn StrUtil.subAfter(packageName, \".\", true);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/GeneratorStyleEnum.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 代码生成主题\n *\n * @author 冷冷\n */\n@Getter\n@AllArgsConstructor\npublic enum GeneratorStyleEnum {\n\n\tVFORM_JSON(1L, \"element-plus 风格\"),\n\n\tVFORM_FORM(2L, \"uview 风格\");\n\n\t/**\n\t * 对应模板ID\n\t */\n\tprivate Long templateId;\n\n\t/**\n\t * 描述\n\t */\n\tprivate String desc;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/NamingCaseTool.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport cn.hutool.core.text.NamingCase;\n\n/**\n * 命名规则处理工具类，提供驼峰、下划线等命名格式转换功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class NamingCaseTool {\n\n\t/**\n\t * 根据字段名生成对应的get方法名\n\t * @param in 字段名称\n\t * @return 生成的get方法名\n\t */\n\tpublic static String getProperty(String in) {\n\t\treturn String.format(\"get%s\", NamingCase.toPascalCase(in));\n\t}\n\n\t/**\n\t * 根据输入字符串生成setter方法名\n\t * @param in 输入字符串\n\t * @return 生成的setter方法名\n\t */\n\tpublic static String setProperty(String in) {\n\t\treturn String.format(\"set%s\", NamingCase.toPascalCase(in));\n\t}\n\n\t/**\n\t * 将字符串转换为帕斯卡命名格式（首字母大写）\n\t * @param in 输入字符串\n\t * @return 首字母大写的字符串\n\t */\n\tpublic static String pascalCase(String in) {\n\t\treturn String.format(NamingCase.toPascalCase(in));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/VelocityKit.java",
    "content": "package com.pig4cloud.pig.codegen.util;\n\nimport cn.hutool.core.util.CharsetUtil;\nimport org.apache.velocity.VelocityContext;\nimport org.apache.velocity.app.Velocity;\nimport org.apache.velocity.tools.generic.DateTool;\nimport org.apache.velocity.tools.generic.MathTool;\nimport org.springframework.stereotype.Service;\n\nimport java.io.StringWriter;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\n\n/**\n * Velocity模板引擎工具类，提供模板渲染和字符串渲染功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\npublic class VelocityKit {\n\n\t/**\n\t * Velocity 模板渲染方法\n\t * @param template 模板路径\n\t * @param map 数据模型\n\t * @return 渲染后的字符串结果\n\t */\n\tpublic static String render(String template, Map<String, Object> map) {\n\t\t// 设置velocity资源加载器\n\t\tProperties prop = new Properties();\n\t\tprop.put(\"resource.loader.file.class\", \"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader\");\n\t\tVelocity.init(prop);\n\n\t\tVelocityContext context = new VelocityContext(map);\n\t\t// 函数库，使用 Lambda 表达式简化代码\n\t\tOptional.of(new MathTool()).ifPresent(mt -> context.put(\"math\", mt));\n\t\tOptional.of(new DateTool()).ifPresent(dt -> context.put(\"dateTool\", dt));\n\t\tOptional.of(new DictTool()).ifPresent(dt -> context.put(\"dict\", dt));\n\t\tOptional.of(new NamingCaseTool()).ifPresent(nct -> context.put(\"str\", nct));\n\n\t\t// 渲染模板，使用 Lambda 表达式简化代码\n\t\tStringWriter sw = new StringWriter();\n\t\tOptional.ofNullable(Velocity.getTemplate(template, CharsetUtil.UTF_8)).ifPresent(tpl -> tpl.merge(context, sw));\n\t\treturn sw.toString();\n\t}\n\n\t/**\n\t * 渲染文本\n\t * @param str 待渲染的字符串\n\t * @param dataModel 数据模型\n\t * @return 渲染后的字符串\n\t */\n\tpublic static String renderStr(String str, Map<String, Object> dataModel) {\n\t\t// 设置velocity资源加载器\n\t\tVelocity.init();\n\t\tStringWriter stringWriter = new StringWriter();\n\t\tVelocityContext context = new VelocityContext(dataModel);\n\t\t// 函数库\n\t\tcontext.put(\"math\", new MathTool());\n\t\tcontext.put(\"dateTool\", new DateTool());\n\t\tcontext.put(\"dict\", new DictTool());\n\t\tcontext.put(\"str\", new NamingCaseTool());\n\t\tVelocity.evaluate(context, stringWriter, \"renderStr\", str);\n\t\treturn stringWriter.toString();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/GenCreateTableVO.java",
    "content": "package com.pig4cloud.pig.codegen.util.vo;\n/*\n *      Copyright (c) 2018-2025, luolin All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions are met:\n *\n *  Redistributions of source code must retain the above copyright notice,\n *  this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in the\n *  documentation and/or other materials provided with the distribution.\n *  Neither the name of the pig4cloud.com developer nor the names of its\n *  contributors may be used to endorse or promote products derived from\n *  this software without specific prior written permission.\n *  Author: luolin (766488893@qq.com)\n */\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n/**\n * 自动创建表管理\n *\n * @author luolin\n * @date 2022-09-23 21:56:11\n */\n@Data\n@Schema(description = \"自动创建表管理\")\npublic class GenCreateTableVO {\n\n\t/**\n\t * 主键ID\n\t */\n\t@Schema(description = \"主键ID\")\n\tprivate Long id;\n\n\t/**\n\t * 表名称\n\t */\n\t@NotBlank(message = \"表名称不能为空\")\n\t@Schema(description = \"表名称\")\n\tprivate String tableName;\n\n\t/**\n\t * 表注释\n\t */\n\t@NotBlank(message = \"表注释不能为空\")\n\t@Schema(description = \"表注释\")\n\tprivate String comments;\n\n\t/**\n\t * 数据源名称\n\t */\n\t@NotBlank(message = \"数据源名称不能为空\")\n\t@Schema(description = \"数据源名称\")\n\tprivate String dsName;\n\n\t/**\n\t * 主键策略\n\t */\n\t@NotBlank(message = \"主键策略不能为空\")\n\t@Schema(description = \"主键策略\")\n\tprivate String pkPolicy;\n\n\t/**\n\t * 创建人\n\t */\n\t@Schema(description = \"创建人\")\n\tprivate Long createUser;\n\n\t/**\n\t * 创建时间\n\t */\n\t@Schema(description = \"创建时间\")\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 表字段信息\n\t */\n\t@Schema(description = \"表字段信息\")\n\tprivate String columnsInfo;\n\n\t/**\n\t * 字段信息\n\t */\n\t@Schema(description = \"字段信息\")\n\tprivate String columnInfo;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/GenTemplateFileVO.java",
    "content": "package com.pig4cloud.pig.codegen.util.vo;\n\nimport lombok.Data;\n\n/**\n * @author lengleng\n * @date 2024/7/13\n * <p>\n * CGTM 文件路径\n * <p>\n * { \"templateName\": \"Controller\", \"generatorPath\":\n * \"${backendPath}/src/main/java/${packagePath}/${moduleName}/controller/${ClassName}Controller.java\",\n * \"templateDesc\": \"后台Controller\", \"templateFile\": \"temps/Controller\" },\n */\n@Data\npublic class GenTemplateFileVO {\n\n\t/**\n\t * 模板名称\n\t */\n\tprivate String templateName;\n\n\t/**\n\t * 路径\n\t */\n\tprivate String generatorPath;\n\n\t/**\n\t * 模板 desc\n\t */\n\tprivate String templateDesc;\n\n\t/**\n\t * 模板文件\n\t */\n\tprivate String templateFile;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/GroupVO.java",
    "content": "package com.pig4cloud.pig.codegen.util.vo;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.pig4cloud.pig.codegen.entity.GenTemplateEntity;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.util.List;\n\n@Data\npublic class GroupVO {\n\n\t/**\n\t * id\n\t */\n\t@TableId(type = IdType.ASSIGN_ID)\n\t@Schema(description = \"id\")\n\tprivate Long id;\n\n\t/**\n\t * 分组名称\n\t */\n\t@Schema(description = \"分组名称\")\n\tprivate String groupName;\n\n\t/**\n\t * 分组描述\n\t */\n\t@Schema(description = \"分组描述\")\n\tprivate String groupDesc;\n\n\t/**\n\t * 模板ids\n\t */\n\t@Schema(description = \"拥有的模板列表\")\n\tprivate Long[] templateId;\n\n\t/**\n\t * 模板列表\n\t */\n\t@Schema(description = \"拥有的模板列表\")\n\tprivate List<GenTemplateEntity> templateList;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/SqlDto.java",
    "content": "package com.pig4cloud.pig.codegen.util.vo;\n\nimport lombok.Data;\n\n/**\n * @author lengleng\n * @date 2022/5/2\n */\n@Data\npublic class SqlDto {\n\n\t/**\n\t * 数据源ID\n\t */\n\tprivate String dsName;\n\n\t/**\n\t * sql脚本\n\t */\n\tprivate String sql;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/TemplateGroupDTO.java",
    "content": "package com.pig4cloud.pig.codegen.util.vo;\n\nimport java.io.Serial;\nimport java.util.List;\n\nimport com.pig4cloud.pig.codegen.entity.GenGroupEntity;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * @author weimeilayer@gmail.com ✨\n * @date 💓💕 2025年5月30日 🐬🐇 💓💕\n */\n@Data\n@Schema(description = \"模板传输对象\")\n@EqualsAndHashCode(callSuper = true)\npublic class TemplateGroupDTO extends GenGroupEntity {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 模板id集合\n\t */\n\t@Schema(description = \"模板id集合\")\n\tprivate List<Long> templateId;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/application.yml",
    "content": "server:\n  port: 5002\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      username: @nacos.username@\n      password: @nacos.password@\n      discovery:\n        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}\n      config:\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n  config:\n    import:\n      - nacos:application-@profiles.active@.yml\n      - nacos:${spring.application.name}-@profiles.active@.yml\n\n\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~    Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~ Redistribution and use in source and binary forms, with or without\n  ~ modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~ this list of conditions and the following disclaimer.\n  ~ Redistributions in binary form must reproduce the above copyright\n  ~ notice, this list of conditions and the following disclaimer in the\n  ~ documentation and/or other materials provided with the distribution.\n  ~ Neither the name of the pig4cloud.com developer nor the names of its\n  ~ contributors may be used to endorse or promote products derived from\n  ~ this software without specific prior written permission.\n  ~ Author: lengleng (wangiegie@gmail.com)\n  -->\n\n<!--\n    小技巧: 在根pom里面设置统一存放路径，统一管理方便维护\n    <properties>\n        <log-path>/Users/lengleng</log-path>\n    </properties>\n    1. 其他模块加日志输出，直接copy本文件放在resources 目录即可\n    2. 注意修改 <property name=\"${log-path}/log.path\" value=\"\"/> 的value模块\n-->\n<configuration debug=\"false\" scan=\"false\">\n\t<property name=\"log.path\" value=\"logs/${project.artifactId}\"/>\n\t<!-- 彩色日志格式 -->\n\t<property name=\"CONSOLE_LOG_PATTERN\"\n\t\t\t  value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n\t<conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n\t<conversionRule conversionWord=\"wex\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n\t<conversionRule conversionWord=\"wEx\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n\t<!-- Console log output -->\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>${CONSOLE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file debug output -->\n\t<appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/debug.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file error output -->\n\t<appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/error.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t\t<filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n\t\t\t<level>ERROR</level>\n\t\t</filter>\n\t</appender>\n\n\t<logger name=\"org.activiti.engine.impl.db\" level=\"DEBUG\">\n\t\t<appender-ref ref=\"debug\"/>\n\t</logger>\n\n\t<!--nacos 心跳 INFO 屏蔽-->\n\t<logger name=\"com.alibaba.nacos\" level=\"OFF\">\n\t\t<appender-ref ref=\"error\"/>\n\t</logger>\n\t<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n\t<root level=\"debug\">\n\t\t<appender-ref ref=\"console\"/>\n\t\t<appender-ref ref=\"debug\"/>\n\t\t<appender-ref ref=\"error\"/>\n\t</root>\n</configuration>\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/mapper/GenFieldTypeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"com.pig4cloud.pig.codegen.mapper.GenFieldTypeMapper\">\n\n    <resultMap id=\"fieldTypeMap\" type=\"com.pig4cloud.pig.codegen.entity.GenFieldType\">\n        <id property=\"id\" column=\"id\"/>\n        <result property=\"columnType\" column=\"column_type\"/>\n        <result property=\"attrType\" column=\"attr_type\"/>\n        <result property=\"packageName\" column=\"package_name\"/>\n        <result property=\"createTime\" column=\"create_time\"/>\n    </resultMap>\n\n    <select id=\"getPackageByTableId\" resultType=\"String\">\n        select t1.package_name\n        from gen_field_type t1,\n        gen_table_column t2\n        where t1.attr_type = t2.attr_type\n        and t2.ds_name = #{dsName} and t2.table_name = #{tableName}\n    </select>\n</mapper>\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/mapper/GenGroupMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"com.pig4cloud.pig.codegen.mapper.GenGroupMapper\">\n\n  <resultMap id=\"genGroupMap\" type=\"com.pig4cloud.pig.codegen.util.vo.GroupVO\">\n        <id property=\"id\" column=\"group_id\"/>\n        <result property=\"groupName\" column=\"group_name\"/>\n        <result property=\"groupDesc\" column=\"group_desc\"/>\n\t    <collection property=\"templateList\" ofType=\"com.pig4cloud.pig.codegen.entity.GenTemplateEntity\"\n\t\t\t\t  select=\"com.pig4cloud.pig.codegen.mapper.GenTemplateMapper.listTemplateById\" column=\"group_id\">\n\t    </collection>\n  </resultMap>\n\n\t<select id=\"getGroupVoById\" resultMap=\"genGroupMap\">\n\t    SELECT\n        g.id as group_id ,\n        g.group_name ,\n        g.group_desc\n\t\tFROM\n\t\tgen_group g\n\t\tWHERE g.id = #{id}\n\n\t</select>\n\n</mapper>\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/mapper/GenTableMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  ~\n  ~      Copyright (c) 2018-2025, lengleng All rights reserved.\n  ~\n  ~  Redistribution and use in source and binary forms, with or without\n  ~  modification, are permitted provided that the following conditions are met:\n  ~\n  ~ Redistributions of source code must retain the above copyright notice,\n  ~  this list of conditions and the following disclaimer.\n  ~  Redistributions in binary form must reproduce the above copyright\n  ~  notice, this list of conditions and the following disclaimer in the\n  ~  documentation and/or other materials provided with the distribution.\n  ~  Neither the name of the pig4cloud.com developer nor the names of its\n  ~  contributors may be used to endorse or promote products derived from\n  ~  this software without specific prior written permission.\n  ~  Author: lengleng (wangiegie@gmail.com)\n  ~\n  -->\n\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"com.pig4cloud.pig.codegen.mapper.GenTableMapper\">\n\n    <resultMap id=\"tableMap\" type=\"com.pig4cloud.pig.codegen.entity.GenTable\">\n        <id property=\"id\" column=\"id\"/>\n        <result property=\"tableName\" column=\"table_name\"/>\n        <result property=\"className\" column=\"class_name\"/>\n        <result property=\"tableComment\" column=\"table_comment\"/>\n        <result property=\"author\" column=\"author\"/>\n        <result property=\"email\" column=\"email\"/>\n        <result property=\"packageName\" column=\"package_name\"/>\n        <result property=\"version\" column=\"version\"/>\n        <result property=\"generatorType\" column=\"generator_type\"/>\n        <result property=\"backendPath\" column=\"backend_path\"/>\n        <result property=\"frontendPath\" column=\"frontend_path\"/>\n        <result property=\"moduleName\" column=\"module_name\"/>\n        <result property=\"functionName\" column=\"function_name\"/>\n        <result property=\"formLayout\" column=\"form_layout\"/>\n        <result property=\"baseclassId\" column=\"baseclass_id\"/>\n        <result property=\"createTime\" column=\"create_time\"/>\n    </resultMap>\n</mapper>\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/mapper/GenTemplateGroupMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"com.pig4cloud.pig.codegen.mapper.GenTemplateGroupMapper\">\n\n  <resultMap id=\"genTemplateGroupMap\" type=\"com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity\">\n        <id property=\"groupId\" column=\"group_id\"/>\n        <id property=\"templateId\" column=\"template_id\"/>\n  </resultMap>\n</mapper>\n"
  },
  {
    "path": "pig-visual/pig-codegen/src/main/resources/mapper/GenTemplateMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"com.pig4cloud.pig.codegen.mapper.GenTemplateMapper\">\n\n  <resultMap id=\"genTemplateMap\" type=\"com.pig4cloud.pig.codegen.entity.GenTemplateEntity\">\n        <id property=\"id\" column=\"id\"/>\n        <result property=\"templateName\" column=\"template_name\"/>\n        <result property=\"generatorPath\" column=\"generator_path\"/>\n        <result property=\"templateDesc\" column=\"template_desc\"/>\n\t  <result property=\"templateCode\" column=\"template_code\"/>\n  </resultMap>\n\t<select id=\"listTemplateById\" resultType=\"com.pig4cloud.pig.codegen.entity.GenTemplateEntity\">\n\t\tSELECT\n\t\t     t.id as id,t.template_name,t.generator_path,t.template_desc,t.template_code\n\t\tFROM gen_template t ,\n\t\t\t gen_template_group tg\n\t\tWHERE t.id = tg.template_id\n\t\t  AND t.del_flag = '0'\n\t\t  and tg.group_id = #{groupId}\n\t</select>\n</mapper>\n"
  },
  {
    "path": "pig-visual/pig-monitor/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-monitor\n\nARG JAR_FILE=target/pig-monitor.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 5001\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-visual/pig-monitor/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n\t\t xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>com.pig4cloud</groupId>\n\t\t<artifactId>pig-visual</artifactId>\n\t\t<version>${revision}</version>\n\t</parent>\n\n\t<artifactId>pig-monitor</artifactId>\n\t<packaging>jar</packaging>\n\n\t<description>pig 监控模块，基于 spring boot admin</description>\n\n\t<dependencies>\n\t\t<!--监控服务端-->\n\t\t<dependency>\n\t\t\t<groupId>de.codecentric</groupId>\n\t\t\t<artifactId>spring-boot-admin-starter-server</artifactId>\n\t\t\t<version>${spring-boot-admin.version}</version>\n\t\t</dependency>\n\t\t<!--注册中心客户端-->\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n\t\t</dependency>\n\t\t<!--配置中心客户端-->\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n\t\t</dependency>\n\t\t<!--undertow容器-->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-undertow</artifactId>\n\t\t</dependency>\n\t\t<!--web 模块-->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<!--security-->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-config</artifactId>\n        </dependency>\n    </dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t\t<artifactId>docker-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/PigMonitorApplication.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.monitor;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n/**\n * 监控中心应用启动类\n *\n * @author lengleng\n * @date 2018/06/21\n */\n@EnableAdminServer\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class PigMonitorApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigMonitorApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/config/CustomCsrfFilter.java",
    "content": "package com.pig4cloud.pig.monitor.config;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.web.csrf.CsrfToken;\nimport org.springframework.web.filter.OncePerRequestFilter;\nimport org.springframework.web.util.WebUtils;\n\nimport java.io.IOException;\n\n/**\n * 自定义CSRF过滤器，用于处理CSRF令牌相关逻辑\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class CustomCsrfFilter extends OncePerRequestFilter {\n\n\tpublic static final String CSRF_COOKIE_NAME = \"XSRF-TOKEN\";\n\n\t/**\n\t * 处理CSRF令牌的过滤器内部逻辑\n\t * @param request HTTP请求\n\t * @param response HTTP响应\n\t * @param filterChain 过滤器链\n\t * @throws ServletException 如果发生servlet相关异常\n\t * @throws IOException 如果发生I/O异常\n\t */\n\t@Override\n\tprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)\n\t\t\tthrows ServletException, IOException {\n\n\t\tCsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());\n\n\t\tif (csrf != null) {\n\n\t\t\tCookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);\n\t\t\tString token = csrf.getToken();\n\n\t\t\tif (cookie == null || token != null && !token.equals(cookie.getValue())) {\n\t\t\t\tcookie = new Cookie(CSRF_COOKIE_NAME, token);\n\t\t\t\tcookie.setPath(\"/\");\n\t\t\t\tresponse.addCookie(cookie);\n\t\t\t}\n\t\t}\n\n\t\tfilterChain.doFilter(request, response);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/config/SecuritySecureConfig.java",
    "content": "/*\n * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pig4cloud.pig.monitor.config;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport jakarta.servlet.DispatcherType;\nimport org.springframework.boot.autoconfigure.security.SecurityProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport java.util.UUID;\n\n/**\n * 安全配置类：用于配置Spring Security相关设置\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Configuration(proxyBeanMethods = false)\npublic class SecuritySecureConfig {\n\n\tprivate final AdminServerProperties adminServer;\n\n\tprivate final SecurityProperties security;\n\n\t/**\n\t * 构造函数，初始化安全管理配置\n\t * @param adminServer 管理服务器配置属性\n\t * @param security 安全配置属性\n\t */\n\tpublic SecuritySecureConfig(AdminServerProperties adminServer, SecurityProperties security) {\n\t\tthis.adminServer = adminServer;\n\t\tthis.security = security;\n\t}\n\n\t/**\n\t * 配置Spring Security过滤器链\n\t * @param http HttpSecurity对象，用于配置安全策略\n\t * @return 配置好的SecurityFilterChain实例\n\t * @throws Exception 配置过程中可能抛出的异常\n\t */\n\t@Bean\n\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\tsuccessHandler.setDefaultTargetUrl(this.adminServer.path(\"/\"));\n\n\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests //\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/assets/**\")))\n\t\t\t.permitAll()\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/info\")))\n\t\t\t.permitAll()\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(adminServer.path(\"/actuator/health\")))\n\t\t\t.permitAll()\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/login\")))\n\t\t\t.permitAll()\n\t\t\t.dispatcherTypeMatchers(DispatcherType.ASYNC)\n\t\t\t.permitAll() // https://github.com/spring-projects/spring-security/issues/11027\n\t\t\t.anyRequest()\n\t\t\t.authenticated())\n\t\t\t.formLogin(\n\t\t\t\t\t(formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\")).successHandler(successHandler))\n\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\")))\n\t\t\t.httpBasic(Customizer.withDefaults());\n\n\t\thttp.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class) // <5>\n\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t.matcher(HttpMethod.POST, this.adminServer.path(\"/instances\")), // <6>\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t.matcher(HttpMethod.DELETE, this.adminServer.path(\"/instances/*\")), // <6>\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\")) // <7>\n\t\t\t\t));\n\n\t\thttp.rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));\n\n\t\treturn http.build();\n\n\t}\n\n\t/**\n\t * 创建内存用户详情服务\n\t * @param passwordEncoder 密码编码器\n\t * @return 包含配置用户的InMemoryUserDetailsManager实例\n\t */\n\t@Bean\n\tpublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\n\t\tUserDetails user = User.withUsername(security.getUser().getName())\n\t\t\t.password(passwordEncoder.encode(security.getUser().getPassword()))\n\t\t\t.roles(\"USER\")\n\t\t\t.build();\n\t\treturn new InMemoryUserDetailsManager(user);\n\t}\n\n\t/**\n\t * 创建并返回一个BCrypt密码编码器实例\n\t * @return BCryptPasswordEncoder实例\n\t */\n\t@Bean\n\tpublic PasswordEncoder passwordEncoder() {\n\t\treturn new BCryptPasswordEncoder();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/converter/NacosServiceInstanceConverter.java",
    "content": "package com.pig4cloud.pig.monitor.converter;\n\nimport de.codecentric.boot.admin.server.cloud.discovery.DefaultServiceInstanceConverter;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static java.util.Collections.emptyMap;\n\n/**\n * Nacos 2.x 服务注册转换器，用于处理服务实例元数据转换\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Configuration(proxyBeanMethods = false)\npublic class NacosServiceInstanceConverter extends DefaultServiceInstanceConverter {\n\n\t/**\n\t * 获取服务实例的元数据\n\t * @param instance 服务实例\n\t * @return 过滤后的元数据映射，不包含空键或空值的条目\n\t */\n\t@Override\n\tprotected Map<String, String> getMetadata(ServiceInstance instance) {\n\t\treturn (instance.getMetadata() != null) ? instance.getMetadata()\n\t\t\t.entrySet()\n\t\t\t.stream()\n\t\t\t.filter((e) -> e.getKey() != null && e.getValue() != null)\n\t\t\t.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) : emptyMap();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-monitor/src/main/resources/application.yml",
    "content": "server:\n  port: 5001\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      username: @nacos.username@\n      password: @nacos.password@\n      discovery:\n        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}\n      config:\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n  config:\n    import:\n      - nacos:application-@profiles.active@.yml\n      - nacos:${spring.application.name}-@profiles.active@.yml\n\n"
  },
  {
    "path": "pig-visual/pig-monitor/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration debug=\"false\" scan=\"false\">\n\t<springProperty scop=\"context\" name=\"spring.application.name\" source=\"spring.application.name\" defaultValue=\"\"/>\n\t<property name=\"log.path\" value=\"logs/${spring.application.name}\"/>\n\t<!-- 彩色日志格式 -->\n\t<property name=\"CONSOLE_LOG_PATTERN\"\n\t\t\t  value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n\t<conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n\t<conversionRule conversionWord=\"wex\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n\t<conversionRule conversionWord=\"wEx\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n\t<!-- Console log output -->\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>${CONSOLE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file debug output -->\n\t<appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/debug.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file error output -->\n\t<appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/error.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t\t<filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n\t\t\t<level>ERROR</level>\n\t\t</filter>\n\t</appender>\n\n\t<!--nacos 心跳 INFO 屏蔽-->\n\t<logger name=\"com.alibaba.nacos\" level=\"OFF\">\n\t\t<appender-ref ref=\"error\"/>\n\t</logger>\n\n\t<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n\t<root level=\"INFO\">\n\t\t<appender-ref ref=\"console\"/>\n\t\t<appender-ref ref=\"debug\"/>\n\t\t<appender-ref ref=\"error\"/>\n\t</root>\n</configuration>\n"
  },
  {
    "path": "pig-visual/pig-quartz/Dockerfile",
    "content": "FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis\n\nWORKDIR /pig-quartz\n\nARG JAR_FILE=target/pig-quartz.jar\n\nCOPY ${JAR_FILE} app.jar\n\nEXPOSE 5007\n\nENV TZ=Asia/Shanghai JAVA_OPTS=\"-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom\"\n\nCMD sleep 60; java $JAVA_OPTS -jar app.jar\n"
  },
  {
    "path": "pig-visual/pig-quartz/SECURITY.md",
    "content": "# Quartz Module Security\n\n## Remote Code Execution (RCE) Vulnerability Fix\n\n### Issue Description\nIn versions 3.8.2 and below, the Quartz management module allowed execution of arbitrary Java classes through reflection without proper validation. This could be exploited to execute malicious code using classes like `jakarta.el.ELProcessor`.\n\n### Attack Vector Example\nAn attacker with access to the Quartz management interface could create a task with:\n- **Class Name**: `jakarta.el.ELProcessor`\n- **Method Name**: `eval`\n- **Parameters**: `Runtime.getRuntime().exec(\"malicious command\")`\n\nThis would result in remote code execution on the server.\n\n### Security Fix Implementation\n\n#### 1. Class Name Validator (`ClassNameValidator.java`)\nA comprehensive validator that maintains blacklists of dangerous classes, packages, and methods:\n\n**Blocked Classes:**\n- `jakarta.el.ELProcessor`, `javax.el.ELProcessor` - Expression Language processors\n- `javax.script.ScriptEngineManager`, `javax.script.ScriptEngine` - Script engines\n- `java.lang.Runtime`, `java.lang.ProcessBuilder` - System command execution\n- `java.lang.reflect.*` - Reflection API\n- `java.lang.ClassLoader`, `java.net.URLClassLoader` - Class loaders\n- `javax.naming.*` - JNDI classes\n- `java.rmi.*` - RMI classes\n- `java.io.ObjectInputStream`, `java.io.ObjectOutputStream` - Serialization\n\n**Blocked Packages:**\n- `sun.*`, `com.sun.*` - Internal JDK classes\n- `jdk.internal.*` - Internal JDK classes\n- `java.lang.reflect.*` - Reflection API\n- `java.security.*` - Security API\n- `javax.naming.*` - JNDI\n- `javax.script.*` - Scripting API\n- `java.rmi.*` - RMI API\n- `javax.management.*` - JMX\n- `org.springframework.context.support.FileSystemXmlApplicationContext` - Spring context manipulation\n- `org.springframework.expression.spel.*` - Spring Expression Language\n\n**Blocked Methods:**\n- `exec`, `eval`, `execute` - Code execution\n- `invoke`, `newInstance` - Reflection\n- `forName`, `loadClass`, `defineClass` - Class loading\n- `getRuntime`, `getMethod`, `getDeclaredMethod` - System access\n\n#### 2. Multi-Layer Defense\n\n**Layer 1: Controller Validation** (`SysJobController.java`)\n- Validates class and method names when creating tasks (POST `/sys-job`)\n- Validates class and method names when updating tasks (PUT `/sys-job`)\n- Returns HTTP 400 error with descriptive message if validation fails\n\n**Layer 2: Execution Validation** (`JavaClassTaskInvok.java`)\n- Validates class and method names before reflection execution\n- Throws `TaskException` if validation fails\n- Prevents execution even if controller validation is bypassed\n\n### Testing\n\nThe fix includes comprehensive test coverage:\n\n**Unit Tests** (`ClassNameValidatorTest.java`)\n- 8 test cases covering validation logic\n- Tests for dangerous classes, packages, methods\n- Tests for valid inputs and edge cases\n\n**Security Tests** (`JavaClassTaskInvokSecurityTest.java`)\n- 7 test cases specifically for RCE attack vectors\n- Tests blocking of:\n  - `jakarta.el.ELProcessor` with `eval` method\n  - `java.lang.Runtime` with `exec` method\n  - `java.lang.ProcessBuilder` \n  - `javax.script.ScriptEngineManager`\n  - `java.lang.ClassLoader`\n  - `javax.naming.InitialContext` (JNDI injection)\n  - Dangerous method names\n\nAll 15 tests pass successfully, confirming the vulnerability is fixed.\n\n### Usage Guidelines\n\n#### For Administrators\n1. **Update immediately** to a version containing this fix\n2. **Review existing tasks** in the Quartz management interface for any suspicious entries\n3. **Audit logs** for any attempts to create tasks with dangerous classes\n4. **Restrict access** to the Quartz management interface to trusted administrators only\n\n#### For Developers\nWhen creating custom task classes:\n1. Use application-specific package names (e.g., `com.yourcompany.tasks.*`)\n2. Avoid using reflection, class loaders, or system commands in task methods\n3. Implement proper input validation in your task classes\n4. Use descriptive method names that don't match dangerous patterns\n\n### Safe Task Example\n\n```java\npackage com.pig4cloud.pig.daemon.tasks;\n\npublic class DataCleanupTask {\n    \n    // Safe method - no dangerous operations\n    public String execute() {\n        // Perform data cleanup logic\n        return \"0\"; // Return \"0\" for success\n    }\n    \n    // Safe method with parameters\n    public String executeWithParams(String params) {\n        // Validate input\n        if (params == null || params.isEmpty()) {\n            return \"1\"; // Return \"1\" for failure\n        }\n        // Perform task logic\n        return \"0\";\n    }\n}\n```\n\n### Configuration in Quartz Management\n\n**Safe Configuration:**\n- **Class Name**: `com.pig4cloud.pig.daemon.tasks.DataCleanupTask`\n- **Method Name**: `execute` or `executeWithParams`\n- **Parameters**: Simple string parameters only\n\n**Blocked Configuration:**\n- **Class Name**: `jakarta.el.ELProcessor` ❌\n- **Method Name**: `eval` ❌\n- **Class Name**: `java.lang.Runtime` ❌\n- **Method Name**: `exec` ❌\n\n### Security Summary\n\n✅ **Fixed**: Remote Code Execution vulnerability through reflection\n✅ **Protection**: Multi-layer validation (controller + execution)\n✅ **Coverage**: Comprehensive blacklist of dangerous classes and methods\n✅ **Tested**: 15 test cases verify the fix\n✅ **Impact**: Prevents arbitrary code execution via Quartz tasks\n\n### References\n\n- CVE: Pending assignment\n- Affected Versions: pig <= 3.8.2\n- Fixed in Version: 3.9.2+\n- Severity: Critical\n"
  },
  {
    "path": "pig-visual/pig-quartz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig-visual</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>pig-quartz</artifactId>\n    <packaging>jar</packaging>\n\n    <description>基于quartz后台定时任务模块</description>\n\n    <dependencies>\n        <!--注册中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n        </dependency>\n        <!--配置中心客户端-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n        </dependency>\n        <!--日志处理-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-log</artifactId>\n        </dependency>\n        <!--feign 处理-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-feign</artifactId>\n        </dependency>\n        <!--mybatis-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-mybatis</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>\n        </dependency>\n        <!--数据库-->\n        <dependency>\n            <groupId>com.mysql</groupId>\n            <artifactId>mysql-connector-j</artifactId>\n        </dependency>\n        <!--swagger-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-swagger</artifactId>\n        </dependency>\n        <!--spring security 、oauth、jwt依赖-->\n        <dependency>\n            <groupId>com.pig4cloud</groupId>\n            <artifactId>pig-common-security</artifactId>\n        </dependency>\n        <!-- quartz 模块 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-quartz</artifactId>\n        </dependency>\n        <!--web 模块-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <!--undertow容器-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-undertow</artifactId>\n        </dependency>\n    </dependencies>\n\n    <profiles>\n        <profile>\n            <id>boot</id>\n        </profile>\n        <profile>\n            <id>cloud</id>\n            <activation>\n                <!-- 默认环境 -->\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-maven-plugin</artifactId>\n                    </plugin>\n                    <plugin>\n                        <groupId>io.fabric8</groupId>\n                        <artifactId>docker-maven-plugin</artifactId>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/PigQuartzApplication.java",
    "content": "package com.pig4cloud.pig.daemon.quartz;\n\nimport com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;\nimport com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;\nimport com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n/**\n * PigQuartz应用启动类\n * <p>\n * 集成定时任务、Feign客户端、资源服务及服务发现功能\n *\n * @author lengleng\n * @author frwcloud\n * @date 2025/05/31\n */\n@EnablePigDoc(\"job\")\n@EnablePigFeignClients\n@EnablePigResourceServer\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class PigQuartzApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(PigQuartzApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/AutowireCapableBeanJobFactory.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.config;\n\nimport org.quartz.JobKey;\nimport org.quartz.spi.TriggerFiredBundle;\nimport org.springframework.beans.factory.config.AutowireCapableBeanFactory;\nimport org.springframework.scheduling.quartz.SpringBeanJobFactory;\nimport org.springframework.util.Assert;\n\n/**\n * 自动装配能力的Bean任务工厂，继承自SpringBeanJobFactory，用于创建并自动装配Job实例\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\nclass AutowireCapableBeanJobFactory extends SpringBeanJobFactory {\n\n\tprivate final AutowireCapableBeanFactory beanFactory;\n\n\tAutowireCapableBeanJobFactory(AutowireCapableBeanFactory beanFactory) {\n\t\tAssert.notNull(beanFactory, \"Bean factory must not be null\");\n\t\tthis.beanFactory = beanFactory;\n\t}\n\n\t/**\n\t * 创建并初始化Job实例\n\t * @param bundle 触发器触发包，包含Job相关信息\n\t * @return 初始化后的Job实例\n\t * @throws Exception 创建或初始化过程中可能抛出的异常\n\t */\n\t@Override\n\tprotected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {\n\t\tObject jobInstance = super.createJobInstance(bundle);\n\t\tthis.beanFactory.autowireBean(jobInstance);\n\n\t\t// 此处必须注入 beanName 不然sentinel 报错\n\t\tJobKey jobKey = bundle.getTrigger().getJobKey();\n\t\tString beanName = jobKey + jobKey.getName();\n\t\tthis.beanFactory.initializeBean(jobInstance, beanName);\n\t\treturn jobInstance;\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigInitQuartzJob.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.config;\n\nimport org.quartz.Scheduler;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobService;\nimport com.pig4cloud.pig.daemon.quartz.util.TaskUtil;\n\nimport lombok.AllArgsConstructor;\n\n/**\n * 初始化加载定时任务配置类\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\n@Configuration\n@AllArgsConstructor\npublic class PigInitQuartzJob implements InitializingBean {\n\n\tprivate final SysJobService sysJobService;\n\n\tprivate final TaskUtil taskUtil;\n\n\tprivate final Scheduler scheduler;\n\n\t/**\n\t * 在属性设置完成后执行，根据任务状态进行相应操作\n\t * @throws Exception 执行过程中可能抛出的异常\n\t */\n\t@Override\n\tpublic void afterPropertiesSet() throws Exception {\n\t\tsysJobService.list().forEach(sysjob -> {\n\t\t\tif (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(sysjob.getJobStatus())) {\n\t\t\t\ttaskUtil.removeJob(sysjob, scheduler);\n\t\t\t}\n\t\t\telse if (PigQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysjob.getJobStatus())) {\n\t\t\t\ttaskUtil.resumeJob(sysjob, scheduler);\n\t\t\t}\n\t\t\telse if (PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysjob.getJobStatus())) {\n\t\t\t\ttaskUtil.pauseJob(sysjob, scheduler);\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttaskUtil.removeJob(sysjob, scheduler);\n\t\t\t}\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzConfig.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.config;\n\nimport com.pig4cloud.pig.common.core.factory.YamlPropertySourceFactory;\nimport org.quartz.Calendar;\nimport org.quartz.JobDetail;\nimport org.quartz.Scheduler;\nimport org.quartz.Trigger;\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.quartz.QuartzProperties;\nimport org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.PropertySource;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.quartz.SchedulerFactoryBean;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\n\n/**\n * Quartz 定时任务配置类\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\n@EnableAsync\n@Configuration\n@PropertySource(value = \"classpath:quartz-config.yml\", factory = YamlPropertySourceFactory.class)\n@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class })\n@EnableConfigurationProperties({ QuartzProperties.class })\npublic class PigQuartzConfig {\n\n\tprivate final QuartzProperties properties;\n\n\tprivate final List<SchedulerFactoryBeanCustomizer> customizers;\n\n\tprivate final JobDetail[] jobDetails;\n\n\tprivate final Map<String, Calendar> calendars;\n\n\tprivate final Trigger[] triggers;\n\n\tprivate final ApplicationContext applicationContext;\n\n\t/**\n\t * 构造函数，初始化PigQuartzConfig配置\n\t * @param properties Quartz配置属性\n\t * @param customizers SchedulerFactoryBean自定义器列表\n\t * @param jobDetails JobDetail数组\n\t * @param calendars 日历Map\n\t * @param triggers 触发器数组\n\t * @param applicationContext Spring应用上下文\n\t */\n\tpublic PigQuartzConfig(QuartzProperties properties,\n\t\t\tObjectProvider<List<SchedulerFactoryBeanCustomizer>> customizers, ObjectProvider<JobDetail[]> jobDetails,\n\t\t\tObjectProvider<Map<String, Calendar>> calendars, ObjectProvider<Trigger[]> triggers,\n\t\t\tApplicationContext applicationContext) {\n\t\tthis.properties = properties;\n\t\tthis.customizers = customizers.getIfAvailable();\n\t\tthis.jobDetails = jobDetails.getIfAvailable();\n\t\tthis.calendars = calendars.getIfAvailable();\n\t\tthis.triggers = triggers.getIfAvailable();\n\t\tthis.applicationContext = applicationContext;\n\t}\n\n\t/**\n\t * 创建并配置Quartz SchedulerFactoryBean\n\t * @return 配置完成的SchedulerFactoryBean实例\n\t */\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic SchedulerFactoryBean quartzScheduler() {\n\t\tSchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();\n\t\tschedulerFactoryBean\n\t\t\t.setJobFactory(new AutowireCapableBeanJobFactory(this.applicationContext.getAutowireCapableBeanFactory()));\n\t\tif (!this.properties.getProperties().isEmpty()) {\n\t\t\tschedulerFactoryBean.setQuartzProperties(this.asProperties(this.properties.getProperties()));\n\t\t}\n\n\t\tif (this.jobDetails != null && this.jobDetails.length > 0) {\n\t\t\tschedulerFactoryBean.setJobDetails(this.jobDetails);\n\t\t}\n\n\t\tif (this.calendars != null && !this.calendars.isEmpty()) {\n\t\t\tschedulerFactoryBean.setCalendars(this.calendars);\n\t\t}\n\n\t\tif (this.triggers != null && this.triggers.length > 0) {\n\t\t\tschedulerFactoryBean.setTriggers(this.triggers);\n\t\t}\n\n\t\tthis.customize(schedulerFactoryBean);\n\t\treturn schedulerFactoryBean;\n\t}\n\n\t/**\n\t * 将Map转换为Properties对象\n\t * @param source 源Map，键值对均为String类型\n\t * @return 转换后的Properties对象\n\t */\n\tprivate Properties asProperties(Map<String, String> source) {\n\t\tProperties properties = new Properties();\n\t\tproperties.putAll(source);\n\t\treturn properties;\n\t}\n\n\t/**\n\t * 自定义SchedulerFactoryBean\n\t * @param schedulerFactoryBean 需要自定义的调度器工厂bean\n\t */\n\tprivate void customize(SchedulerFactoryBean schedulerFactoryBean) {\n\t\tif (this.customizers != null) {\n\n\t\t\tfor (SchedulerFactoryBeanCustomizer customizer : this.customizers) {\n\t\t\t\tcustomizer.customize(schedulerFactoryBean);\n\t\t\t}\n\t\t}\n\n\t}\n\n\t/**\n\t * 通过SchedulerFactoryBean获取Scheduler的实例\n\t * @return\n\t */\n\t@Bean\n\tpublic Scheduler scheduler() {\n\t\treturn quartzScheduler().getScheduler();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzCustomizerConfig.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.config;\n\nimport org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.quartz.SchedulerFactoryBean;\n\n/**\n * PigQuartz 自定义配置类，用于配置 SchedulerFactoryBean\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\n@Configuration\npublic class PigQuartzCustomizerConfig implements SchedulerFactoryBeanCustomizer {\n\n\t/**\n\t * 自定义SchedulerFactoryBean配置\n\t * @param schedulerFactoryBean 调度器工厂bean\n\t */\n\t@Override\n\tpublic void customize(SchedulerFactoryBean schedulerFactoryBean) {\n\t\tschedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzFactory.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.config;\n\nimport org.quartz.DisallowConcurrentExecution;\nimport org.quartz.Job;\nimport org.quartz.JobExecutionContext;\nimport org.springframework.beans.factory.annotation.Autowired;\n\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\n\nimport lombok.SneakyThrows;\n\n/**\n * 动态任务工厂：用于执行动态任务调度\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\n@DisallowConcurrentExecution\npublic class PigQuartzFactory implements Job {\n\n\t/**\n\t * 定时任务调用工厂\n\t */\n\t@Autowired\n\tprivate PigQuartzInvokeFactory pigxQuartzInvokeFactory;\n\n\t/**\n\t * 执行定时任务\n\t * @param jobExecutionContext 任务执行上下文\n\t * @throws Exception 执行过程中可能抛出的异常\n\t */\n\t@Override\n\t@SneakyThrows\n\tpublic void execute(JobExecutionContext jobExecutionContext) {\n\t\tSysJob sysJob = (SysJob) jobExecutionContext.getMergedJobDataMap()\n\t\t\t.get(PigQuartzEnum.SCHEDULE_JOB_KEY.getType());\n\t\tpigxQuartzInvokeFactory.init(sysJob, jobExecutionContext.getTrigger());\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzInvokeFactory.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.config;\n\nimport org.aspectj.lang.annotation.Aspect;\nimport org.quartz.Trigger;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.stereotype.Service;\n\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.event.SysJobEvent;\n\nimport lombok.AllArgsConstructor;\nimport lombok.SneakyThrows;\n\n/**\n * 定时任务调用工厂类 用于初始化并发布定时任务事件\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Aspect\n@Service\n@AllArgsConstructor\npublic class PigQuartzInvokeFactory {\n\n\tprivate final ApplicationEventPublisher publisher;\n\n\t/**\n\t * 初始化并发布定时任务事件\n\t * @param sysJob 系统任务对象\n\t * @param trigger 任务触发器\n\t */\n\t@SneakyThrows\n\tvoid init(SysJob sysJob, Trigger trigger) {\n\t\tpublisher.publishEvent(new SysJobEvent(sysJob, trigger));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/constants/JobTypeQuartzEnum.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 任务类型枚举\n *\n * @author lengleng\n * @date 2019/03/14\n */\n@Getter\n@AllArgsConstructor\npublic enum JobTypeQuartzEnum {\n\n\t/**\n\t * 反射java类\n\t */\n\tJAVA(\"1\", \"反射java类\"),\n\n\t/**\n\t * spring bean 的方式\n\t */\n\tSPRING_BEAN(\"2\", \"spring bean容器实例\"),\n\n\t/**\n\t * rest 调用\n\t */\n\tREST(\"3\", \"rest调用\"),\n\n\t/**\n\t * jar\n\t */\n\tJAR(\"4\", \"jar调用\");\n\n\t/**\n\t * 类型\n\t */\n\tprivate final String type;\n\n\t/**\n\t * 描述\n\t */\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/constants/PigQuartzEnum.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 定时任务枚举类\n * <p>\n * 定义定时任务相关的枚举常量，包括错失执行策略、任务状态等\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\n@Getter\n@AllArgsConstructor\npublic enum PigQuartzEnum {\n\n\t/**\n\t * 错失执行策略默认\n\t */\n\tMISFIRE_DEFAULT(\"0\", \"默认\"),\n\n\t/**\n\t * 错失执行策略-立即执行错失任务\n\t */\n\tMISFIRE_IGNORE_MISFIRES(\"1\", \"立即执行错失任务\"),\n\n\t/**\n\t * 错失执行策略-触发一次执行周期执行\n\t */\n\tMISFIRE_FIRE_AND_PROCEED(\"2\", \"触发一次执行周期执行\"),\n\n\t/**\n\t * 错失执行策略-不触发执行周期执行\n\t */\n\tMISFIRE_DO_NOTHING(\"3\", \"不触发周期执行\"),\n\n\t/**\n\t * 任务详细信息的key\n\t */\n\tSCHEDULE_JOB_KEY(\"scheduleJob\", \"获取任务详细信息的key\"),\n\n\t/**\n\t * JOB执行状态：0执行成功\n\t */\n\tJOB_LOG_STATUS_SUCCESS(\"0\", \"执行成功\"),\n\t/**\n\t * JOB执行状态：1执行失败\n\t */\n\tJOB_LOG_STATUS_FAIL(\"1\", \"执行失败\"),\n\n\t/**\n\t * JOB状态：1已发布\n\t */\n\tJOB_STATUS_RELEASE(\"1\", \"已发布\"),\n\t/**\n\t * JOB状态：2运行中\n\t */\n\tJOB_STATUS_RUNNING(\"2\", \"运行中\"),\n\t/**\n\t * JOB状态：3暂停\n\t */\n\tJOB_STATUS_NOT_RUNNING(\"3\", \"暂停\"),\n\t/**\n\t * JOB状态：4删除\n\t */\n\tJOB_STATUS_DEL(\"4\", \"删除\");\n\n\t/**\n\t * 类型\n\t */\n\tprivate final String type;\n\n\t/**\n\t * 描述\n\t */\n\tprivate final String description;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/controller/SysJobController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.controller;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.log.annotation.SysLog;\nimport com.pig4cloud.pig.common.security.annotation.HasPermission;\nimport com.pig4cloud.pig.common.security.util.SecurityUtils;\nimport com.pig4cloud.pig.daemon.quartz.constants.JobTypeQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobLogService;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobService;\nimport com.pig4cloud.pig.daemon.quartz.util.ClassNameValidator;\nimport com.pig4cloud.pig.daemon.quartz.util.TaskUtil;\nimport com.pig4cloud.plugin.excel.annotation.ResponseExcel;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.Scheduler;\nimport org.quartz.SchedulerException;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * 定时任务管理控制器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/sys-job\")\n@Tag(description = \"sys-job\", name = \"定时任务管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysJobController {\n\n\tprivate final SysJobService sysJobService;\n\n\tprivate final SysJobLogService sysJobLogService;\n\n\tprivate final TaskUtil taskUtil;\n\n\tprivate final Scheduler scheduler;\n\n\t/**\n\t * 定时任务分页查询\n\t * @param page 分页对象\n\t * @param sysJob 定时任务调度表\n\t * @return R\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页定时业务查询\", description = \"分页定时业务查询\")\n\tpublic R getJobPage(Page page, SysJob sysJob) {\n\t\tLambdaQueryWrapper<SysJob> wrapper = Wrappers.<SysJob>lambdaQuery()\n\t\t\t.like(StrUtil.isNotBlank(sysJob.getJobName()), SysJob::getJobName, sysJob.getJobName())\n\t\t\t.like(StrUtil.isNotBlank(sysJob.getJobGroup()), SysJob::getJobGroup, sysJob.getJobGroup())\n\t\t\t.eq(StrUtil.isNotBlank(sysJob.getJobStatus()), SysJob::getJobStatus, sysJob.getJobGroup())\n\t\t\t.eq(StrUtil.isNotBlank(sysJob.getJobExecuteStatus()), SysJob::getJobExecuteStatus,\n\t\t\t\t\tsysJob.getJobExecuteStatus());\n\t\treturn R.ok(sysJobService.page(page, wrapper));\n\t}\n\n\t/**\n\t * 通过id查询定时任务\n\t * @param id id\n\t * @return R\n\t */\n\t@GetMapping(\"/{id}\")\n\t@Operation(summary = \"唯一标识查询定时任务\", description = \"唯一标识查询定时任务\")\n\tpublic R getById(@PathVariable(\"id\") Long id) {\n\t\treturn R.ok(sysJobService.getById(id));\n\t}\n\n\t/**\n\t * 新增定时任务,默认新增状态为1已发布\n\t * @param sysJob 定时任务调度表\n\t * @return R\n\t */\n\t@SysLog(\"新增定时任务\")\n\t@PostMapping\n\t@HasPermission(\"job_sys_job_add\")\n\t@Operation(summary = \"新增定时任务\", description = \"新增定时任务\")\n\tpublic R saveJob(@RequestBody SysJob sysJob) {\n\t\tlong count = sysJobService.count(\n\t\t\t\tWrappers.query(SysJob.builder().jobName(sysJob.getJobName()).jobGroup(sysJob.getJobGroup()).build()));\n\n\t\tif (count > 0) {\n\t\t\treturn R.failed(\"任务重复，请检查此组内是否已包含同名任务\");\n\t\t}\n\n\t\t// 安全验证：对于Java类类型的任务，验证类名和方法名\n\t\tif (JobTypeQuartzEnum.JAVA.getType().equals(sysJob.getJobType())) {\n\t\t\tif (!ClassNameValidator.isValidClassName(sysJob.getClassName())) {\n\t\t\t\tlog.warn(\"新增定时任务失败，类名验证不通过：{}\", sysJob.getClassName());\n\t\t\t\treturn R.failed(\"类名验证失败，该类在黑名单中或包含危险特征，拒绝创建\");\n\t\t\t}\n\t\t\tif (!ClassNameValidator.isValidMethodName(sysJob.getMethodName())) {\n\t\t\t\tlog.warn(\"新增定时任务失败，方法名验证不通过：{}\", sysJob.getMethodName());\n\t\t\t\treturn R.failed(\"方法名验证失败，该方法在黑名单中或包含危险特征，拒绝创建\");\n\t\t\t}\n\t\t}\n\n\t\tsysJob.setJobStatus(PigQuartzEnum.JOB_STATUS_RELEASE.getType());\n\t\tsysJob.setCreateBy(SecurityUtils.getUser().getUsername());\n\t\treturn R.ok(sysJobService.save(sysJob));\n\t}\n\n\t/**\n\t * 修改定时任务\n\t * @param sysJob 定时任务调度表\n\t * @return R\n\t */\n\t@SysLog(\"修改定时任务\")\n\t@PutMapping\n\t@HasPermission(\"job_sys_job_edit\")\n\t@Operation(summary = \"修改定时任务\", description = \"修改定时任务\")\n\tpublic R updateJob(@RequestBody SysJob sysJob) {\n\t\t// 安全验证：对于Java类类型的任务，验证类名和方法名\n\t\tif (JobTypeQuartzEnum.JAVA.getType().equals(sysJob.getJobType())) {\n\t\t\tif (!ClassNameValidator.isValidClassName(sysJob.getClassName())) {\n\t\t\t\tlog.warn(\"修改定时任务失败，类名验证不通过：{}\", sysJob.getClassName());\n\t\t\t\treturn R.failed(\"类名验证失败，该类在黑名单中或包含危险特征，拒绝修改\");\n\t\t\t}\n\t\t\tif (!ClassNameValidator.isValidMethodName(sysJob.getMethodName())) {\n\t\t\t\tlog.warn(\"修改定时任务失败，方法名验证不通过：{}\", sysJob.getMethodName());\n\t\t\t\treturn R.failed(\"方法名验证失败，该方法在黑名单中或包含危险特征，拒绝修改\");\n\t\t\t}\n\t\t}\n\n\t\tsysJob.setUpdateBy(SecurityUtils.getUser().getUsername());\n\t\tSysJob querySysJob = this.sysJobService.getById(sysJob.getJobId());\n\t\tif (PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(querySysJob.getJobStatus())) {\n\t\t\t// 如修改暂停的需更新调度器\n\t\t\tthis.taskUtil.addOrUpateJob(sysJob, scheduler);\n\t\t\tsysJobService.updateById(sysJob);\n\t\t}\n\t\telse if (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) {\n\t\t\tsysJobService.updateById(sysJob);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 通过id删除定时任务\n\t * @param id 定时任务唯一标识\n\t * @return 操作结果\n\t * @throws IllegalArgumentException 当任务未暂停时尝试删除会抛出异常\n\t */\n\t@SysLog(\"删除定时任务\")\n\t@DeleteMapping(\"/{id}\")\n\t@HasPermission(\"job_sys_job_del\")\n\t@Operation(summary = \"唯一标识查询定时任务，暂停任务才能删除\", description = \"唯一标识查询定时任务，暂停任务才能删除\")\n\tpublic R removeById(@PathVariable Long id) {\n\t\tSysJob querySysJob = this.sysJobService.getById(id);\n\t\tif (PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(querySysJob.getJobStatus())) {\n\t\t\tthis.taskUtil.removeJob(querySysJob, scheduler);\n\t\t\tthis.sysJobService.removeById(id);\n\t\t}\n\t\telse if (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) {\n\t\t\tthis.sysJobService.removeById(id);\n\t\t}\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 暂停全部定时任务\n\t * @return R\n\t */\n\t@SysLog(\"暂停全部定时任务\")\n\t@PostMapping(\"/shutdown-jobs\")\n\t@HasPermission(\"job_sys_job_shutdown_job\")\n\t@Operation(summary = \"暂停全部定时任务\", description = \"暂停全部定时任务\")\n\tpublic R shutdownJobs() {\n\t\ttaskUtil.pauseJobs(scheduler);\n\t\tlong count = this.sysJobService.count(\n\t\t\t\tnew LambdaQueryWrapper<SysJob>().eq(SysJob::getJobStatus, PigQuartzEnum.JOB_STATUS_RUNNING.getType()));\n\t\tif (count <= 0) {\n\t\t\treturn R.ok(\"无正在运行定时任务\");\n\t\t}\n\t\telse {\n\t\t\t// 更新定时任务状态条件，运行状态2更新为暂停状态3\n\t\t\tthis.sysJobService.update(\n\t\t\t\t\tSysJob.builder().jobStatus(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()).build(),\n\t\t\t\t\tnew UpdateWrapper<SysJob>().lambda()\n\t\t\t\t\t\t.eq(SysJob::getJobStatus, PigQuartzEnum.JOB_STATUS_RUNNING.getType()));\n\t\t\treturn R.ok(\"暂停成功\");\n\t\t}\n\t}\n\n\t/**\n\t * 启动全部定时任务\n\t * @return\n\t */\n\t@SysLog(\"启动全部暂停的定时任务\")\n\t@PostMapping(\"/start-jobs\")\n\t@HasPermission(\"job_sys_job_start_job\")\n\t@Operation(summary = \"启动全部暂停的定时任务\", description = \"启动全部暂停的定时任务\")\n\tpublic R startJobs() {\n\t\t// 更新定时任务状态条件，暂停状态3更新为运行状态2\n\t\tthis.sysJobService.update(SysJob.builder().jobStatus(PigQuartzEnum.JOB_STATUS_RUNNING.getType()).build(),\n\t\t\t\tnew UpdateWrapper<SysJob>().lambda()\n\t\t\t\t\t.eq(SysJob::getJobStatus, PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()));\n\t\ttaskUtil.startJobs(scheduler);\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 刷新全部定时任务 暂停和运行的添加到调度器其他状态从调度器移除\n\t * @return R\n\t */\n\t@SysLog(\"刷新全部定时任务\")\n\t@PostMapping(\"/refresh-jobs\")\n\t@HasPermission(\"job_sys_job_refresh_job\")\n\t@Operation(summary = \"刷新全部定时任务\", description = \"刷新全部定时任务\")\n\tpublic R refreshJobs() {\n\t\tsysJobService.list().forEach(sysjob -> {\n\t\t\tif (PigQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysjob.getJobStatus())\n\t\t\t\t\t|| PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysjob.getJobStatus())) {\n\t\t\t\ttaskUtil.addOrUpateJob(sysjob, scheduler);\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttaskUtil.removeJob(sysjob, scheduler);\n\t\t\t}\n\t\t});\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 启动定时任务\n\t * @param jobId 任务id\n\t * @return R\n\t */\n\t@SysLog(\"启动定时任务\")\n\t@PostMapping(\"/start-job/{id}\")\n\t@HasPermission(\"job_sys_job_start_job\")\n\t@Operation(summary = \"启动定时任务\", description = \"启动定时任务\")\n\tpublic R startJob(@PathVariable(\"id\") Long jobId) throws SchedulerException {\n\t\tSysJob querySysJob = this.sysJobService.getById(jobId);\n\t\tif (querySysJob == null) {\n\t\t\treturn R.failed(\"无此定时任务,请确认\");\n\t\t}\n\n\t\t// 如果定时任务不存在，强制状态为1已发布\n\t\tif (!scheduler.checkExists(TaskUtil.getJobKey(querySysJob))) {\n\t\t\tquerySysJob.setJobStatus(PigQuartzEnum.JOB_STATUS_RELEASE.getType());\n\t\t\tlog.warn(\"定时任务不在quartz中,任务id:{},强制状态为已发布并加入调度器\", jobId);\n\t\t}\n\n\t\tif (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) {\n\t\t\ttaskUtil.addOrUpateJob(querySysJob, scheduler);\n\t\t}\n\t\telse {\n\t\t\ttaskUtil.resumeJob(querySysJob, scheduler);\n\t\t}\n\t\t// 更新定时任务状态为运行状态2\n\t\tthis.sysJobService\n\t\t\t.updateById(SysJob.builder().jobId(jobId).jobStatus(PigQuartzEnum.JOB_STATUS_RUNNING.getType()).build());\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 启动定时任务\n\t * @param jobId 任务id\n\t * @return R\n\t */\n\t@SysLog(\"立刻执行定时任务\")\n\t@PostMapping(\"/run-job/{id}\")\n\t@HasPermission(\"job_sys_job_run_job\")\n\t@Operation(summary = \"立刻执行定时任务\", description = \"立刻执行定时任务\")\n\tpublic R runJob(@PathVariable(\"id\") Long jobId) throws SchedulerException {\n\t\tSysJob querySysJob = this.sysJobService.getById(jobId);\n\n\t\t// 执行定时任务前判定任务是否在quartz中\n\t\tif (!scheduler.checkExists(TaskUtil.getJobKey(querySysJob))) {\n\t\t\tquerySysJob.setJobStatus(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType());\n\t\t\tlog.warn(\"立刻执行定时任务-定时任务不在quartz中,任务id:{},强制状态为暂停并加入调度器\", jobId);\n\t\t\ttaskUtil.addOrUpateJob(querySysJob, scheduler);\n\t\t}\n\n\t\treturn TaskUtil.runOnce(scheduler, querySysJob) ? R.ok() : R.failed();\n\t}\n\n\t/**\n\t * 暂停定时任务\n\t * @return\n\t */\n\t@SysLog(\"暂停定时任务\")\n\t@PostMapping(\"/shutdown-job/{id}\")\n\t@HasPermission(\"job_sys_job_shutdown_job\")\n\t@Operation(summary = \"暂停定时任务\", description = \"暂停定时任务\")\n\tpublic R shutdownJob(@PathVariable(\"id\") Long id) {\n\t\tSysJob querySysJob = this.sysJobService.getById(id);\n\t\t// 更新定时任务状态条件，运行状态2更新为暂停状态3\n\t\tthis.sysJobService.updateById(SysJob.builder()\n\t\t\t.jobId(querySysJob.getJobId())\n\t\t\t.jobStatus(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType())\n\t\t\t.build());\n\t\ttaskUtil.pauseJob(querySysJob, scheduler);\n\t\treturn R.ok();\n\t}\n\n\t/**\n\t * 分页查询定时执行日志\n\t * @param page 分页参数\n\t * @param sysJobLog 查询条件\n\t * @return 分页结果\n\t */\n\t@GetMapping(\"/job-log\")\n\t@Operation(summary = \"唯一标识查询定时执行日志\", description = \"唯一标识查询定时执行日志\")\n\tpublic R getJobLogPage(Page page, SysJobLog sysJobLog) {\n\t\treturn R.ok(sysJobLogService.page(page, Wrappers.query(sysJobLog)));\n\t}\n\n\t/**\n\t * 校验任务名称和任务组组合是否唯一\n\t * @param jobName 任务名称\n\t * @param jobGroup 任务组\n\t * @return 校验结果，若已存在返回失败信息，否则返回成功\n\t */\n\t@GetMapping(\"/is-valid-task-name\")\n\t@Operation(summary = \"检验任务名称和任务组联合是否唯一\", description = \"检验任务名称和任务组联合是否唯一\")\n\tpublic R isValidTaskName(@RequestParam String jobName, @RequestParam String jobGroup) {\n\t\treturn this.sysJobService\n\t\t\t.count(Wrappers.query(SysJob.builder().jobName(jobName).jobGroup(jobGroup).build())) > 0\n\t\t\t\t\t? R.failed(\"任务重复，请检查此组内是否已包含同名任务\") : R.ok();\n\t}\n\n\t/**\n\t * 导出任务数据\n\t * @param sysJob 查询条件对象\n\t * @return 符合条件的任务列表\n\t */\n\t@ResponseExcel\n\t@GetMapping(\"/export\")\n\t@Operation(summary = \"导出任务\", description = \"导出任务\")\n\tpublic List<SysJob> exportJobs(SysJob sysJob) {\n\t\treturn sysJobService.list(Wrappers.query(sysJob));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/controller/SysJobLogController.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.controller;\n\nimport cn.hutool.core.collection.CollUtil;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.security.SecurityRequirement;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.AllArgsConstructor;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * 定时任务日志控制器\n *\n * @author frwcloud\n * @author lengleng\n * @date 2025/05/31\n */\n@RestController\n@AllArgsConstructor\n@RequestMapping(\"/sys-job-log\")\n@Tag(description = \"sys-job-log\", name = \"定时任务日志管理模块\")\n@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)\npublic class SysJobLogController {\n\n\tprivate final SysJobLogService sysJobLogService;\n\n\t/**\n\t * 分页查询定时任务日志\n\t * @param page 分页对象\n\t * @param sysJobLog 查询条件对象\n\t * @return 分页查询结果\n\t */\n\t@GetMapping(\"/page\")\n\t@Operation(summary = \"分页定时任务日志查询\", description = \"分页定时任务日志查询\")\n\tpublic R getJobLogPage(Page page, SysJobLog sysJobLog) {\n\t\treturn R.ok(sysJobLogService.page(page, Wrappers.query(sysJobLog)));\n\t}\n\n\t/**\n\t * 批量删除日志\n\t * @param ids 要删除的日志ID数组\n\t * @return 操作结果\n\t */\n\t@DeleteMapping\n\t@Operation(summary = \"批量删除日志\", description = \"批量删除日志\")\n\tpublic R removeBatchByIds(@RequestBody Long[] ids) {\n\t\treturn R.ok(sysJobLogService.removeBatchByIds(CollUtil.toList(ids)));\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/entity/SysJob.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.entity;\n\nimport java.io.Serial;\nimport java.time.LocalDateTime;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\n\n/**\n * 定时任务调度表\n *\n * @author frwcloud\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@Schema(description = \"定时任务\")\n@EqualsAndHashCode(callSuper = false)\npublic class SysJob extends Model<SysJob> {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 任务id\n\t */\n\t@TableId(value = \"job_id\", type = IdType.ASSIGN_ID)\n\tprivate Long jobId;\n\n\t/**\n\t * 任务名称\n\t */\n\tprivate String jobName;\n\n\t/**\n\t * 任务组名\n\t */\n\tprivate String jobGroup;\n\n\t/**\n\t * 组内执行顺利，值越大执行优先级越高，最大值9，最小值1\n\t */\n\tprivate String jobOrder;\n\n\t/**\n\t * 1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他\n\t */\n\tprivate String jobType;\n\n\t/**\n\t * job_type=3时，rest调用地址，仅支持rest get协议,需要增加String返回值，0成功，1失败;job_type=4时，jar路径;其它值为空\n\t */\n\tprivate String executePath;\n\n\t/**\n\t * job_type=1时，类完整路径;job_type=2时，spring bean名称;其它值为空\n\t */\n\tprivate String className;\n\n\t/**\n\t * 任务方法\n\t */\n\tprivate String methodName;\n\n\t/**\n\t * 参数值\n\t */\n\tprivate String methodParamsValue;\n\n\t/**\n\t * cron执行表达式\n\t */\n\tprivate String cronExpression;\n\n\t/**\n\t * 错失执行策略（1错失周期立即执行 2错失周期执行一次 3下周期执行）\n\t */\n\tprivate String misfirePolicy;\n\n\t/**\n\t * 1、多租户任务;2、非多租户任务\n\t */\n\tprivate String jobTenantType;\n\n\t/**\n\t * 状态（0、未发布;1、已发布;2、运行中;3、暂停;4、删除;）\n\t */\n\tprivate String jobStatus;\n\n\t/**\n\t * 状态（0正常 1异常）\n\t */\n\tprivate String jobExecuteStatus;\n\n\t/**\n\t * 创建者\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate String createBy;\n\n\t/**\n\t * 更新者\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate String updateBy;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n\t/**\n\t * 修改时间\n\t */\n\t@TableField(fill = FieldFill.UPDATE)\n\tprivate LocalDateTime updateTime;\n\n\t/**\n\t * 首次执行时间\n\t */\n\tprivate LocalDateTime startTime;\n\n\t/**\n\t * 上次执行时间\n\t */\n\tprivate LocalDateTime previousTime;\n\n\t/**\n\t * 下次执行时间\n\t */\n\tprivate LocalDateTime nextTime;\n\n\t/**\n\t * 备注信息\n\t */\n\tprivate String remark;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/entity/SysJobLog.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.entity;\n\nimport java.io.Serial;\nimport java.time.LocalDateTime;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\n\n/**\n * 定时任务执行日志表\n *\n * @author frwcloud\n * @date 2019-01-27 13:40:20\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = false)\n@Schema(description = \"定时任务日志\")\npublic class SysJobLog extends Model<SysJobLog> {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 任务日志ID\n\t */\n\t@TableId(value = \"job_log_id\", type = IdType.ASSIGN_ID)\n\tprivate Long jobLogId;\n\n\t/**\n\t * 任务id\n\t */\n\tprivate Long jobId;\n\n\t/**\n\t * 任务名称\n\t */\n\tprivate String jobName;\n\n\t/**\n\t * 任务组名\n\t */\n\tprivate String jobGroup;\n\n\t/**\n\t * 组内执行顺利，值越大执行优先级越高，最大值9，最小值1\n\t */\n\tprivate String jobOrder;\n\n\t/**\n\t * 1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他\n\t */\n\tprivate String jobType;\n\n\t/**\n\t * job_type=3时，rest调用地址，仅支持post协议;job_type=4时，jar路径;其它值为空\n\t */\n\tprivate String executePath;\n\n\t/**\n\t * job_type=1时，类完整路径;job_type=2时，spring bean名称;其它值为空\n\t */\n\tprivate String className;\n\n\t/**\n\t * 任务方法\n\t */\n\tprivate String methodName;\n\n\t/**\n\t * 参数值\n\t */\n\tprivate String methodParamsValue;\n\n\t/**\n\t * cron执行表达式\n\t */\n\tprivate String cronExpression;\n\n\t/**\n\t * 日志信息\n\t */\n\tprivate String jobMessage;\n\n\t/**\n\t * 执行状态（0正常 1失败）\n\t */\n\tprivate String jobLogStatus;\n\n\t/**\n\t * 执行时间\n\t */\n\tprivate String executeTime;\n\n\t/**\n\t * 异常信息\n\t */\n\tprivate String exceptionInfo;\n\n\t/**\n\t * 创建时间\n\t */\n\t@TableField(fill = FieldFill.INSERT)\n\tprivate LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobEvent.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.event;\n\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\nimport org.quartz.Trigger;\n\n/**\n * 系统任务事件类，用于封装定时任务及其触发器\n *\n * @author frwcloud\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\n@AllArgsConstructor\npublic class SysJobEvent {\n\n\tprivate final SysJob sysJob;\n\n\tprivate final Trigger trigger;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobListener.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.event;\n\nimport org.quartz.Trigger;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.util.TaskInvokUtil;\n\nimport lombok.RequiredArgsConstructor;\n\n/**\n * 系统任务监听器：用于异步监听并处理定时任务事件\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@RequiredArgsConstructor\npublic class SysJobListener {\n\n\tprivate final TaskInvokUtil taskInvokUtil;\n\n\t@Async\n\t@Order\n\t@EventListener(SysJobEvent.class)\n\tpublic void comSysJob(SysJobEvent event) {\n\t\tSysJob sysJob = event.getSysJob();\n\t\tTrigger trigger = event.getTrigger();\n\t\ttaskInvokUtil.invokMethod(sysJob, trigger);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobLogEvent.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.event;\n\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 定时任务日志多线程事件\n *\n * @author frwcloud\n * @author lengleng\n * @date 2025/05/31\n */\n@Getter\n@AllArgsConstructor\npublic class SysJobLogEvent {\n\n\tprivate final SysJobLog sysJobLog;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobLogListener.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.event;\n\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobLogService;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\n/**\n * 系统任务日志监听器：用于异步监听并处理定时任务日志事件\n *\n * @author frwcloud\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class SysJobLogListener {\n\n\tprivate final SysJobLogService sysJobLogService;\n\n\t/**\n\t * 异步保存系统任务日志\n\t * @param event 系统任务日志事件\n\t */\n\t@Async\n\t@Order\n\t@EventListener(SysJobLogEvent.class)\n\tpublic void saveSysJobLog(SysJobLogEvent event) {\n\t\tSysJobLog sysJobLog = event.getSysJobLog();\n\t\tsysJobLogService.save(sysJobLog);\n\t\tlog.info(\"执行定时任务日志\");\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/exception/TaskException.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.exception;\n\nimport java.io.Serial;\n\n/**\n * 定时任务异常类\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic class TaskException extends Exception {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * 无参构造方法，创建一个TaskException实例\n\t */\n\tpublic TaskException() {\n\t\tsuper();\n\t}\n\n\t/**\n\t * 构造方法，使用指定消息创建TaskException实例\n\t * @param msg 异常信息\n\t */\n\tpublic TaskException(String msg) {\n\t\tsuper(msg);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/mapper/SysJobLogMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 定时任务执行日志表 Mapper 接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface SysJobLogMapper extends BaseMapper<SysJobLog> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/mapper/SysJobMapper.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 定时任务调度表 Mapper 接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Mapper\npublic interface SysJobMapper extends BaseMapper<SysJob> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/SysJobLogService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\n\n/**\n * 定时任务执行日志服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface SysJobLogService extends IService<SysJobLog> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/SysJobService.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\n\n/**\n * 定时任务调度服务接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface SysJobService extends IService<SysJob> {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/impl/SysJobLogServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.service.impl;\n\nimport org.springframework.stereotype.Service;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport com.pig4cloud.pig.daemon.quartz.mapper.SysJobLogMapper;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobLogService;\n\nimport lombok.AllArgsConstructor;\n\n/**\n * 定时任务执行日志服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@AllArgsConstructor\npublic class SysJobLogServiceImpl extends ServiceImpl<SysJobLogMapper, SysJobLog> implements SysJobLogService {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/impl/SysJobServiceImpl.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.service.impl;\n\nimport org.springframework.stereotype.Service;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.mapper.SysJobMapper;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobService;\n\nimport lombok.AllArgsConstructor;\n\n/**\n * 定时任务调度服务实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Service\n@AllArgsConstructor\npublic class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implements SysJobService {\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/task/RestTaskDemo.java",
    "content": "package com.pig4cloud.pig.daemon.quartz.task;\n\nimport com.pig4cloud.pig.common.core.util.R;\nimport com.pig4cloud.pig.common.security.annotation.Inner;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.time.LocalDateTime;\n\n/**\n * 用于测试REST风格调用的演示类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@RestController\n@RequestMapping(\"/inner-job\")\n@Tag(description = \"REST风格\", name = \"REST风格调用管理模块\")\npublic class RestTaskDemo {\n\n\t/**\n\t * REST风格调用定时任务的演示方法\n\t * @param param 路径参数\n\t * @return 统一响应结果\n\t */\n\t@Inner(value = false)\n\t@GetMapping(\"/{param}\")\n\t@Operation(summary = \"REST风格调用定时任务的演示方法\", description = \"REST风格调用定时任务的演示方法\")\n\tpublic R demoMethod(@PathVariable(\"param\") String param) {\n\t\tlog.info(\"测试于:{}，传入参数{}\", LocalDateTime.now(), param);\n\t\treturn R.ok();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/task/SpringBeanTaskDemo.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.task;\n\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport java.time.LocalDateTime;\n\n/**\n * Spring Bean任务演示类\n *\n * @author lengleng\n * @author 郑健楠\n * @date 2025/05/31\n */\n@Slf4j\n@Component(\"demo\")\npublic class SpringBeanTaskDemo {\n\n\t/**\n\t * 演示方法，用于测试Spring Bean\n\t * @param para 输入参数\n\t * @return 返回任务日志状态成功类型\n\t * @throws Exception 可能抛出的异常\n\t */\n\t@SneakyThrows\n\tpublic String demoMethod(String para) {\n\t\tlog.info(\"测试于:{}，输入参数{}\", LocalDateTime.now(), para);\n\t\treturn PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getType();\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/ClassNameValidator.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * 类名验证器，用于防止反射执行恶意类\n *\n * @author lengleng\n * @date 2025/10/22\n */\n@Slf4j\npublic class ClassNameValidator {\n\n\t/**\n\t * 危险类的黑名单 - 禁止使用的类和包\n\t */\n\tprivate static final Set<String> DANGEROUS_CLASSES = new HashSet<>(Arrays.asList(\n\t\t\t// EL表达式处理器 - 可执行任意代码\n\t\t\t\"jakarta.el.ELProcessor\", \"javax.el.ELProcessor\",\n\t\t\t// 脚本引擎 - 可执行任意脚本\n\t\t\t\"javax.script.ScriptEngineManager\", \"javax.script.ScriptEngine\",\n\t\t\t// 反射相关 - 可绕过安全限制\n\t\t\t\"java.lang.reflect.Method\", \"java.lang.reflect.Constructor\", \"java.lang.reflect.Field\",\n\t\t\t// 类加载器 - 可加载恶意类\n\t\t\t\"java.lang.ClassLoader\", \"java.net.URLClassLoader\",\n\t\t\t// 运行时 - 可执行系统命令\n\t\t\t\"java.lang.Runtime\", \"java.lang.ProcessBuilder\", \"java.lang.Process\",\n\t\t\t// JNDI - 可进行远程代码执行\n\t\t\t\"javax.naming.InitialContext\", \"javax.naming.Context\",\n\t\t\t// RMI - 可进行远程代码执行\n\t\t\t\"java.rmi.registry.Registry\", \"java.rmi.server.UnicastRemoteObject\",\n\t\t\t// 序列化 - 可导致反序列化漏洞\n\t\t\t\"java.io.ObjectInputStream\", \"java.io.ObjectOutputStream\"));\n\n\t/**\n\t * 危险包前缀黑名单\n\t */\n\tprivate static final Set<String> DANGEROUS_PACKAGES = new HashSet<>(Arrays.asList(\"sun.\", \"com.sun.\",\n\t\t\t\"jdk.internal.\", \"java.lang.reflect.\", \"java.security.\", \"javax.naming.\", \"javax.script.\", \"java.rmi.\",\n\t\t\t\"javax.management.\", \"org.springframework.context.support.FileSystemXmlApplicationContext\",\n\t\t\t\"org.springframework.expression.spel.\"));\n\n\t/**\n\t * 验证类名是否安全\n\t * @param className 要验证的类名\n\t * @return 如果类名安全返回true，否则返回false\n\t */\n\tpublic static boolean isValidClassName(String className) {\n\t\tif (StrUtil.isBlank(className)) {\n\t\t\tlog.warn(\"类名为空，验证失败\");\n\t\t\treturn false;\n\t\t}\n\n\t\t// 检查是否在危险类黑名单中\n\t\tif (DANGEROUS_CLASSES.contains(className)) {\n\t\t\tlog.warn(\"类名 [{}] 在危险类黑名单中，拒绝执行\", className);\n\t\t\treturn false;\n\t\t}\n\n\t\t// 检查是否以危险包前缀开头\n\t\tfor (String dangerousPackage : DANGEROUS_PACKAGES) {\n\t\t\tif (className.startsWith(dangerousPackage)) {\n\t\t\t\tlog.warn(\"类名 [{}] 以危险包前缀 [{}] 开头，拒绝执行\", className, dangerousPackage);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// 检查是否包含危险特征\n\t\tif (className.toLowerCase().contains(\"runtime\") || className.toLowerCase().contains(\"processbuilder\")\n\t\t\t\t|| className.toLowerCase().contains(\"elprocessor\") || className.toLowerCase().contains(\"scriptengine\")\n\t\t\t\t|| className.toLowerCase().contains(\"classloader\")) {\n\t\t\tlog.warn(\"类名 [{}] 包含危险特征，拒绝执行\", className);\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * 验证方法名是否安全\n\t * @param methodName 要验证的方法名\n\t * @return 如果方法名安全返回true，否则返回false\n\t */\n\tpublic static boolean isValidMethodName(String methodName) {\n\t\tif (StrUtil.isBlank(methodName)) {\n\t\t\tlog.warn(\"方法名为空，验证失败\");\n\t\t\treturn false;\n\t\t}\n\n\t\t// 检查方法名是否包含危险特征\n\t\tSet<String> dangerousMethods = new HashSet<>(Arrays.asList(\"exec\", \"eval\", \"execute\", \"invoke\", \"newInstance\",\n\t\t\t\t\"forName\", \"getRuntime\", \"loadClass\", \"defineClass\", \"getMethod\", \"getDeclaredMethod\"));\n\n\t\tif (dangerousMethods.contains(methodName)) {\n\t\t\tlog.warn(\"方法名 [{}] 在危险方法黑名单中，拒绝执行\", methodName);\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/ITaskInvok.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.exception.TaskException;\n\n/**\n * 定时任务反射实现接口\n *\n * @author lengleng\n * @date 2025/05/31\n */\npublic interface ITaskInvok {\n\n\t/**\n\t * 执行反射方法\n\t * @param sysJob 任务配置类\n\t * @throws TaskException 执行任务时可能抛出的异常\n\t */\n\tvoid invokMethod(SysJob sysJob) throws TaskException;\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/JarTaskInvok.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.exception.TaskException;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 定时任务可执行jar反射实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Component(\"jarTaskInvok\")\npublic class JarTaskInvok implements ITaskInvok {\n\n\t/**\n\t * 调用方法执行定时任务jar\n\t * @param sysJob 定时任务信息\n\t * @throws TaskException 执行任务时发生异常抛出\n\t */\n\t@Override\n\tpublic void invokMethod(SysJob sysJob) throws TaskException {\n\t\tProcessBuilder processBuilder = new ProcessBuilder();\n\t\tFile jar = new File(sysJob.getExecutePath());\n\t\tprocessBuilder.directory(jar.getParentFile());\n\t\tList<String> commands = new ArrayList<>();\n\t\tcommands.add(\"java\");\n\t\tcommands.add(\"-jar\");\n\t\tcommands.add(sysJob.getExecutePath());\n\t\tif (StrUtil.isNotEmpty(sysJob.getMethodParamsValue())) {\n\t\t\tcommands.add(sysJob.getMethodParamsValue());\n\t\t}\n\t\tprocessBuilder.command(commands);\n\t\ttry {\n\t\t\tprocessBuilder.start();\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tlog.error(\"定时任务jar反射执行异常,执行任务：{}\", sysJob.getExecutePath());\n\t\t\tthrow new TaskException(\"定时任务jar反射执行异常,执行任务：\" + sysJob.getExecutePath());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/JavaClassTaskInvok.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.exception.TaskException;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * 基于Java反射实现的定时任务调用类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Component(\"javaClassTaskInvok\")\n@Slf4j\npublic class JavaClassTaskInvok implements ITaskInvok {\n\n\t/**\n\t * 调用定时任务方法\n\t * @param sysJob 定时任务信息\n\t * @throws TaskException 执行任务过程中出现异常时抛出\n\t */\n\t@Override\n\tpublic void invokMethod(SysJob sysJob) throws TaskException {\n\t\t// 安全验证：检查类名和方法名是否合法\n\t\tif (!ClassNameValidator.isValidClassName(sysJob.getClassName())) {\n\t\t\tlog.error(\"定时任务类名验证失败，类名包含危险特征或在黑名单中：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务类名验证失败，拒绝执行危险类：\" + sysJob.getClassName());\n\t\t}\n\n\t\tif (!ClassNameValidator.isValidMethodName(sysJob.getMethodName())) {\n\t\t\tlog.error(\"定时任务方法名验证失败，方法名包含危险特征：{}\", sysJob.getMethodName());\n\t\t\tthrow new TaskException(\"定时任务方法名验证失败，拒绝执行危险方法：\" + sysJob.getMethodName());\n\t\t}\n\n\t\tObject obj;\n\t\tClass clazz;\n\t\tMethod method;\n\t\tObject returnValue;\n\t\ttry {\n\t\t\tif (StrUtil.isNotEmpty(sysJob.getMethodParamsValue())) {\n\t\t\t\tclazz = Class.forName(sysJob.getClassName());\n\t\t\t\tobj = clazz.getDeclaredConstructor().newInstance();\n\t\t\t\tmethod = clazz.getDeclaredMethod(sysJob.getMethodName(), String.class);\n\t\t\t\treturnValue = method.invoke(obj, sysJob.getMethodParamsValue());\n\t\t\t}\n\t\t\telse {\n\t\t\t\tclazz = Class.forName(sysJob.getClassName());\n\t\t\t\tobj = clazz.getDeclaredConstructor().newInstance();\n\t\t\t\tmethod = clazz.getDeclaredMethod(sysJob.getMethodName());\n\t\t\t\treturnValue = method.invoke(obj);\n\t\t\t}\n\t\t\tif (StrUtil.isEmpty(returnValue.toString())\n\t\t\t\t\t|| PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString())) {\n\t\t\t\tlog.error(\"定时任务javaClassTaskInvok异常,执行任务：{}\", sysJob.getClassName());\n\t\t\t\tthrow new TaskException(\"定时任务javaClassTaskInvok业务执行失败,任务：\" + sysJob.getClassName());\n\t\t\t}\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\tlog.error(\"定时任务java反射类没有找到,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务java反射类没有找到,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t\tcatch (IllegalAccessException e) {\n\t\t\tlog.error(\"定时任务java反射类异常,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务java反射类异常,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t\tcatch (InstantiationException e) {\n\t\t\tlog.error(\"定时任务java反射类异常,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务java反射类异常,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t\tcatch (NoSuchMethodException e) {\n\t\t\tlog.error(\"定时任务java反射执行方法名异常,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务java反射执行方法名异常,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t\tcatch (InvocationTargetException e) {\n\t\t\tlog.error(\"定时任务java反射执行异常,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务java反射执行异常,执行任务：\" + sysJob.getClassName());\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/RestTaskInvok.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpUtil;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.exception.TaskException;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * REST定时任务反射实现类\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@AllArgsConstructor\n@Component(\"restTaskInvok\")\npublic class RestTaskInvok implements ITaskInvok {\n\n\t/**\n\t * 调用方法执行定时任务\n\t * @param sysJob 定时任务信息\n\t * @throws TaskException 任务执行失败时抛出异常\n\t */\n\t@Override\n\tpublic void invokMethod(SysJob sysJob) throws TaskException {\n\t\ttry {\n\t\t\tHttpRequest request = HttpUtil.createGet(sysJob.getExecutePath());\n\t\t\trequest.execute();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlog.error(\"定时任务restTaskInvok异常,执行任务：{}\", sysJob.getExecutePath());\n\t\t\tthrow new TaskException(\"定时任务restTaskInvok业务执行失败,任务：\" + sysJob.getExecutePath());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/SpringBeanTaskInvok.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.exception.TaskException;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.ReflectionUtils;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * 基于Spring Bean的定时任务反射执行器\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Component(\"springBeanTaskInvok\")\n@Slf4j\npublic class SpringBeanTaskInvok implements ITaskInvok {\n\n\t/**\n\t * 调用定时任务方法\n\t * @param sysJob 定时任务信息\n\t * @throws TaskException 当任务执行失败或反射调用异常时抛出\n\t */\n\t@Override\n\tpublic void invokMethod(SysJob sysJob) throws TaskException {\n\t\tObject target;\n\t\tMethod method;\n\t\tObject returnValue;\n\t\t// 通过Spring上下文去找 也有可能找不到\n\t\ttarget = SpringContextHolder.getBean(sysJob.getClassName());\n\t\ttry {\n\t\t\tif (StrUtil.isNotEmpty(sysJob.getMethodParamsValue())) {\n\t\t\t\tmethod = target.getClass().getDeclaredMethod(sysJob.getMethodName(), String.class);\n\t\t\t\tReflectionUtils.makeAccessible(method);\n\t\t\t\treturnValue = method.invoke(target, sysJob.getMethodParamsValue());\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmethod = target.getClass().getDeclaredMethod(sysJob.getMethodName());\n\t\t\t\tReflectionUtils.makeAccessible(method);\n\t\t\t\treturnValue = method.invoke(target);\n\t\t\t}\n\t\t\tif (StrUtil.isEmpty(returnValue.toString())\n\t\t\t\t\t|| PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString())) {\n\t\t\t\tlog.error(\"定时任务springBeanTaskInvok异常,执行任务：{}\", sysJob.getClassName());\n\t\t\t\tthrow new TaskException(\"定时任务springBeanTaskInvok业务执行失败,任务：\" + sysJob.getClassName());\n\t\t\t}\n\t\t}\n\t\tcatch (NoSuchMethodException e) {\n\t\t\tlog.error(\"定时任务spring bean反射异常方法未找到,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务spring bean反射异常方法未找到,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t\tcatch (IllegalAccessException e) {\n\t\t\tlog.error(\"定时任务spring bean反射异常,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务spring bean反射异常,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t\tcatch (InvocationTargetException e) {\n\t\t\tlog.error(\"定时任务spring bean反射执行异常,执行任务：{}\", sysJob.getClassName());\n\t\t\tthrow new TaskException(\"定时任务spring bean反射执行异常,执行任务：\" + sysJob.getClassName());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/TaskInvokFactory.java",
    "content": "package com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.common.core.util.SpringContextHolder;\nimport com.pig4cloud.pig.daemon.quartz.constants.JobTypeQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.exception.TaskException;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 任务调用工厂类：根据任务类型获取对应的任务调用器\n *\n * @author lengleng\n * @version 1.0\n * @date 2025/05/31\n */\n@Slf4j\npublic class TaskInvokFactory {\n\n\t/**\n\t * 根据任务类型获取对应的任务执行器\n\t * @param jobType 任务类型\n\t * @return 任务执行器实例\n\t * @throws TaskException 当任务类型为空或不支持时抛出异常\n\t */\n\tpublic static ITaskInvok getInvoker(String jobType) throws TaskException {\n\t\tif (StrUtil.isBlank(jobType)) {\n\t\t\tlog.info(\"获取TaskInvok传递参数有误，jobType:{}\", jobType);\n\t\t\tthrow new TaskException(\"\");\n\t\t}\n\n\t\tITaskInvok iTaskInvok = null;\n\t\tif (JobTypeQuartzEnum.JAVA.getType().equals(jobType)) {\n\t\t\tiTaskInvok = SpringContextHolder.getBean(\"javaClassTaskInvok\");\n\t\t}\n\t\telse if (JobTypeQuartzEnum.SPRING_BEAN.getType().equals(jobType)) {\n\t\t\tiTaskInvok = SpringContextHolder.getBean(\"springBeanTaskInvok\");\n\t\t}\n\t\telse if (JobTypeQuartzEnum.REST.getType().equals(jobType)) {\n\t\t\tiTaskInvok = SpringContextHolder.getBean(\"restTaskInvok\");\n\t\t}\n\t\telse if (JobTypeQuartzEnum.JAR.getType().equals(jobType)) {\n\t\t\tiTaskInvok = SpringContextHolder.getBean(\"jarTaskInvok\");\n\t\t}\n\t\telse if (StrUtil.isBlank(jobType)) {\n\t\t\tlog.info(\"定时任务类型无对应反射方式，反射类型:{}\", jobType);\n\t\t\tthrow new TaskException(\"\");\n\t\t}\n\n\t\treturn iTaskInvok;\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/TaskInvokUtil.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJobLog;\nimport com.pig4cloud.pig.daemon.quartz.event.SysJobLogEvent;\nimport com.pig4cloud.pig.daemon.quartz.service.SysJobService;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.CronTrigger;\nimport org.quartz.Trigger;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.stereotype.Component;\n\nimport java.time.ZoneId;\nimport java.util.Date;\n\n/**\n * 定时任务反射工具类，用于执行和管理定时任务的反射调用\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Component\n@RequiredArgsConstructor\npublic class TaskInvokUtil {\n\n\tprivate final ApplicationEventPublisher publisher;\n\n\tprivate final SysJobService sysJobService;\n\n\t/**\n\t * 执行定时任务方法\n\t * @param sysJob 定时任务信息\n\t * @param trigger 触发器\n\t */\n\t@SneakyThrows\n\tpublic void invokMethod(SysJob sysJob, Trigger trigger) {\n\n\t\t// 执行开始时间\n\t\tlong startTime;\n\t\t// 执行结束时间\n\t\tlong endTime;\n\t\t// 获取执行开始时间\n\t\tstartTime = System.currentTimeMillis();\n\t\t// 更新定时任务表内的状态、执行时间、上次执行时间、下次执行时间等信息\n\t\tSysJob updateSysjob = new SysJob();\n\t\tupdateSysjob.setJobId(sysJob.getJobId());\n\t\t// 日志\n\t\tSysJobLog sysJobLog = new SysJobLog();\n\t\tsysJobLog.setJobId(sysJob.getJobId());\n\t\tsysJobLog.setJobName(sysJob.getJobName());\n\t\tsysJobLog.setJobGroup(sysJob.getJobGroup());\n\t\tsysJobLog.setJobOrder(sysJob.getJobOrder());\n\t\tsysJobLog.setJobType(sysJob.getJobType());\n\t\tsysJobLog.setExecutePath(sysJob.getExecutePath());\n\t\tsysJobLog.setClassName(sysJob.getClassName());\n\t\tsysJobLog.setMethodName(sysJob.getMethodName());\n\t\tsysJobLog.setMethodParamsValue(sysJob.getMethodParamsValue());\n\t\tsysJobLog.setCronExpression(sysJob.getCronExpression());\n\t\ttry {\n\t\t\t// 执行任务\n\t\t\tITaskInvok iTaskInvok = TaskInvokFactory.getInvoker(sysJob.getJobType());\n\t\t\t// 确保租户上下文有值，使得当前线程中的多租户特性生效。\n\t\t\tiTaskInvok.invokMethod(sysJob);\n\t\t\t// 记录成功状态\n\t\t\tsysJobLog.setJobMessage(PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getDescription());\n\t\t\tsysJobLog.setJobLogStatus(PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getType());\n\t\t\t// 任务表信息更新\n\t\t\tupdateSysjob.setJobExecuteStatus(PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getType());\n\t\t}\n\t\tcatch (Throwable e) {\n\t\t\tlog.error(\"定时任务执行失败，任务名称：{}；任务组名：{}，cron执行表达式：{}，执行时间：{}\", sysJob.getJobName(), sysJob.getJobGroup(),\n\t\t\t\t\tsysJob.getCronExpression(), new Date());\n\t\t\t// 记录失败状态\n\t\t\tsysJobLog.setJobMessage(PigQuartzEnum.JOB_LOG_STATUS_FAIL.getDescription());\n\t\t\tsysJobLog.setJobLogStatus(PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType());\n\t\t\tsysJobLog.setExceptionInfo(StrUtil.sub(e.getMessage(), 0, 2000));\n\t\t\t// 任务表信息更新\n\t\t\tupdateSysjob.setJobExecuteStatus(PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType());\n\t\t}\n\t\tfinally {\n\t\t\t// 记录执行时间 立刻执行使用的是simpleTeigger\n\t\t\tif (trigger instanceof CronTrigger) {\n\t\t\t\tupdateSysjob\n\t\t\t\t\t.setStartTime(trigger.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());\n\t\t\t\tupdateSysjob.setPreviousTime(\n\t\t\t\t\t\ttrigger.getPreviousFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());\n\t\t\t\tupdateSysjob.setNextTime(\n\t\t\t\t\t\ttrigger.getNextFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());\n\t\t\t}\n\t\t\t// 记录执行时长\n\t\t\tendTime = System.currentTimeMillis();\n\t\t\tsysJobLog.setExecuteTime(String.valueOf(endTime - startTime));\n\n\t\t\tpublisher.publishEvent(new SysJobLogEvent(sysJobLog));\n\t\t\tsysJobService.updateById(updateSysjob);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/TaskUtil.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage com.pig4cloud.pig.daemon.quartz.util;\n\nimport com.pig4cloud.pig.daemon.quartz.config.PigQuartzFactory;\nimport com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum;\nimport com.pig4cloud.pig.daemon.quartz.entity.SysJob;\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.*;\nimport org.springframework.stereotype.Component;\n\n/**\n * 定时任务工具类，提供定时任务的增删改查及状态管理功能\n *\n * @author lengleng\n * @date 2025/05/31\n */\n@Slf4j\n@Component\npublic class TaskUtil {\n\n\t/**\n\t * 获取定时任务的唯一key\n\t * @param sysjob 定时任务信息\n\t * @return 定时任务的唯一key\n\t */\n\tpublic static JobKey getJobKey(SysJob sysjob) {\n\t\treturn JobKey.jobKey(sysjob.getJobName(), sysjob.getJobGroup());\n\t}\n\n\t/**\n\t * 获取定时任务触发器的唯一键\n\t * @param sysjob 定时任务信息\n\t * @return 定时任务触发器键\n\t */\n\tpublic static TriggerKey getTriggerKey(SysJob sysjob) {\n\t\treturn TriggerKey.triggerKey(sysjob.getJobName(), sysjob.getJobGroup());\n\t}\n\n\t/**\n\t * 添加或更新定时任务\n\t * @param sysjob 任务信息\n\t * @param scheduler 调度器\n\t */\n\tpublic void addOrUpateJob(SysJob sysjob, Scheduler scheduler) {\n\t\tCronTrigger trigger = null;\n\t\ttry {\n\t\t\tJobKey jobKey = getJobKey(sysjob);\n\t\t\t// 获得触发器\n\t\t\tTriggerKey triggerKey = getTriggerKey(sysjob);\n\t\t\ttrigger = (CronTrigger) scheduler.getTrigger(triggerKey);\n\t\t\t// 判断触发器是否存在（如果存在说明之前运行过但是在当前被禁用了，如果不存在说明一次都没运行过）\n\t\t\tif (trigger == null) {\n\t\t\t\t// 新建一个工作任务 指定任务类型为串接进行的\n\t\t\t\tJobDetail jobDetail = JobBuilder.newJob(PigQuartzFactory.class).withIdentity(jobKey).build();\n\t\t\t\t// 将任务信息添加到任务信息中\n\t\t\t\tjobDetail.getJobDataMap().put(PigQuartzEnum.SCHEDULE_JOB_KEY.getType(), sysjob);\n\t\t\t\t// 将cron表达式进行转换\n\t\t\t\tCronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(sysjob.getCronExpression());\n\t\t\t\tcronScheduleBuilder = this.handleCronScheduleMisfirePolicy(sysjob, cronScheduleBuilder);\n\t\t\t\t// 创建触发器并将cron表达式对象给塞入\n\t\t\t\ttrigger = TriggerBuilder.newTrigger()\n\t\t\t\t\t.withIdentity(triggerKey)\n\t\t\t\t\t.withSchedule(cronScheduleBuilder)\n\t\t\t\t\t.build();\n\t\t\t\t// 在调度器中将触发器和任务进行组合\n\t\t\t\tscheduler.scheduleJob(jobDetail, trigger);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(sysjob.getCronExpression());\n\t\t\t\tcronScheduleBuilder = this.handleCronScheduleMisfirePolicy(sysjob, cronScheduleBuilder);\n\t\t\t\t// 按照新的规则进行\n\t\t\t\ttrigger = trigger.getTriggerBuilder()\n\t\t\t\t\t.withIdentity(triggerKey)\n\t\t\t\t\t.withSchedule(cronScheduleBuilder)\n\t\t\t\t\t.build();\n\t\t\t\t// 将任务信息更新到任务信息中\n\t\t\t\ttrigger.getJobDataMap().put(PigQuartzEnum.SCHEDULE_JOB_KEY.getType(), sysjob);\n\t\t\t\t// 重启\n\t\t\t\tscheduler.rescheduleJob(triggerKey, trigger);\n\t\t\t}\n\t\t\t// 如任务状态为暂停\n\t\t\tif (sysjob.getJobStatus().equals(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType())) {\n\t\t\t\tthis.pauseJob(sysjob, scheduler);\n\t\t\t}\n\t\t}\n\t\tcatch (SchedulerException e) {\n\t\t\tlog.error(\"添加或更新定时任务，失败信息：{}\", e.getMessage());\n\t\t}\n\t}\n\n\t/**\n\t * 立即执行一次任务\n\t * @param scheduler 调度器\n\t * @param sysJob 任务信息\n\t * @return 任务是否执行成功\n\t */\n\tpublic static boolean runOnce(Scheduler scheduler, SysJob sysJob) {\n\t\ttry {\n\t\t\t// 参数\n\t\t\tJobDataMap dataMap = new JobDataMap();\n\t\t\tdataMap.put(PigQuartzEnum.SCHEDULE_JOB_KEY.getType(), sysJob);\n\n\t\t\tscheduler.triggerJob(getJobKey(sysJob), dataMap);\n\t\t}\n\t\tcatch (SchedulerException e) {\n\t\t\tlog.error(\"立刻执行定时任务，失败信息：{}\", e.getMessage());\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * 暂停定时任务\n\t * @param sysjob 任务信息\n\t * @param scheduler 任务调度器\n\t */\n\tpublic void pauseJob(SysJob sysjob, Scheduler scheduler) {\n\t\ttry {\n\t\t\tif (scheduler != null) {\n\t\t\t\tscheduler.pauseJob(getJobKey(sysjob));\n\t\t\t}\n\t\t}\n\t\tcatch (SchedulerException e) {\n\t\t\tlog.error(\"暂停任务失败，失败信息：{}\", e.getMessage());\n\t\t}\n\n\t}\n\n\t/**\n\t * 恢复定时任务\n\t * @param sysjob 任务信息\n\t * @param scheduler 任务调度器\n\t */\n\tpublic void resumeJob(SysJob sysjob, Scheduler scheduler) {\n\t\ttry {\n\t\t\tif (scheduler != null) {\n\t\t\t\tscheduler.resumeJob(getJobKey(sysjob));\n\t\t\t}\n\t\t}\n\t\tcatch (SchedulerException e) {\n\t\t\tlog.error(\"恢复任务失败，失败信息：{}\", e.getMessage());\n\t\t}\n\n\t}\n\n\t/**\n\t * 移除定时任务\n\t * @param sysjob 定时任务信息\n\t * @param scheduler 任务调度器\n\t */\n\tpublic void removeJob(SysJob sysjob, Scheduler scheduler) {\n\t\ttry {\n\t\t\tif (scheduler != null) {\n\t\t\t\t// 停止触发器\n\t\t\t\tscheduler.pauseTrigger(getTriggerKey(sysjob));\n\t\t\t\t// 移除触发器\n\t\t\t\tscheduler.unscheduleJob(getTriggerKey(sysjob));\n\t\t\t\t// 删除任务\n\t\t\t\tscheduler.deleteJob(getJobKey(sysjob));\n\t\t\t}\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlog.error(\"移除定时任务失败，失败信息：{}\", e.getMessage());\n\t\t}\n\t}\n\n\t/**\n\t * 启动所有运行中的定时任务\n\t * @param scheduler 调度器实例\n\t * @throws SchedulerException 当启动任务失败时抛出异常\n\t */\n\tpublic void startJobs(Scheduler scheduler) {\n\t\ttry {\n\t\t\tif (scheduler != null) {\n\t\t\t\tscheduler.resumeAll();\n\t\t\t}\n\t\t}\n\t\tcatch (SchedulerException e) {\n\t\t\tlog.error(\"启动所有运行定时任务失败，失败信息：{}\", e.getMessage());\n\t\t}\n\t}\n\n\t/**\n\t * 暂停所有运行中的定时任务\n\t * @param scheduler 调度器实例\n\t * @throws Exception 暂停任务过程中可能抛出的异常\n\t */\n\tpublic void pauseJobs(Scheduler scheduler) {\n\t\ttry {\n\t\t\tif (scheduler != null) {\n\t\t\t\tscheduler.pauseAll();\n\t\t\t}\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tlog.error(\"暂停所有运行定时任务失败，失败信息：{}\", e.getMessage());\n\t\t}\n\t}\n\n\t/**\n\t * 根据任务配置处理错失执行策略\n\t * @param sysJob 任务信息\n\t * @param cronScheduleBuilder 原始Cron调度构建器\n\t * @return 处理后的Cron调度构建器\n\t */\n\tprivate CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob sysJob,\n\t\t\tCronScheduleBuilder cronScheduleBuilder) {\n\t\tif (PigQuartzEnum.MISFIRE_DEFAULT.getType().equals(sysJob.getMisfirePolicy())) {\n\t\t\treturn cronScheduleBuilder;\n\t\t}\n\t\telse if (PigQuartzEnum.MISFIRE_IGNORE_MISFIRES.getType().equals(sysJob.getMisfirePolicy())) {\n\t\t\treturn cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();\n\t\t}\n\t\telse if (PigQuartzEnum.MISFIRE_FIRE_AND_PROCEED.getType().equals(sysJob.getMisfirePolicy())) {\n\t\t\treturn cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();\n\t\t}\n\t\telse if (PigQuartzEnum.MISFIRE_DO_NOTHING.getType().equals(sysJob.getMisfirePolicy())) {\n\t\t\treturn cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();\n\t\t}\n\t\telse {\n\t\t\treturn cronScheduleBuilder;\n\t\t}\n\t}\n\n\t/**\n\t * 校验cron表达式是否合法\n\t * @param cronExpression 待校验的cron表达式\n\t * @return true表示合法，false表示不合法\n\t */\n\tpublic boolean isValidCron(String cronExpression) {\n\t\treturn CronExpression.isValidExpression(cronExpression);\n\t}\n\n}\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/resources/application.yml",
    "content": "server:\n  port: 5007\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      username: @nacos.username@\n      password: @nacos.password@\n      discovery:\n        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}\n      config:\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n  config:\n    import:\n      - optional:nacos:application-@profiles.active@.yml\n      - optional:nacos:${spring.application.name}-@profiles.active@.yml\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n    小技巧: 在根pom里面设置统一存放路径，统一管理方便维护\n    <properties>\n        <log-path>/Users/lengleng</log-path>\n    </properties>\n    1. 其他模块加日志输出，直接copy本文件放在resources 目录即可\n    2. 注意修改 <property name=\"${log-path}/log.path\" value=\"\"/> 的value模块\n-->\n<configuration debug=\"false\" scan=\"false\">\n\t<property name=\"log.path\" value=\"logs/${project.artifactId}\"/>\n\t<!-- 彩色日志格式 -->\n\t<property name=\"CONSOLE_LOG_PATTERN\"\n\t\t\t  value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\t<property name=\"FILE_LOG_PATTERN\" value=\":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n\"/>\n\t<!-- 彩色日志依赖的渲染类 -->\n\t<conversionRule conversionWord=\"clr\" class=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n\t<conversionRule conversionWord=\"wex\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n\t<conversionRule conversionWord=\"wEx\"\n\t\t\t\t\tclass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n\t<!-- Console log output -->\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>${CONSOLE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file debug output -->\n\t<appender name=\"debug\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/debug.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<!-- Log file error output -->\n\t<appender name=\"error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>${log.path}/error.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>\n\t\t\t<maxFileSize>50MB</maxFileSize>\n\t\t\t<maxHistory>30</maxHistory>\n\t\t</rollingPolicy>\n\t\t<encoder>\n\t\t\t<pattern>${FILE_LOG_PATTERN}</pattern>\n\t\t</encoder>\n\t\t<filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n\t\t\t<level>ERROR</level>\n\t\t</filter>\n\t</appender>\n\n\t<logger name=\"org.activiti.engine.impl.db\" level=\"DEBUG\">\n\t\t<appender-ref ref=\"debug\"/>\n\t</logger>\n\n\t<!--nacos 心跳 INFO 屏蔽-->\n\t<logger name=\"com.alibaba.nacos\" level=\"OFF\">\n\t\t<appender-ref ref=\"error\"/>\n\t</logger>\n\t<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->\n\t<root level=\"DEBUG\">\n\t\t<appender-ref ref=\"console\"/>\n\t\t<appender-ref ref=\"debug\"/>\n\t\t<appender-ref ref=\"error\"/>\n\t</root>\n</configuration>\n"
  },
  {
    "path": "pig-visual/pig-quartz/src/main/resources/quartz-config.yml",
    "content": "# 如下配置为使用数据库存储定时任务，属性不经常修改，所以不放在 application.yml 中统一管理\nspring:\n  quartz:\n    properties:\n      org:\n        quartz:\n          scheduler:\n            instanceName: clusteredScheduler\n            instanceId: AUTO\n          jobStore:\n            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore # 数据库存储\n            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 数据库代理\n            tablePrefix: QRTZ_ # 表前缀\n            isClustered: true # 集群\n            clusterCheckinInterval: 30000 # 集群检查间隔\n            useProperties: false\n          threadPool:\n            class: org.quartz.simpl.SimpleThreadPool # 线程池\n            threadCount: 50 # 线程数量\n            threadPriority: 5 # 线程优先级\n            threadsInheritContextClassLoaderOfInitializingThread: true # 是否继承类加载器\n    job-store-type: jdbc # 持久化到数据库\n    jdbc:\n      initialize-schema: never # 不初始化表结构\n"
  },
  {
    "path": "pig-visual/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.pig4cloud</groupId>\n        <artifactId>pig</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>pig-visual</artifactId>\n\n    <description>pig 图形化相关功能</description>\n    <packaging>pom</packaging>\n\n    <modules>\n        <module>pig-codegen</module>\n        <module>pig-monitor</module>\n        <module>pig-quartz</module>\n    </modules>\n</project>\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n\t\t xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<groupId>com.pig4cloud</groupId>\n\t<artifactId>pig</artifactId>\n\t<name>${project.artifactId}</name>\n\t<version>${revision}</version>\n\t<packaging>pom</packaging>\n\t<url>https://www.pig4cloud.com</url>\n\t<properties>\n\t\t<!-- 项目版本号 -->\n\t\t<revision>3.9.2</revision>\n\t\t<java.version>17</java.version>\n\t\t<spring-boot.version>3.5.11</spring-boot.version>\n\t\t<spring-cloud.version>2025.0.1</spring-cloud.version>\n\t\t<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>\n\t\t<jasypt.version>3.0.5</jasypt.version>\n\t\t<git.commit.plugin>9.0.1</git.commit.plugin>\n\t\t<docker.namespace>pig4cloud</docker.namespace>\n\t\t<maven.compiler.target>17</maven.compiler.target>\n\t\t<docker.host>http://192.168.0.100:2375</docker.host>\n\t\t<commons-lang3.version>3.20.0</commons-lang3.version>\n\t\t<docker.plugin.version>0.45.1</docker.plugin.version>\n\t\t<spring.checkstyle.plugin>0.0.47</spring.checkstyle.plugin>\n\t\t<spring-boot-admin.version>3.5.6</spring-boot-admin.version>\n\t\t<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>\n\t\t<docker.username>username</docker.username>\n\t\t<docker.password>password</docker.password>\n\t\t<docker.registry>registry.cn-shanghai.aliyuncs.com</docker.registry>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>\n\t</properties>\n\n\t<!-- 以下依赖 全局所有的模块都会引入  -->\n\t<dependencies>\n\t\t<!--配置文件处理器-->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<!--配置文件加解密-->\n\t\t<dependency>\n\t\t\t<groupId>com.github.ulisesbocchio</groupId>\n\t\t\t<artifactId>jasypt-spring-boot-starter</artifactId>\n\t\t\t<version>${jasypt.version}</version>\n\t\t</dependency>\n\t\t<!--监控-->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<!--监控客户端-->\n\t\t<dependency>\n\t\t\t<groupId>de.codecentric</groupId>\n\t\t\t<artifactId>spring-boot-admin-starter-client</artifactId>\n\t\t\t<version>${spring-boot-admin.version}</version>\n\t\t</dependency>\n\t\t<!--Lombok-->\n\t\t<dependency>\n\t\t\t<groupId>org.projectlombok</groupId>\n\t\t\t<artifactId>lombok</artifactId>\n\t\t\t<scope>provided</scope>\n\t\t</dependency>\n\t\t<!-- JAVA 17 -->\n\t\t<dependency>\n\t\t\t<groupId>com.sun.xml.bind</groupId>\n\t\t\t<artifactId>jaxb-impl</artifactId>\n\t\t</dependency>\n\t\t<!--测试依赖-->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<modules>\n\t\t<module>pig-register</module>\n\t\t<module>pig-gateway</module>\n\t\t<module>pig-auth</module>\n\t\t<module>pig-upms</module>\n\t\t<module>pig-common</module>\n\t\t<module>pig-visual</module>\n\t</modules>\n\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<!--pig 公共版本定义-->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.pig4cloud</groupId>\n\t\t\t\t<artifactId>pig-common-bom</artifactId>\n\t\t\t\t<version>${project.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<!-- spring boot 依赖 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-dependencies</artifactId>\n\t\t\t\t<version>${spring-boot.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<!-- spring cloud 依赖 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-dependencies</artifactId>\n\t\t\t\t<version>${spring-cloud.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<!-- commons-lang3 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t\t<artifactId>commons-lang3</artifactId>\n\t\t\t\t<version>${commons-lang3.version}</version>\n\t\t\t</dependency>\n\t\t\t<!-- spring cloud alibaba 依赖 -->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.alibaba.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-alibaba-dependencies</artifactId>\n\t\t\t\t<version>${spring-cloud-alibaba.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<!--web 模块-->\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t\t\t<version>${spring-boot.version}</version>\n\t\t\t\t<exclusions>\n\t\t\t\t\t<!--排除tomcat依赖-->\n\t\t\t\t\t<exclusion>\n\t\t\t\t\t\t<artifactId>spring-boot-starter-tomcat</artifactId>\n\t\t\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t\t</exclusion>\n\t\t\t\t</exclusions>\n\t\t\t</dependency>\n\t\t\t<!-- 排除 spring cloud alibaba 令人头疼的日志封装-->\n\t\t\t<dependency>\n\t\t\t\t<groupId>com.alibaba.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n\t\t\t\t<version>${spring-cloud-alibaba.version}</version>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\n\t<build>\n\t\t<finalName>${project.name}</finalName>\n\t\t<resources>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<filtering>true</filtering>\n\t\t\t</resource>\n\t\t</resources>\n\t\t<pluginManagement>\n\t\t\t<plugins>\n\t\t\t\t<plugin>\n\t\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t\t<version>${spring-boot.version}</version>\n\t\t\t\t\t<configuration>\n\t\t\t\t\t\t<finalName>${project.build.finalName}</finalName>\n\t\t\t\t\t\t<layers>\n\t\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t\t</layers>\n\t\t\t\t\t</configuration>\n\t\t\t\t\t<executions>\n\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t<goal>repackage</goal>\n\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t</execution>\n\t\t\t\t\t</executions>\n\t\t\t\t</plugin>\n\t\t\t\t<plugin>\n\t\t\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t\t\t<artifactId>docker-maven-plugin</artifactId>\n\t\t\t\t\t<version>${docker.plugin.version}</version>\n\t\t\t\t\t<configuration>\n\t\t\t\t\t\t<!-- Docker Remote Api-->\n\t\t\t\t\t\t<dockerHost>${docker.host}</dockerHost>\n\t\t\t\t\t\t<!-- Docker 镜像私服-->\n\t\t\t\t\t\t<registry>${docker.registry}</registry>\n\t\t\t\t\t\t<!-- 认证信息-->\n\t\t\t\t\t\t<authConfig>\n\t\t\t\t\t\t\t<push>\n\t\t\t\t\t\t\t\t<username>${docker.username}</username>\n\t\t\t\t\t\t\t\t<password>${docker.password}</password>\n\t\t\t\t\t\t\t</push>\n\t\t\t\t\t\t</authConfig>\n\t\t\t\t\t\t<images>\n\t\t\t\t\t\t\t<image>\n\t\t\t\t\t\t\t\t<!-- 镜像名称： 172.17.0.111/library/pig-gateway:2.6.3-->\n\t\t\t\t\t\t\t\t<name>${docker.registry}/${docker.namespace}/${project.name}:${project.version}</name>\n\t\t\t\t\t\t\t\t<build>\n\t\t\t\t\t\t\t\t\t<dockerFile>${project.basedir}/Dockerfile</dockerFile>\n\t\t\t\t\t\t\t\t</build>\n\t\t\t\t\t\t\t</image>\n\t\t\t\t\t\t</images>\n\t\t\t\t\t</configuration>\n\t\t\t\t</plugin>\n\t\t\t</plugins>\n\t\t</pluginManagement>\n\t\t<plugins>\n\t\t\t<!-- 统一 revision 版本 -->\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.codehaus.mojo</groupId>\n\t\t\t\t<artifactId>flatten-maven-plugin</artifactId>\n\t\t\t\t<version>${flatten-maven-plugin.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<flattenMode>resolveCiFriendliesOnly</flattenMode>\n\t\t\t\t\t<updatePomFile>true</updatePomFile>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>flatten</id>\n\t\t\t\t\t\t<phase>process-resources</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>flatten</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>flatten.clean</id>\n\t\t\t\t\t\t<phase>clean</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>clean</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t<version>${maven-compiler-plugin.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<release>${java.version}</release>\n\t\t\t\t\t<encoding>UTF-8</encoding>\n\t\t\t\t\t<parameters>true</parameters>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<!--打包jar 与git commit 关联插件-->\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.github.git-commit-id</groupId>\n\t\t\t\t<artifactId>git-commit-id-maven-plugin</artifactId>\n\t\t\t\t<version>${git.commit.plugin}</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>get-the-git-infos</id>\n\t\t\t\t\t\t<phase>initialize</phase>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t\t<configuration>\n\t\t\t\t\t<failOnNoGitDirectory>false</failOnNoGitDirectory>\n\t\t\t\t\t<generateGitPropertiesFile>true</generateGitPropertiesFile>\n\t\t\t\t\t<!--因为项目定制了jackson的日期时间序列化/反序列化格式，因此这里要进行配置,不然通过management.info.git.mode=full进行完整git信息监控时会存在问题-->\n\t\t\t\t\t<dateFormat>yyyy-MM-dd HH:mm:ss</dateFormat>\n\t\t\t\t\t<includeOnlyProperties>\n\t\t\t\t\t\t<includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>\n\t\t\t\t\t\t<includeOnlyProperty>^git.commit.(id|message|time).*$</includeOnlyProperty>\n\t\t\t\t\t</includeOnlyProperties>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<!--\n\t\t\t\t代码格式插件，默认使用spring 规则，可运行命令进行项目格式化：./mvnw spring-javaformat:apply 或 mvn spring-javaformat:apply，可在IDEA中安装插件以下插件进行自动格式化：\n\t\t\t\thttps://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin\n\t\t\t-->\n\t\t\t<plugin>\n\t\t\t\t<groupId>io.spring.javaformat</groupId>\n\t\t\t\t<artifactId>spring-javaformat-maven-plugin</artifactId>\n\t\t\t\t<version>${spring.checkstyle.plugin}</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<phase>validate</phase>\n\t\t\t\t\t\t<inherited>true</inherited>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>cloud</id>\n\t\t\t<properties>\n\t\t\t\t<!-- 环境标识，需要与配置文件的名称相对应 -->\n\t\t\t\t<profiles.active>dev</profiles.active>\n\t\t\t\t<nacos.username>nacos</nacos.username>\n\t\t\t\t<nacos.password>nacos</nacos.password>\n\t\t\t</properties>\n\t\t\t<activation>\n\t\t\t\t<!-- 默认环境 -->\n\t\t\t\t<activeByDefault>true</activeByDefault>\n\t\t\t</activation>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>boot</id>\n\t\t\t<modules>\n\t\t\t\t<module>pig-boot</module>\n\t\t\t</modules>\n\t\t</profile>\n\t\t<!-- 自动激活，针对 lombok 注解增强-->\n\t\t<profile>\n\t\t\t<id>jdk25-annotation-processor</id>\n\t\t\t<activation>\n\t\t\t\t<jdk>[25,)</jdk>\n\t\t\t</activation>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<!-- 支持 JDK 25+ 编译 -->\n\t\t\t\t\t\t\t<annotationProcessorPaths>\n\t\t\t\t\t\t\t\t<path>\n\t\t\t\t\t\t\t\t\t<groupId>org.projectlombok</groupId>\n\t\t\t\t\t\t\t\t\t<artifactId>lombok</artifactId>\n\t\t\t\t\t\t\t\t</path>\n\t\t\t\t\t\t\t</annotationProcessorPaths>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t</profiles>\n</project>\n"
  }
]