[
  {
    "path": ".gitignore",
    "content": "### IDEA ###\n.idea/*\n*.iml\n*/target/*\n*/*.iml\n/.gradle/\n/application.pid"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"{}\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright 2019-2023 Zheng Jie\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."
  },
  {
    "path": "README.md",
    "content": "<h1 style=\"text-align: center\">ELADMIN 后台管理系统</h1>\n<div style=\"text-align: center\">\n\n[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/elunez/eladmin/blob/master/LICENSE)\n[![star](https://gitee.com/elunez/eladmin/badge/star.svg?theme=white)](https://gitee.com/elunez/eladmin)\n[![GitHub stars](https://img.shields.io/github/stars/elunez/eladmin.svg?style=social&label=Stars)](https://github.com/elunez/eladmin)\n[![GitHub forks](https://img.shields.io/github/forks/elunez/eladmin.svg?style=social&label=Fork)](https://github.com/elunez/eladmin)\n\n</div>\n\n#### 项目简介\n一个基于 Spring Boot 2.7.18 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统\n\n现已发布基于 mybatis-plus 版本，项目地址：[https://github.com/elunez/eladmin-mp](https://github.com/elunez/eladmin-mp)、[https://gitee.com/elunez/eladmin-mp](https://gitee.com/elunez/eladmin-mp)。\n\n**开发文档：**  [https://eladmin.vip](https://eladmin.vip)\n\n**体验地址：**  [https://eladmin.vip/demo](https://eladmin.vip/demo)\n\n**账号密码：** `admin / 123456`\n\n#### 项目源码\n\n|     |   后端源码  |   前端源码  |\n|---  |--- | --- |\n|  github   |  https://github.com/elunez/eladmin   |  https://github.com/elunez/eladmin-web   |\n|  码云   |  https://gitee.com/elunez/eladmin   |  https://gitee.com/elunez/eladmin-web   |\n\n#### VPS推荐\n<a href=\"https://bwh81.net/aff.php?aff=70876\" target=\"_blank\">\n<img src=\"https://eladmin.vip/images/banner/side.jpeg\" style=\"width: 435px;border-radius: 2px;\">\n</a>\n\n使用优惠码: `BWHCGLUKKB`，可获得 6.81% 的折扣 [查看介绍](https://bwhstock.in/)\n\n#### 主要特性\n- 使用最新技术栈，社区资源丰富。\n- 高效率开发，代码生成器可一键生成前后端代码\n- 支持数据字典，可方便地对一些状态进行管理\n- 支持接口限流，避免恶意请求导致服务层压力过大\n- 支持接口级别的功能权限与数据权限，可自定义操作\n- 自定义权限注解与匿名接口注解，可快速对接口拦截与放行\n- 对一些常用地前端组件封装：表格数据请求、数据字典等\n- 前后端统一异常拦截处理，统一输出异常，避免繁琐的判断\n- 支持在线用户管理与服务器性能监控，支持限制单用户登录\n- 支持运维管理，可方便地对远程服务器的应用进行部署与管理\n\n####  系统功能\n- 用户管理：提供用户的相关配置，新增用户后，默认密码为123456\n- 角色管理：对权限与菜单进行分配，可根据部门设置角色的数据权限\n- 菜单管理：已实现菜单动态路由，后端可配置化，支持多级菜单\n- 部门管理：可配置系统组织架构，树形表格展示\n- 岗位管理：配置各个部门的职位\n- 字典管理：可维护常用一些固定的数据，如：状态，性别等\n- 系统日志：记录用户操作日志与异常日志，方便开发人员定位排错\n- SQL监控：采用druid 监控数据库访问性能，默认用户名admin，密码123456\n- 定时任务：整合Quartz做定时任务，加入任务日志，任务运行情况一目了然\n- 代码生成：高灵活度生成前后端代码，减少大量重复的工作任务\n- 邮件工具：配合富文本，发送html格式的邮件\n- 亚马逊S3云存储：支持市面上大多数对象存储，兼容亚马逊S3协议，如七牛云，阿里云等\n- 支付宝支付：整合了支付宝支付并且提供了测试账号，可自行测试\n- 服务监控：监控服务器的负载情况\n- 运维管理：一键部署你的应用\n\n#### 项目结构\n项目采用按功能分模块的开发方式，结构如下\n\n- `eladmin-common` 为系统的公共模块，各种工具类，公共配置存在该模块\n\n- `eladmin-system` 为系统核心模块也是项目入口模块，也是最终需要打包部署的模块\n\n- `eladmin-logging` 为系统的日志模块，其他模块如果需要记录日志需要引入该模块\n\n- `eladmin-tools` 为第三方工具模块，包含：邮件、亚马逊S3云存储、本地存储、支付宝\n\n- `eladmin-generator` 为系统的代码生成模块，支持生成前后端CRUD代码\n\n#### 详细结构\n\n```\n- eladmin-common 公共模块\n    - annotation 为系统自定义注解\n    - aspect 自定义注解的切面\n    - base 提供了Entity、DTO基类和mapstruct的通用mapper\n    - config 项目通用配置\n        - Web配置跨域与静态资源映射、Swagger配置，文件上传临时路径配置\n        - Redis配置，Redission配置, 异步线程池配置\n        - 权限拦截配置、AuthorityConfig、Druid 删除广告配置\n    - exception 项目统一异常的处理\n    - utils 系统通用工具类，列举一些常用的工具类\n        - BigDecimaUtils 金额计算工具类\n        - RequestHolder 请求工具类\n        - SecurityUtils 安全工具类\n        - StringUtils 字符串工具类\n        - SpringBeanHolder Spring Bean工具类\n        - RedisUtils Redis工具类\n        - EncryptUtils 加密工具类\n        - FileUtil 文件工具类\n- eladmin-system 系统核心模块（系统启动入口）\n    - sysrunner 程序启动后处理数据\n\t- modules 系统相关模块(登录授权、系统监控、定时任务、系统模块、运维模块)\n- eladmin-logging 系统日志模块\n- eladmin-tools 系统第三方工具模块\n    - email 邮件工具\n    - amazon 亚马逊S3云存储工具\n    - alipay 支付宝支付工具\n    - local-storage 本地存储工具\n- eladmin-generator 系统代码生成模块\n```\n\n#### 特别鸣谢\n\n- 感谢 [PanJiaChen](https://github.com/PanJiaChen/vue-element-admin) 大佬提供的前端模板\n\n- 感谢 [Moxun](https://github.com/moxun1639) 大佬提供的前端 Curd 通用组件\n\n- 感谢 [zhy6599](https://gitee.com/zhy6599) 大佬提供的后端运维管理相关功能\n\n- 感谢 [j.yao.SUSE](https://github.com/everhopingandwaiting) 大佬提供的匿名接口与Redis限流等功能\n\n- 感谢 [d15801543974](https://github.com/d15801543974) 大佬提供的基于注解的通用查询方式\n\n#### 项目捐赠\n项目的发展离不开你的支持，请作者喝杯咖啡吧☕  [Donate](https://eladmin.vip/pages/030101/)\n\n#### 反馈交流\n- QQ交流群：891137268 、947578238、659622532"
  },
  {
    "path": "eladmin-common/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\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>eladmin</artifactId>\n        <groupId>me.zhengjie</groupId>\n        <version>2.7</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <properties>\n        <hutool.version>5.8.35</hutool.version>\n    </properties>\n\n    <artifactId>eladmin-common</artifactId>\n    <name>公共模块</name>\n\n    <dependencies>\n        <!--工具包-->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n            <version>${hutool.version}</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * <p>\n *   用于判断是否过滤数据权限\n *   1、如果没有用到 @OneToOne 这种关联关系，只需要填写 fieldName [参考：DeptQueryCriteria.class]\n *   2、如果用到了 @OneToOne ，fieldName 和 joinName 都需要填写，拿UserQueryCriteria.class举例:\n *   应该是 @DataPermission(joinName = \"dept\", fieldName = \"id\")\n * </p>\n * @author Zheng Jie\n * @website <a href=\"https://eladmin.vip\">...</a>\n * @date 2020-05-07\n **/\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface DataPermission {\n\n    /**\n     * Entity 中的字段名称\n     */\n    String fieldName() default \"\";\n\n    /**\n     * Entity 中与部门关联的字段名称\n     */\n    String joinName() default \"\";\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.annotation;\n\nimport me.zhengjie.aspect.LimitType;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * @author jacky\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Limit {\n\n    // 资源名称，用于描述接口功能\n    String name() default \"\";\n\n    // 资源 key\n    String key() default \"\";\n\n    // key prefix\n    String prefix() default \"\";\n\n    // 时间的，单位秒\n    int period();\n\n    // 限制访问次数\n    int count();\n\n    // 限制类型\n    LimitType limitType() default LimitType.CUSTOMER;\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/Query.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * @author Zheng Jie\n * @date 2019-6-4 13:52:30\n */\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Query {\n\n    // Dong ZhaoYang 2017/8/7 基本对象的属性名\n    String propName() default \"\";\n    // Dong ZhaoYang 2017/8/7 查询方式\n    Type type() default Type.EQUAL;\n\n    /**\n     * 连接查询的属性名，如User类中的dept\n     */\n    String joinName() default \"\";\n\n    /**\n     * 默认左连接\n     */\n    Join join() default Join.LEFT;\n\n    /**\n     * 多字段模糊搜索，仅支持String类型字段，多个用逗号隔开, 如@Query(blurry = \"email,username\")\n     */\n    String blurry() default \"\";\n\n    enum Type {\n        // jie 2019/6/4 相等\n        EQUAL\n        // Dong ZhaoYang 2017/8/7 大于等于\n        , GREATER_THAN\n        // Dong ZhaoYang 2017/8/7 小于等于\n        , LESS_THAN\n        // Dong ZhaoYang 2017/8/7 中模糊查询\n        , INNER_LIKE\n        // Dong ZhaoYang 2017/8/7 左模糊查询\n        , LEFT_LIKE\n        // Dong ZhaoYang 2017/8/7 右模糊查询\n        , RIGHT_LIKE\n        // Dong ZhaoYang 2017/8/7 小于\n        , LESS_THAN_NQ\n        // jie 2019/6/4 包含\n        , IN\n        // 不包含\n        , NOT_IN\n        // 不等于\n        ,NOT_EQUAL\n        // between\n        ,BETWEEN\n        // 不为空\n        ,NOT_NULL\n        // 为空\n        ,IS_NULL,\n        // Aborn Jiang 2022/06/01, 对应SQL: SELECT * FROM table WHERE FIND_IN_SET('querytag', table.tags);\n        FIND_IN_SET\n    }\n\n    /**\n     * @author Zheng Jie\n     * 适用于简单连接查询，复杂的请自定义该注解，或者使用sql查询\n     */\n    enum Join {\n        /** jie 2019-6-4 13:18:30 */\n        LEFT, RIGHT, INNER\n    }\n\n}\n\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.annotation.rest;\n\nimport java.lang.annotation.*;\n\n/**\n * @author jacky\n *  用于标记匿名访问方法\n */\n@Inherited\n@Documented\n@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface AnonymousAccess {\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java",
    "content": "/*\n * Copyright 2002-2016 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 *      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 me.zhengjie.annotation.rest;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.core.annotation.AliasFor;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n/**\n * Annotation for mapping HTTP {@code DELETE} requests onto specific handler\n * methods.\n * 支持匿名访问  DeleteMapping\n *\n * @author liaojinlong\n * @see AnonymousGetMapping\n * @see AnonymousPostMapping\n * @see AnonymousPutMapping\n * @see AnonymousPatchMapping\n * @see RequestMapping\n */\n@AnonymousAccess\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@RequestMapping(method = RequestMethod.DELETE)\npublic @interface AnonymousDeleteMapping {\n\n    /**\n     * Alias for {@link RequestMapping#name}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String name() default \"\";\n\n    /**\n     * Alias for {@link RequestMapping#value}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] value() default {};\n\n    /**\n     * Alias for {@link RequestMapping#path}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] path() default {};\n\n    /**\n     * Alias for {@link RequestMapping#params}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] params() default {};\n\n    /**\n     * Alias for {@link RequestMapping#headers}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] headers() default {};\n\n    /**\n     * Alias for {@link RequestMapping#consumes}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] consumes() default {};\n\n    /**\n     * Alias for {@link RequestMapping#produces}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] produces() default {};\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java",
    "content": "/*\n * Copyright 2002-2016 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 *      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 me.zhengjie.annotation.rest;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.core.annotation.AliasFor;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n/**\n * Annotation for mapping HTTP {@code GET} requests onto specific handler\n * methods.\n * <p>\n * 支持匿名访问   GetMapping\n *\n * @author liaojinlong\n * @see RequestMapping\n */\n@AnonymousAccess\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@RequestMapping(method = RequestMethod.GET)\npublic @interface AnonymousGetMapping {\n\n    /**\n     * Alias for {@link RequestMapping#name}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String name() default \"\";\n\n    /**\n     * Alias for {@link RequestMapping#value}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] value() default {};\n\n    /**\n     * Alias for {@link RequestMapping#path}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] path() default {};\n\n    /**\n     * Alias for {@link RequestMapping#params}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] params() default {};\n\n    /**\n     * Alias for {@link RequestMapping#headers}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] headers() default {};\n\n    /**\n     * Alias for {@link RequestMapping#consumes}.\n     *\n     * @since 4.3.5\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] consumes() default {};\n\n    /**\n     * Alias for {@link RequestMapping#produces}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] produces() default {};\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java",
    "content": "/*\n * Copyright 2002-2016 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 *      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 me.zhengjie.annotation.rest;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.core.annotation.AliasFor;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n/**\n * Annotation for mapping HTTP {@code PATCH} requests onto specific handler\n * methods.\n * * 支持匿名访问    PatchMapping\n *\n * @author liaojinlong\n * @see AnonymousGetMapping\n * @see AnonymousPostMapping\n * @see AnonymousPutMapping\n * @see AnonymousDeleteMapping\n * @see RequestMapping\n */\n@AnonymousAccess\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@RequestMapping(method = RequestMethod.PATCH)\npublic @interface AnonymousPatchMapping {\n\n    /**\n     * Alias for {@link RequestMapping#name}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String name() default \"\";\n\n    /**\n     * Alias for {@link RequestMapping#value}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] value() default {};\n\n    /**\n     * Alias for {@link RequestMapping#path}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] path() default {};\n\n    /**\n     * Alias for {@link RequestMapping#params}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] params() default {};\n\n    /**\n     * Alias for {@link RequestMapping#headers}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] headers() default {};\n\n    /**\n     * Alias for {@link RequestMapping#consumes}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] consumes() default {};\n\n    /**\n     * Alias for {@link RequestMapping#produces}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] produces() default {};\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java",
    "content": "/*\n * Copyright 2002-2016 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 *      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 me.zhengjie.annotation.rest;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.core.annotation.AliasFor;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n/**\n * Annotation for mapping HTTP {@code POST} requests onto specific handler\n * methods.\n * 支持匿名访问 PostMapping\n *\n * @author liaojinlong\n * @see AnonymousGetMapping\n * @see AnonymousPostMapping\n * @see AnonymousPutMapping\n * @see AnonymousDeleteMapping\n * @see RequestMapping\n */\n@AnonymousAccess\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@RequestMapping(method = RequestMethod.POST)\npublic @interface AnonymousPostMapping {\n\n    /**\n     * Alias for {@link RequestMapping#name}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String name() default \"\";\n\n    /**\n     * Alias for {@link RequestMapping#value}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] value() default {};\n\n    /**\n     * Alias for {@link RequestMapping#path}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] path() default {};\n\n    /**\n     * Alias for {@link RequestMapping#params}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] params() default {};\n\n    /**\n     * Alias for {@link RequestMapping#headers}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] headers() default {};\n\n    /**\n     * Alias for {@link RequestMapping#consumes}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] consumes() default {};\n\n    /**\n     * Alias for {@link RequestMapping#produces}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] produces() default {};\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java",
    "content": "/*\n * Copyright 2002-2016 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 *      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 me.zhengjie.annotation.rest;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.core.annotation.AliasFor;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n/**\n * Annotation for mapping HTTP {@code PUT} requests onto specific handler\n * methods.\n * * 支持匿名访问  PutMapping\n *\n * @author liaojinlong\n * @see AnonymousGetMapping\n * @see AnonymousPostMapping\n * @see AnonymousPutMapping\n * @see AnonymousDeleteMapping\n * @see RequestMapping\n */\n@AnonymousAccess\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@RequestMapping(method = RequestMethod.PUT)\npublic @interface AnonymousPutMapping {\n\n    /**\n     * Alias for {@link RequestMapping#name}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String name() default \"\";\n\n    /**\n     * Alias for {@link RequestMapping#value}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] value() default {};\n\n    /**\n     * Alias for {@link RequestMapping#path}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] path() default {};\n\n    /**\n     * Alias for {@link RequestMapping#params}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] params() default {};\n\n    /**\n     * Alias for {@link RequestMapping#headers}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] headers() default {};\n\n    /**\n     * Alias for {@link RequestMapping#consumes}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] consumes() default {};\n\n    /**\n     * Alias for {@link RequestMapping#produces}.\n     */\n    @AliasFor(annotation = RequestMapping.class)\n    String[] produces() default {};\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.aspect;\n\nimport cn.hutool.core.util.ObjUtil;\nimport com.google.common.collect.ImmutableList;\nimport me.zhengjie.annotation.Limit;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.utils.RequestHolder;\nimport me.zhengjie.utils.StringUtils;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.script.DefaultRedisScript;\nimport org.springframework.data.redis.core.script.RedisScript;\nimport org.springframework.stereotype.Component;\nimport javax.servlet.http.HttpServletRequest;\nimport java.lang.reflect.Method;\n\n/**\n * @author /\n */\n@Aspect\n@Component\npublic class LimitAspect {\n\n    private final RedisTemplate<Object,Object> redisTemplate;\n    private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);\n\n    public LimitAspect(RedisTemplate<Object,Object> redisTemplate) {\n        this.redisTemplate = redisTemplate;\n    }\n\n    @Pointcut(\"@annotation(me.zhengjie.annotation.Limit)\")\n    public void pointcut() {\n    }\n\n    @Around(\"pointcut()\")\n    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {\n        HttpServletRequest request = RequestHolder.getHttpServletRequest();\n        MethodSignature signature = (MethodSignature) joinPoint.getSignature();\n        Method signatureMethod = signature.getMethod();\n        Limit limit = signatureMethod.getAnnotation(Limit.class);\n        LimitType limitType = limit.limitType();\n        String key = limit.key();\n        if (StringUtils.isEmpty(key)) {\n            if (limitType == LimitType.IP) {\n                key = StringUtils.getIp(request);\n            } else {\n                key = signatureMethod.getName();\n            }\n        }\n\n        ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), \"_\", key, \"_\", request.getRequestURI().replace(\"/\",\"_\")));\n\n        String luaScript = buildLuaScript();\n        RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);\n        Long count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());\n        if (ObjUtil.isNotNull(count) && count.intValue() <= limit.count()) {\n            logger.info(\"第{}次访问key为 {}，描述为 [{}] 的接口\", count, keys, limit.name());\n            return joinPoint.proceed();\n        } else {\n            throw new BadRequestException(\"访问次数受限制\");\n        }\n    }\n\n    /**\n     * 限流脚本\n     */\n    private String buildLuaScript() {\n        return \"local c\" +\n                \"\\nc = redis.call('get',KEYS[1])\" +\n                \"\\nif c and tonumber(c) > tonumber(ARGV[1]) then\" +\n                \"\\nreturn c;\" +\n                \"\\nend\" +\n                \"\\nc = redis.call('incr',KEYS[1])\" +\n                \"\\nif tonumber(c) == 1 then\" +\n                \"\\nredis.call('expire',KEYS[1],ARGV[2])\" +\n                \"\\nend\" +\n                \"\\nreturn c;\";\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.aspect;\n\n/**\n * 限流枚举\n * @author /\n */\npublic enum LimitType {\n    // 默认\n    CUSTOMER,\n    //  by ip addr\n    IP\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java",
    "content": "package me.zhengjie.base;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport java.io.Serializable;\nimport java.lang.reflect.Field;\nimport java.sql.Timestamp;\n\n/**\n * @author Zheng Jie\n * @date 2019年10月24日20:48:53\n */\n@Getter\n@Setter\npublic class BaseDTO  implements Serializable {\n\n    @ApiModelProperty(value = \"创建人\")\n    private String createBy;\n\n    @ApiModelProperty(value = \"修改人\")\n    private String updateBy;\n\n    @ApiModelProperty(value = \"创建时间: yyyy-MM-dd HH:mm:ss\", hidden = true)\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd HH:mm:ss\")\n    private Timestamp createTime;\n\n    @ApiModelProperty(value = \"更新时间: yyyy-MM-dd HH:mm:ss\", hidden = true)\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd HH:mm:ss\")\n    private Timestamp updateTime;\n\n\n    @Override\n    public String toString() {\n        ToStringBuilder builder = new ToStringBuilder(this);\n        Field[] fields = this.getClass().getDeclaredFields();\n        try {\n            for (Field f : fields) {\n                f.setAccessible(true);\n                builder.append(f.getName(), f.get(this)).append(\"\\n\");\n            }\n        } catch (Exception e) {\n            builder.append(\"toString builder encounter an error\");\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.base;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.hibernate.annotations.CreationTimestamp;\nimport org.hibernate.annotations.UpdateTimestamp;\nimport org.springframework.data.annotation.CreatedBy;\nimport org.springframework.data.annotation.LastModifiedBy;\nimport org.springframework.data.jpa.domain.support.AuditingEntityListener;\nimport javax.persistence.Column;\nimport javax.persistence.EntityListeners;\nimport javax.persistence.MappedSuperclass;\nimport java.io.Serializable;\nimport java.lang.reflect.Field;\nimport java.sql.Timestamp;\n\n/**\n * 通用字段， is_del 根据需求自行添加\n * @author Zheng Jie\n * @date 2019年10月24日20:46:32\n */\n@Getter\n@Setter\n@MappedSuperclass\n@EntityListeners(AuditingEntityListener.class)\npublic class BaseEntity implements Serializable {\n\n    @CreatedBy\n    @Column(name = \"create_by\", updatable = false)\n    @ApiModelProperty(value = \"创建人\", hidden = true)\n    private String createBy;\n\n    @LastModifiedBy\n    @Column(name = \"update_by\")\n    @ApiModelProperty(value = \"更新人\", hidden = true)\n    private String updateBy;\n\n    @CreationTimestamp\n    @Column(name = \"create_time\", updatable = false)\n    @ApiModelProperty(value = \"创建时间: yyyy-MM-dd HH:mm:ss\", hidden = true)\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd HH:mm:ss\")\n    private Timestamp createTime;\n\n    @UpdateTimestamp\n    @Column(name = \"update_time\")\n    @ApiModelProperty(value = \"更新时间: yyyy-MM-dd HH:mm:ss\", hidden = true)\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd HH:mm:ss\")\n    private Timestamp updateTime;\n\n    /* 分组校验 */\n    public @interface Create {}\n\n    /* 分组校验 */\n    public @interface Update {}\n\n    @Override\n    public String toString() {\n        ToStringBuilder builder = new ToStringBuilder(this);\n        Field[] fields = this.getClass().getDeclaredFields();\n        try {\n            for (Field f : fields) {\n                f.setAccessible(true);\n                builder.append(f.getName(), f.get(this)).append(\"\\n\");\n            }\n        } catch (Exception e) {\n            builder.append(\"toString builder encounter an error\");\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.base;\n\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\npublic interface BaseMapper<D, E> {\n\n    /**\n     * DTO转Entity\n     * @param dto /\n     * @return /\n     */\n    E toEntity(D dto);\n\n    /**\n     * Entity转DTO\n     * @param entity /\n     * @return /\n     */\n    D toDto(E entity);\n\n    /**\n     * DTO集合转Entity集合\n     * @param dtoList /\n     * @return /\n     */\n    List <E> toEntity(List<D> dtoList);\n\n    /**\n     * Entity集合转DTO集合\n     * @param entityList /\n     * @return /\n     */\n    List <D> toDto(List<E> entityList);\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.AsyncConfigurer;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 创建自定义的线程池\n * @author Zheng Jie\n * @description\n * @date 2023-06-08\n **/\n@EnableAsync\n@Configuration\npublic class AsyncExecutor implements AsyncConfigurer {\n\n    public static int corePoolSize;\n\n    public static int maxPoolSize;\n\n    public static int keepAliveSeconds;\n\n    public static int queueCapacity;\n\n    @Value(\"${task.pool.core-pool-size}\")\n    public void setCorePoolSize(int corePoolSize) {\n        AsyncExecutor.corePoolSize = corePoolSize;\n    }\n\n    @Value(\"${task.pool.max-pool-size}\")\n    public void setMaxPoolSize(int maxPoolSize) {\n        AsyncExecutor.maxPoolSize = maxPoolSize;\n    }\n\n    @Value(\"${task.pool.keep-alive-seconds}\")\n    public void setKeepAliveSeconds(int keepAliveSeconds) {\n        AsyncExecutor.keepAliveSeconds = keepAliveSeconds;\n    }\n\n    @Value(\"${task.pool.queue-capacity}\")\n    public void setQueueCapacity(int queueCapacity) {\n        AsyncExecutor.queueCapacity = queueCapacity;\n    }\n\n    /**\n     * 定义自定义异步处理线程池，线程名的标号自增计数器\n     */\n    private static AtomicInteger atomicInteger = new AtomicInteger(1);\n\n    /**\n     * 自定义线程池，用法 @Async\n     * @return Executor\n     */\n    @Override\n    public Executor getAsyncExecutor() {\n        // 自定义工厂\n        ThreadFactory factory = r -> new Thread(r, \"el-async-\" + atomicInteger.getAndIncrement());\n        // 自定义线程池\n        return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds,\n                TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueCapacity), factory,\n                new ThreadPoolExecutor.CallerRunsPolicy());\n    }\n\n    /**\n     * 自定义线程池，用法，注入到类中使用\n     * private ThreadPoolTaskExecutor taskExecutor;\n     * @return ThreadPoolTaskExecutor\n     */\n    @Bean(\"taskAsync\")\n    public ThreadPoolTaskExecutor taskAsync() {\n        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n        executor.setCorePoolSize(2);\n        executor.setMaxPoolSize(4);\n        executor.setQueueCapacity(20);\n        executor.setKeepAliveSeconds(60);\n        executor.setThreadNamePrefix(\"el-task-\");\n        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        return executor;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config;\n\nimport me.zhengjie.utils.SecurityUtils;\nimport org.springframework.data.domain.AuditorAware;\nimport org.springframework.stereotype.Component;\nimport java.util.Optional;\n\n/**\n * @description  : 设置审计\n * @author  : Dong ZhaoYang\n * @date : 2019/10/28\n */\n@Component(\"auditorAware\")\npublic class AuditorConfig implements AuditorAware<String> {\n\n    /**\n     * 返回操作员标志信息\n     *\n     * @return /\n     */\n    @Override\n    public Optional<String> getCurrentAuditor() {\n        try {\n            // 这里应根据实际业务情况获取具体信息\n            return Optional.of(SecurityUtils.getCurrentUsername());\n        }catch (Exception ignored){}\n        // 用户定时任务，或者无Token调用的情况\n        return Optional.of(\"System\");\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config;\n\nimport me.zhengjie.utils.SecurityUtils;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.stereotype.Service;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n */\n@Service(value = \"el\")\npublic class AuthorityConfig {\n\n    /**\n     * 判断接口是否有权限\n     * @param permissions 权限\n     * @return /\n     */\n    public Boolean check(String ...permissions){\n        // 获取当前用户的所有权限\n        List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());\n        // 判断当前用户的所有权限是否包含接口上定义的权限\n        return elPermissions.contains(\"admin\") || Arrays.stream(permissions).anyMatch(elPermissions::contains);\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.p6spy.engine.spy.appender.MessageFormattingStrategy;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author Zheng Jie\n * @description 自定义 p6spy sql输出格式\n * @date 2024-12-26\n **/\n@Slf4j\npublic class CustomP6SpyLogger implements MessageFormattingStrategy {\n\n    // 重置颜色\n    private static final String RESET = \"\\u001B[0m\";\n    // 红色\n    private static final String RED = \"\\u001B[31m\";\n    // 绿色\n    private static final String GREEN = \"\\u001B[32m\";\n\n    /**\n     * 格式化 sql\n     * @param connectionId 连接id\n     * @param now 当前时间\n     * @param elapsed 执行时长\n     * @param category sql分类\n     * @param prepared 预编译sql\n     * @param sql 执行sql\n     * @param url 数据库连接url\n     * @return 格式化后的sql\n     */\n    @Override\n    public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {\n        // 去掉换行和多余空格\n        if(StrUtil.isNotBlank(sql)){\n            sql = sql.replaceAll(\"\\\\s+\", \" \").trim();\n        }\n\n        // 格式化并加上颜色\n        return String.format(\n                \"%s[Time: %dms]%s - %s%s%s;\",\n                GREEN, elapsed, RESET, RED, sql, RESET\n        );\n    }\n}\n\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.JSONFactory;\nimport com.alibaba.fastjson2.JSONWriter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.codec.digest.MurmurHash3;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.annotation.CachingConfigurerSupport;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.cache.interceptor.CacheErrorHandler;\nimport org.springframework.cache.interceptor.KeyGenerator;\nimport org.springframework.cache.interceptor.SimpleCacheErrorHandler;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.cache.RedisCacheConfiguration;\nimport org.springframework.data.redis.cache.RedisCacheManager;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.serializer.RedisSerializationContext;\nimport org.springframework.data.redis.serializer.RedisSerializer;\nimport org.springframework.data.redis.serializer.SerializationException;\nimport org.springframework.data.redis.serializer.StringRedisSerializer;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author Zheng Jie\n * @date 2025-01-13\n */\n@Slf4j\n@Configuration\n@EnableCaching\n@AutoConfigureBefore(RedisAutoConfiguration.class)\npublic class RedisConfiguration extends CachingConfigurerSupport {\n\n    // 自动识别json对象白名单配置（仅允许解析的包名，范围越小越安全）\n    private static final String[] WHITELIST_STR = {\"me.zhengjie\" };\n\n    /**\n     *  设置 redis 数据默认过期时间，默认2小时\n     *  设置@cacheable 序列化方式\n     */\n    @Bean\n    public RedisCacheConfiguration redisCacheConfiguration(){\n        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);\n        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();\n        configuration = configuration.serializeValuesWith(RedisSerializationContext.\n                SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));\n        return configuration;\n    }\n\n    @Bean(name = \"redisTemplate\")\n    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {\n        RedisTemplate<Object, Object> template = new RedisTemplate<>();\n        // 指定 key 和 value 的序列化方案\n        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);\n        // value值的序列化采用fastJsonRedisSerializer\n        template.setValueSerializer(fastJsonRedisSerializer);\n        template.setHashValueSerializer(fastJsonRedisSerializer);\n        // 设置fastJson的序列化白名单\n        for (String pack : WHITELIST_STR) {\n            JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack);\n        }\n        // key的序列化采用StringRedisSerializer\n        template.setKeySerializer(new StringRedisSerializer());\n        template.setHashKeySerializer(new StringRedisSerializer());\n        template.setConnectionFactory(redisConnectionFactory);\n        return template;\n    }\n\n    /**\n     * 缓存管理器\n     * @param redisConnectionFactory /\n     * @return 缓存管理器\n     */\n    @Bean\n    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {\n        RedisCacheConfiguration config = redisCacheConfiguration();\n        return RedisCacheManager.builder(redisConnectionFactory)\n                .cacheDefaults(config)\n                .build();\n    }\n\n    /**\n     * 自定义缓存key生成策略\n     */\n    @Bean\n    public KeyGenerator keyGenerator() {\n        return (target, method, params) -> {\n            Map<String,Object> container = new HashMap<>(8);\n            Class<?> targetClassClass = target.getClass();\n            // 类地址\n            container.put(\"class\",targetClassClass.toGenericString());\n            // 方法名称\n            container.put(\"methodName\",method.getName());\n            // 包名称\n            container.put(\"package\",targetClassClass.getPackage());\n            // 参数列表\n            for (int i = 0; i < params.length; i++) {\n                container.put(String.valueOf(i),params[i]);\n            }\n            // 转为JSON字符串\n            String jsonString = JSON.toJSONString(container);\n            // 使用 MurmurHash 生成 hash\n            return Integer.toHexString(MurmurHash3.hash32x86(jsonString.getBytes()));\n        };\n    }\n\n    @Bean\n    @SuppressWarnings({\"unchecked\",\"all\"})\n    public CacheErrorHandler errorHandler() {\n        return new SimpleCacheErrorHandler() {\n            @Override\n            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {\n                // 处理缓存读取错误\n                log.error(\"Cache Get Error: {}\",exception.getMessage());\n            }\n            @Override\n            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {\n                // 处理缓存写入错误\n                log.error(\"Cache Put Error: {}\",exception.getMessage());\n            }\n            @Override\n            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {\n                // 处理缓存删除错误\n                log.error(\"Cache Evict Error: {}\",exception.getMessage());\n            }\n            @Override\n            public void handleCacheClearError(RuntimeException exception, Cache cache) {\n                // 处理缓存清除错误\n                log.error(\"Cache Clear Error: {}\",exception.getMessage());\n            }\n        };\n    }\n\n    /**\n     * Value 序列化\n     *\n     * @param <T>\n     * @author /\n     */\n    static class FastJsonRedisSerializer<T> implements RedisSerializer<T> {\n\n        private final Class<T> clazz;\n\n        FastJsonRedisSerializer(Class<T> clazz) {\n            super();\n            this.clazz = clazz;\n        }\n\n        @Override\n        public byte[] serialize(T t) throws SerializationException\n        {\n            if (t == null) {\n                return new byte[0];\n            }\n            return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8);\n        }\n\n        @Override\n        public T deserialize(byte[] bytes) throws SerializationException\n        {\n            if (bytes == null || bytes.length == 0) {\n                return null;\n            }\n            String str = new String(bytes, StandardCharsets.UTF_8);\n            return JSON.parseObject(str, clazz);\n        }\n\n    }\n}"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.Data;\nimport org.redisson.Redisson;\nimport org.redisson.api.RedissonClient;\nimport org.redisson.config.Config;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Data\n@Configuration\n@AutoConfigureBefore(RedisAutoConfiguration.class)\npublic class RedissonConfiguration {\n\n    @Value(\"${spring.redis.host}\")\n    private String redisHost;\n\n    @Value(\"${spring.redis.port}\")\n    private int redisPort;\n\n    @Value(\"${spring.redis.database}\")\n    private int redisDatabase;\n\n    @Value(\"${spring.redis.password:}\")\n    private String redisPassword;\n\n    @Value(\"${spring.redis.timeout:5000}\")\n    private int timeout;\n\n    @Value(\"${spring.redis.lettuce.pool.max-active:64}\")\n    private int connectionPoolSize;\n\n    @Value(\"${spring.redis.lettuce.pool.min-idle:16}\")\n    private int connectionMinimumIdleSize;\n\n    @Bean\n    public RedissonClient redissonClient() {\n        Config config = new Config();\n        config.useSingleServer()\n                .setAddress(\"redis://\" + redisHost + \":\" + redisPort)\n                .setDatabase(redisDatabase)\n                .setTimeout(timeout)\n                .setConnectionPoolSize(connectionPoolSize)\n                .setConnectionMinimumIdleSize(connectionMinimumIdleSize);\n        if(StrUtil.isNotBlank(redisPassword)){\n            config.useSingleServer().setPassword(redisPassword);\n        }\n        return Redisson.create(config);\n    }\n}\n\n\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java",
    "content": "package me.zhengjie.config;\n\nimport com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;\nimport com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;\nimport com.alibaba.druid.util.Utils;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport javax.servlet.*;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @author Zheng Jie\n * @description\n * @date 2025-01-11\n **/\n@Configuration\n@SuppressWarnings({\"unchecked\",\"all\"})\n@ConditionalOnWebApplication\n@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)\n@ConditionalOnProperty(name = \"spring.datasource.druid.stat-view-servlet.enabled\",\n        havingValue = \"true\", matchIfMissing = true)\npublic class RemoveDruidAdConfig {\n\n    /**\n     * 方法名: removeDruidAdFilterRegistrationBean\n     * 方法描述 除去页面底部的广告\n     * @param properties com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties\n     * @return org.springframework.boot.web.servlet.FilterRegistrationBean\n     */\n    @Bean\n    public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {\n\n        // 获取web监控页面的参数\n        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();\n        // 提取common.js的配置路径\n        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : \"/druid/*\";\n        String commonJsPattern = pattern.replaceAll(\"\\\\*\", \"js/common.js\");\n\n        final String filePath = \"support/http/resources/js/common.js\";\n\n        //创建filter进行过滤\n        Filter filter = new Filter() {\n            @Override\n            public void init(FilterConfig filterConfig) throws ServletException {}\n\n            @Override\n            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n                HttpServletRequest httpRequest = (HttpServletRequest) request;\n                HttpServletResponse httpResponse = (HttpServletResponse) response;\n                if (httpRequest.getRequestURI().endsWith(\"js/common.js\")) {\n                    // 获取common.js\n                    String text = Utils.readFromResource(filePath);\n                    // 正则替换banner, 除去底部的广告信息\n                    text = text.replaceAll(\"<a.*?druid_banner\\\"></a><br/>\", \"\");\n                    text = text.replaceAll(\"powered by.*?shrek.wang</a>\", \"\");\n                    httpResponse.setContentType(\"application/javascript\");\n                    httpResponse.setCharacterEncoding(\"UTF-8\");\n                    httpResponse.getWriter().write(text);\n                } else {\n                    chain.doFilter(request, response);\n                }\n            }\n            @Override\n            public void destroy() {}\n        };\n        FilterRegistrationBean registrationBean = new FilterRegistrationBean();\n        registrationBean.setFilter(filter);\n        registrationBean.addUrlPatterns(commonJsPattern);\n        return registrationBean;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config.properties;\n\nimport lombok.Data;\nimport me.zhengjie.utils.ElConstant;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @author Zheng Jie\n */\n@Data\n@Configuration\n@ConfigurationProperties(prefix = \"file\")\npublic class FileProperties {\n\n    /** 文件大小限制 */\n    private Long maxSize;\n\n    /** 头像大小限制 */\n    private Long avatarMaxSize;\n\n    private ElPath mac;\n\n    private ElPath linux;\n\n    private ElPath windows;\n\n    public ElPath getPath(){\n        String os = System.getProperty(\"os.name\");\n        if(os.toLowerCase().startsWith(ElConstant.WIN)) {\n            return windows;\n        } else if(os.toLowerCase().startsWith(ElConstant.MAC)){\n            return mac;\n        }\n        return linux;\n    }\n\n    @Data\n    public static class ElPath{\n\n        private String path;\n\n        private String avatar;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config.properties;\n\nimport lombok.Data;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author Zheng Jie\n * @website https://eladmin.vip\n * @description\n * @date 2020-05-18\n **/\n@Data\n@Component\npublic class RsaProperties {\n\n    public static String privateKey;\n\n    @Value(\"${rsa.private_key}\")\n    public void setPrivateKey(String privateKey) {\n        RsaProperties.privateKey = privateKey;\n    }\n}"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config.webConfig;\n\nimport com.alibaba.fastjson2.JSONWriter;\nimport com.alibaba.fastjson2.support.config.FastJsonConfig;\nimport com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;\nimport me.zhengjie.config.properties.FileProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.converter.HttpMessageConverter;\nimport org.springframework.web.cors.CorsConfiguration;\nimport org.springframework.web.cors.UrlBasedCorsConfigurationSource;\nimport org.springframework.web.filter.CorsFilter;\nimport org.springframework.web.servlet.config.annotation.EnableWebMvc;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * WebMvcConfigurer\n *\n * @author Zheng Jie\n * @date 2018-11-30\n */\n@Configuration\n@EnableWebMvc\npublic class ConfigurerAdapter implements WebMvcConfigurer {\n\n    /** 文件配置 */\n    private final FileProperties properties;\n\n    public ConfigurerAdapter(FileProperties properties) {\n        this.properties = properties;\n    }\n\n    @Bean\n    public CorsFilter corsFilter() {\n        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n        CorsConfiguration config = new CorsConfiguration();\n        config.setAllowCredentials(true);\n        config.addAllowedOriginPattern(\"*\");\n        config.addAllowedHeader(\"*\");\n        config.addAllowedMethod(\"*\");\n        source.registerCorsConfiguration(\"/**\", config);\n        return new CorsFilter(source);\n    }\n\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n        FileProperties.ElPath path = properties.getPath();\n        String avatarUtl = \"file:\" + path.getAvatar().replace(\"\\\\\",\"/\");\n        String pathUtl = \"file:\" + path.getPath().replace(\"\\\\\",\"/\");\n        registry.addResourceHandler(\"/avatar/**\").addResourceLocations(avatarUtl).setCachePeriod(0);\n        registry.addResourceHandler(\"/file/**\").addResourceLocations(pathUtl).setCachePeriod(0);\n        registry.addResourceHandler(\"/**\").addResourceLocations(\"classpath:/META-INF/resources/\").setCachePeriod(0);\n    }\n\n    @Override\n    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {\n        // 配置 FastJsonHttpMessageConverter\n        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();\n        List<MediaType> supportMediaTypeList = new ArrayList<>();\n        supportMediaTypeList.add(MediaType.APPLICATION_JSON);\n        FastJsonConfig config = new FastJsonConfig();\n        config.setDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        // 开启引用检测\n        config.setWriterFeatures(JSONWriter.Feature.ReferenceDetection);\n        converter.setFastJsonConfig(config);\n        converter.setSupportedMediaTypes(supportMediaTypeList);\n        converter.setDefaultCharset(StandardCharsets.UTF_8);\n        converters.add(converter);\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config.webConfig;\n\nimport org.springframework.boot.web.servlet.MultipartConfigFactory;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport javax.servlet.MultipartConfigElement;\nimport java.io.File;\n\n/**\n * @date 2018-12-28\n * @author <a href=\"https://blog.csdn.net/llibin1024530411/article/details/79474953\">...</a>\n */\n@Configuration\npublic class MultipartConfig {\n\n    /**\n     * 文件上传临时路径\n     */\n    @Bean\n    MultipartConfigElement multipartConfigElement() {\n        MultipartConfigFactory factory = new MultipartConfigFactory();\n        String location = System.getProperty(\"user.home\") + \"/.eladmin/file/tmp\";\n        File tmpFile = new File(location);\n        if (!tmpFile.exists()) {\n            if (!tmpFile.mkdirs()) {\n                System.out.println(\"create was not successful.\");\n            }\n        }\n        factory.setLocation(location);\n        return factory.createMultipartConfig();\n    }\n}"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java",
    "content": "/*\n *  Copyright 2019-2023 Zheng Jie\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 me.zhengjie.config.webConfig;\n\nimport org.apache.catalina.connector.Connector;\nimport org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @author bearBoy80\n */\n@Configuration(proxyBeanMethods = false)\npublic class QueryCustomizer implements TomcatConnectorCustomizer {\n    @Override\n    public void customize(Connector connector) {\n        connector.setProperty(\"relaxedQueryChars\", \"[]{}\");\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config.webConfig;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.utils.AnonTagUtils;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.beans.factory.config.BeanPostProcessor;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.util.ReflectionUtils;\nimport org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;\nimport springfox.documentation.builders.ApiInfoBuilder;\nimport springfox.documentation.builders.PathSelectors;\nimport springfox.documentation.service.ApiInfo;\nimport springfox.documentation.service.ApiKey;\nimport springfox.documentation.service.AuthorizationScope;\nimport springfox.documentation.service.SecurityReference;\nimport springfox.documentation.service.SecurityScheme;\nimport springfox.documentation.spi.DocumentationType;\nimport springfox.documentation.spi.service.contexts.SecurityContext;\nimport springfox.documentation.spring.web.plugins.Docket;\nimport springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;\nimport springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;\nimport springfox.documentation.swagger2.annotations.EnableSwagger2;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * api页面 /doc.html\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Configuration\n@EnableSwagger2\n@RequiredArgsConstructor\npublic class SwaggerConfig {\n\n    @Value(\"${jwt.header}\")\n    private String tokenHeader;\n\n    @Value(\"${swagger.enabled}\")\n    private Boolean enabled;\n\n    @Value(\"${server.servlet.context-path:}\")\n    private String apiPath;\n\n    private final ApplicationContext applicationContext;\n\n    @Bean\n    @SuppressWarnings({\"unchecked\",\"all\"})\n    public Docket createRestApi() {\n        return new Docket(DocumentationType.SWAGGER_2)\n                .enable(enabled)\n                .pathMapping(\"/\")\n                .apiInfo(apiInfo())\n                .select()\n                .paths(PathSelectors.regex(\"^(?!/error).*\"))\n                .paths(PathSelectors.any())\n                .build()\n                //添加登陆认证\n                .securitySchemes(securitySchemes())\n                .securityContexts(securityContexts());\n    }\n\n    private ApiInfo apiInfo() {\n        return new ApiInfoBuilder()\n                .description(\"一个简单且易上手的 Spring boot 后台管理框架\")\n                .title(\"ELADMIN 接口文档\")\n                .version(\"2.7\")\n                .build();\n    }\n\n    private List<SecurityScheme> securitySchemes() {\n        //设置请求头信息\n        List<SecurityScheme> securitySchemes = new ArrayList<>();\n        ApiKey apiKey = new ApiKey(tokenHeader, tokenHeader, \"header\");\n        securitySchemes.add(apiKey);\n        return securitySchemes;\n    }\n\n    private List<SecurityContext> securityContexts() {\n        //设置需要登录认证的路径\n        List<SecurityContext> securityContexts = new ArrayList<>();\n        securityContexts.add(getContextByPath());\n        return securityContexts;\n    }\n\n    private SecurityContext getContextByPath() {\n        Set<String> urls = AnonTagUtils.getAllAnonymousUrl(applicationContext);\n        urls = urls.stream().filter(url -> !url.equals(\"/\")).collect(Collectors.toSet());\n        String regExp = \"^(?!\" + apiPath + String.join(\"|\" + apiPath, urls) + \").*$\";\n        return SecurityContext.builder()\n                .securityReferences(defaultAuth())\n                .operationSelector(o->o.requestMappingPattern()\n                        // 排除不需要认证的接口\n                        .matches(regExp))\n                .build();\n    }\n\n    private List<SecurityReference> defaultAuth() {\n        List<SecurityReference> securityReferences = new ArrayList<>();\n        AuthorizationScope authorizationScope = new AuthorizationScope(\"global\", \"accessEverything\");\n        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];\n        authorizationScopes[0] = authorizationScope;\n        securityReferences.add(new SecurityReference(tokenHeader, authorizationScopes));\n        return securityReferences;\n    }\n\n    /**\n     * 解决Springfox与SpringBoot集成后，WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider冲突问题\n     * @return /\n     */\n    @Bean\n    @SuppressWarnings({\"all\"})\n    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {\n        return new BeanPostProcessor() {\n\n            @Override\n            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {\n                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));\n                }\n                return bean;\n            }\n\n            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {\n                List<T> filteredMappings = mappings.stream()\n                        .filter(mapping -> mapping.getPatternParser() == null)\n                        .collect(Collectors.toList());\n                mappings.clear();\n                mappings.addAll(filteredMappings);\n            }\n\n            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {\n                Field field = ReflectionUtils.findField(bean.getClass(), \"handlerMappings\");\n                if (field != null) {\n                    field.setAccessible(true);\n                    try {\n                        return (List<RequestMappingInfoHandlerMapping>) field.get(bean);\n                    } catch (IllegalAccessException e) {\n                        throw new IllegalStateException(\"Failed to access handlerMappings field\", e);\n                    }\n                }\n                return Collections.emptyList();\n            }\n        };\n    }\n}\n\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java",
    "content": "package me.zhengjie.config.webConfig;\n\nimport cn.hutool.core.collection.CollUtil;\nimport com.fasterxml.classmate.TypeResolver;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.data.domain.Pageable;\nimport springfox.documentation.schema.AlternateTypeRule;\nimport springfox.documentation.schema.AlternateTypeRuleConvention;\n\nimport java.util.List;\n\nimport static springfox.documentation.schema.AlternateTypeRules.newRule;\n\n/**\n * 将Pageable转换展示在swagger中\n */\n@Configuration\npublic class SwaggerDataConfig {\n\n    @Bean\n    public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {\n        return new AlternateTypeRuleConvention() {\n            @Override\n            public int getOrder() {\n                return Ordered.HIGHEST_PRECEDENCE;\n            }\n\n            @Override\n            public List<AlternateTypeRule> rules() {\n                return CollUtil.newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));\n            }\n        };\n    }\n\n    @ApiModel\n    @Data\n    private static class Page {\n        @ApiModelProperty(\"页码 (0..N)\")\n        private Integer page;\n\n        @ApiModelProperty(\"每页显示的数目\")\n        private Integer size;\n\n        @ApiModelProperty(\"以下列格式排序标准：property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件：如：id,asc\")\n        private List<String> sort;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.config.webConfig;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.socket.server.standard.ServerEndpointExporter;\n\n/**\n * @author ZhangHouYing\n * @date 2019-08-24 15:44\n */\n@Configuration\npublic class WebSocketConfig {\n\n\t@Bean\n\tpublic ServerEndpointExporter serverEndpointExporter() {\n\t\treturn new ServerEndpointExporter();\n\t}\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.exception;\n\nimport lombok.Getter;\nimport org.springframework.http.HttpStatus;\nimport static org.springframework.http.HttpStatus.BAD_REQUEST;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n * 统一异常处理\n */\n@Getter\npublic class BadRequestException extends RuntimeException{\n\n    private Integer status = BAD_REQUEST.value();\n\n    public BadRequestException(String msg){\n        super(msg);\n    }\n\n    public BadRequestException(HttpStatus status,String msg){\n        super(msg);\n        this.status = status.value();\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.exception;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\npublic class EntityExistException extends RuntimeException {\n\n    public EntityExistException(Class clazz, String field, String val) {\n        super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));\n    }\n\n    private static String generateMessage(String entity, String field, String val) {\n        return StringUtils.capitalize(entity)\n                + \" with \" + field + \" \"+ val + \" existed\";\n    }\n}"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.exception;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\npublic class EntityNotFoundException extends RuntimeException {\n\n    public EntityNotFoundException(Class clazz, String field, String val) {\n        super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));\n    }\n\n    private static String generateMessage(String entity, String field, String val) {\n        return StringUtils.capitalize(entity)\n                + \" with \" + field + \" \"+ val + \" does not exist\";\n    }\n}"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.exception.handler;\n\nimport lombok.Data;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Data\npublic class ApiError {\n\n    private Integer status = 400;\n    private Long timestamp;\n    private String message;\n\n    private ApiError() {\n        timestamp = System.currentTimeMillis();\n    }\n\n    public static ApiError error(String message){\n        ApiError apiError = new ApiError();\n        apiError.setMessage(message);\n        return apiError;\n    }\n\n    public static ApiError error(Integer status, String message){\n        ApiError apiError = new ApiError();\n        apiError.setStatus(status);\n        apiError.setMessage(message);\n        return apiError;\n    }\n}\n\n\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.exception.handler;\n\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.exception.EntityExistException;\nimport me.zhengjie.exception.EntityNotFoundException;\nimport me.zhengjie.utils.ThrowableUtil;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.authentication.BadCredentialsException;\nimport org.springframework.validation.FieldError;\nimport org.springframework.validation.ObjectError;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport static org.springframework.http.HttpStatus.*;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Slf4j\n@RestControllerAdvice\npublic class GlobalExceptionHandler {\n\n    /**\n     * 处理所有不可知的异常\n     */\n    @ExceptionHandler(Throwable.class)\n    public ResponseEntity<ApiError> handleException(Throwable e){\n        // 打印堆栈信息\n        log.error(ThrowableUtil.getStackTrace(e));\n        return buildResponseEntity(ApiError.error(e.getMessage()));\n    }\n\n    /**\n     * BadCredentialsException\n     */\n    @ExceptionHandler(BadCredentialsException.class)\n    public ResponseEntity<ApiError> badCredentialsException(BadCredentialsException e){\n        // 打印堆栈信息\n        String message = \"坏的凭证\".equals(e.getMessage()) ? \"用户名或密码不正确\" : e.getMessage();\n        log.error(message);\n        return buildResponseEntity(ApiError.error(message));\n    }\n\n    /**\n     * 处理自定义异常\n     */\n\t@ExceptionHandler(value = BadRequestException.class)\n\tpublic ResponseEntity<ApiError> badRequestException(BadRequestException e) {\n        // 打印堆栈信息\n        log.error(ThrowableUtil.getStackTrace(e));\n        return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage()));\n\t}\n\n    /**\n     * 处理 EntityExist\n     */\n    @ExceptionHandler(value = EntityExistException.class)\n    public ResponseEntity<ApiError> entityExistException(EntityExistException e) {\n        // 打印堆栈信息\n        log.error(ThrowableUtil.getStackTrace(e));\n        return buildResponseEntity(ApiError.error(e.getMessage()));\n    }\n\n    /**\n     * 处理 EntityNotFound\n     */\n    @ExceptionHandler(value = EntityNotFoundException.class)\n    public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) {\n        // 打印堆栈信息\n        log.error(ThrowableUtil.getStackTrace(e));\n        return buildResponseEntity(ApiError.error(NOT_FOUND.value(),e.getMessage()));\n    }\n\n    /**\n     * 处理所有接口数据验证异常\n     */\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){\n        // 打印堆栈信息\n        log.error(ThrowableUtil.getStackTrace(e));\n        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);\n        String message = objectError.getDefaultMessage();\n        if (objectError instanceof FieldError) {\n            message = ((FieldError) objectError).getField() + \": \" + message;\n        }\n        return buildResponseEntity(ApiError.error(message));\n    }\n\n    /**\n     * 统一返回\n     */\n    private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {\n        return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java",
    "content": "/*\n * Copyright 2019-2020 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 *      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 me.zhengjie.utils;\n\nimport me.zhengjie.annotation.rest.AnonymousAccess;\nimport me.zhengjie.utils.enums.RequestMethodEnum;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.web.bind.annotation.RequestMethod;\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 java.util.*;\n\n/**\n * @author Zheng Jie\n * @description 匿名标记工具\n * @date 2025-01-13\n **/\npublic class AnonTagUtils {\n\n    /**\n     * 获取匿名标记的URL\n     * @param applicationContext /\n     * @return /\n     */\n    public static Map<String, Set<String>> getAnonymousUrl(ApplicationContext applicationContext){\n        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean(\"requestMappingHandlerMapping\");\n        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();\n        Map<String, Set<String>> anonymousUrls = new HashMap<>(8);\n        // 获取匿名标记\n        Set<String> get = new HashSet<>();\n        Set<String> post = new HashSet<>();\n        Set<String> put = new HashSet<>();\n        Set<String> patch = new HashSet<>();\n        Set<String> delete = new HashSet<>();\n        Set<String> all = new HashSet<>();\n        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {\n            HandlerMethod handlerMethod = infoEntry.getValue();\n            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);\n            if (null != anonymousAccess) {\n                List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());\n                RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());\n                if (infoEntry.getKey().getPatternsCondition()!=null) {\n                    switch (Objects.requireNonNull(request)) {\n                        case GET:\n                            get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());\n                            break;\n                        case POST:\n                            post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());\n                            break;\n                        case PUT:\n                            put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());\n                            break;\n                        case PATCH:\n                            patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());\n                            break;\n                        case DELETE:\n                            delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());\n                            break;\n                        default:\n                            all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());\n                            break;\n                    }\n                }\n            }\n        }\n        anonymousUrls.put(RequestMethodEnum.GET.getType(), get);\n        anonymousUrls.put(RequestMethodEnum.POST.getType(), post);\n        anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);\n        anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);\n        anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);\n        anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);\n        return anonymousUrls;\n    }\n\n    /**\n     * 获取所有匿名标记的URL\n     * @param applicationContext /\n     * @return /\n     */\n    public static Set<String> getAllAnonymousUrl(ApplicationContext applicationContext){\n        Set<String> allUrl = new HashSet<>();\n        Map<String, Set<String>> anonymousUrls = getAnonymousUrl(applicationContext);\n        for (String key : anonymousUrls.keySet()) {\n            allUrl.addAll(anonymousUrls.get(key));\n        }\n        return allUrl;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java",
    "content": "/*\n * Copyright 2019-2020 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 *      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 me.zhengjie.utils;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\n\n/**\n * @author Zheng Jie\n * @description 计算类\n * @date 2024-12-27\n **/\npublic class BigDecimalUtils {\n\n    /**\n     * 将对象转换为 BigDecimal\n     * @param obj 输入对象\n     * @return 转换后的 BigDecimal\n     */\n    private static BigDecimal toBigDecimal(Object obj) {\n        if (obj instanceof BigDecimal) {\n            return (BigDecimal) obj;\n        } else if (obj instanceof Long) {\n            return BigDecimal.valueOf((Long) obj);\n        } else if (obj instanceof Integer) {\n            return BigDecimal.valueOf((Integer) obj);\n        } else if (obj instanceof Double) {\n            return new BigDecimal(String.valueOf(obj));\n        } else {\n            throw new IllegalArgumentException(\"Unsupported type\");\n        }\n    }\n\n    /**\n     * 加法\n     * @param a 加数\n     * @param b 加数\n     * @return 两个加数的和，保留两位小数\n     */\n    public static BigDecimal add(Object a, Object b) {\n        BigDecimal bdA = toBigDecimal(a);\n        BigDecimal bdB = toBigDecimal(b);\n        return bdA.add(bdB).setScale(2, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 减法\n     * @param a 被减数\n     * @param b 减数\n     * @return 两数的差，保留两位小数\n     */\n    public static BigDecimal subtract(Object a, Object b) {\n        BigDecimal bdA = toBigDecimal(a);\n        BigDecimal bdB = toBigDecimal(b);\n        return bdA.subtract(bdB).setScale(2, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 乘法\n     * @param a 乘数\n     * @param b 乘数\n     * @return 两个乘数的积，保留两位小数\n     */\n    public static BigDecimal multiply(Object a, Object b) {\n        BigDecimal bdA = toBigDecimal(a);\n        BigDecimal bdB = toBigDecimal(b);\n        return bdA.multiply(bdB).setScale(2, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 除法\n     * @param a 被除数\n     * @param b 除数\n     * @return 两数的商，保留两位小数\n     */\n    public static BigDecimal divide(Object a, Object b) {\n        BigDecimal bdA = toBigDecimal(a);\n        BigDecimal bdB = toBigDecimal(b);\n        return bdA.divide(bdB, 2, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 除法\n     * @param a 被除数\n     * @param b 除数\n     * @param scale 保留小数位数\n     * @return 两数的商，保留两位小数\n     */\n    public static BigDecimal divide(Object a, Object b, int scale) {\n        BigDecimal bdA = toBigDecimal(a);\n        BigDecimal bdB = toBigDecimal(b);\n        return bdA.divide(bdB, scale, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 分转元\n     * @param obj 分的金额\n     * @return 转换后的元，保留两位小数\n     */\n    public static BigDecimal centsToYuan(Object obj) {\n        BigDecimal cents = toBigDecimal(obj);\n        return cents.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 元转分\n     * @param obj 元的金额\n     * @return 转换后的分\n     */\n    public static Long yuanToCents(Object obj) {\n        BigDecimal yuan = toBigDecimal(obj);\n        return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();\n    }\n    \n    public static void main(String[] args) {\n        BigDecimal num1 = new BigDecimal(\"10.123\");\n        BigDecimal num2 = new BigDecimal(\"2.456\");\n\n        System.out.println(\"加法结果: \" + add(num1, num2));\n        System.out.println(\"减法结果: \" + subtract(num1, num2));\n        System.out.println(\"乘法结果: \" + multiply(num1, num2));\n        System.out.println(\"除法结果: \" + divide(num1, num2));\n\n        Long cents = 12345L;\n        System.out.println(\"分转元结果: \" + centsToYuan(cents));\n\n        BigDecimal yuan = new BigDecimal(\"123.45\");\n        System.out.println(\"元转分结果: \" + yuanToCents(yuan));\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java",
    "content": "/*\n * Copyright 2019-2020 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 *      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 me.zhengjie.utils;\n\n/**\n * @author liaojinlong\n * @date 2020/6/11 15:49\n * @description 关于缓存的Key集合\n */\npublic interface CacheKey {\n\n    /**\n     * 用户\n     */\n    String USER_ID = \"user::id:\";\n\n    /**\n     * 数据\n     */\n    String DATA_USER = \"data::user:\";\n\n    /**\n     * 菜单\n     */\n    String MENU_ID = \"menu::id:\";\n    String MENU_USER = \"menu::user:\";\n\n    /**\n     * 角色授权\n     */\n    String ROLE_AUTH = \"role::auth:\";\n    String ROLE_USER = \"role::user:\";\n\n    /**\n     * 角色信息\n     */\n    String ROLE_ID = \"role::id:\";\n\n    /**\n     * 部门\n     */\n    String DEPT_ID = \"dept::id:\";\n\n    /**\n     * 岗位\n     */\n    String JOB_ID = \"job::id:\";\n\n    /**\n     * 数据字典\n     */\n    String DICT_NAME = \"dict::name:\";\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport java.io.Closeable;\n\n/**\n * @author Zheng Jie\n * @website https://eladmin.vip\n * @description 用于关闭各种连接，缺啥补啥\n * @date 2021-03-05\n **/\npublic class CloseUtil {\n\n    public static void close(Closeable closeable) {\n        if (null != closeable) {\n            try {\n                closeable.close();\n            } catch (Exception e) {\n                // 静默关闭\n            }\n        }\n    }\n\n    public static void close(AutoCloseable closeable) {\n        if (null != closeable) {\n            try {\n                closeable.close();\n            } catch (Exception e) {\n                // 静默关闭\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java",
    "content": "/*\n * Copyright 2019-2020 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 *      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 me.zhengjie.utils;\n\nimport java.time.*;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Date;\n\n/**\n * @author: liaojinlong\n * @date: 2020/6/11 16:28\n * @apiNote: JDK 8  新日期类 格式化与字符串转换 工具类\n */\npublic class DateUtil {\n\n    public static final DateTimeFormatter DFY_MD_HMS = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");\n    public static final DateTimeFormatter DFY_MD = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\");\n\n    /**\n     * LocalDateTime 转时间戳\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public static Long getTimeStamp(LocalDateTime localDateTime) {\n        return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond();\n    }\n\n    /**\n     * 时间戳转LocalDateTime\n     *\n     * @param timeStamp /\n     * @return /\n     */\n    public static LocalDateTime fromTimeStamp(Long timeStamp) {\n        return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset());\n    }\n\n    /**\n     * LocalDateTime 转 Date\n     * Jdk8 后 不推荐使用 {@link Date} Date\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public static Date toDate(LocalDateTime localDateTime) {\n        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());\n    }\n\n    /**\n     * LocalDate 转 Date\n     * Jdk8 后 不推荐使用 {@link Date} Date\n     *\n     * @param localDate /\n     * @return /\n     */\n    public static Date toDate(LocalDate localDate) {\n        return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault())));\n    }\n\n\n    /**\n     * Date转 LocalDateTime\n     * Jdk8 后 不推荐使用 {@link Date} Date\n     *\n     * @param date /\n     * @return /\n     */\n    public static LocalDateTime toLocalDateTime(Date date) {\n        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());\n    }\n\n    /**\n     * 日期 格式化\n     *\n     * @param localDateTime /\n     * @param patten /\n     * @return /\n     */\n    public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) {\n        DateTimeFormatter df = DateTimeFormatter.ofPattern(patten);\n        return df.format(localDateTime);\n    }\n\n    /**\n     * 日期 格式化\n     *\n     * @param localDateTime /\n     * @param df /\n     * @return /\n     */\n    public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) {\n        return df.format(localDateTime);\n    }\n\n    /**\n     * 日期格式化 yyyy-MM-dd HH:mm:ss\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) {\n        return DFY_MD_HMS.format(localDateTime);\n    }\n\n    /**\n     * 日期格式化 yyyy-MM-dd\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public String localDateTimeFormatyMd(LocalDateTime localDateTime) {\n        return DFY_MD.format(localDateTime);\n    }\n\n    /**\n     * 字符串转 LocalDateTime ，字符串格式 yyyy-MM-dd\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) {\n        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);\n        return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));\n    }\n\n    /**\n     * 字符串转 LocalDateTime ，字符串格式 yyyy-MM-dd\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) {\n        return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));\n    }\n\n    /**\n     * 字符串转 LocalDateTime ，字符串格式 yyyy-MM-dd HH:mm:ss\n     *\n     * @param localDateTime /\n     * @return /\n     */\n    public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) {\n        return LocalDateTime.from(DFY_MD_HMS.parse(localDateTime));\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\n/**\n * 常用静态常量\n *\n * @author Zheng Jie\n * @date 2018-12-26\n */\npublic class ElConstant {\n    /**\n     * win 系统\n     */\n    public static final String WIN = \"win\";\n\n    /**\n     * mac 系统\n     */\n    public static final String MAC = \"mac\";\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.SecretKey;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.DESKeySpec;\nimport javax.crypto.spec.IvParameterSpec;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * 加密\n * @author Zheng Jie\n * @date 2018-11-23\n */\npublic class EncryptUtils {\n\n    private static final String STR_PARAM = \"Passw0rd\";\n    private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));\n\n    private static DESKeySpec getDesKeySpec(String source) throws Exception {\n        if (source == null || source.isEmpty()) {\n            return null;\n        }\n        String strKey = \"Passw0rd\";\n        return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));\n    }\n\n    /**\n     * 对称加密\n     */\n    public static String desEncrypt(String source) throws Exception {\n        Cipher cipher = Cipher.getInstance(\"DES/CBC/PKCS5Padding\");\n        DESKeySpec desKeySpec = getDesKeySpec(source);\n        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(\"DES\");\n        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);\n        cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);\n        return byte2hex(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();\n    }\n\n    /**\n     * 对称解密\n     */\n    public static String desDecrypt(String source) throws Exception {\n        Cipher cipher = Cipher.getInstance(\"DES/CBC/PKCS5Padding\");\n        byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));\n        DESKeySpec desKeySpec = getDesKeySpec(source);\n        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(\"DES\");\n        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);\n        cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);\n        byte[] retByte = cipher.doFinal(src);\n        return new String(retByte);\n    }\n\n    private static String byte2hex(byte[] inStr) {\n        String stmp;\n        StringBuilder out = new StringBuilder(inStr.length * 2);\n        for (byte b : inStr) {\n            stmp = Integer.toHexString(b & 0xFF);\n            if (stmp.length() == 1) {\n                out.append(\"0\").append(stmp);\n            } else {\n                out.append(stmp);\n            }\n        }\n        return out.toString();\n    }\n\n    private static byte[] hex2byte(byte[] b) {\n        int size = 2;\n        if ((b.length % size) != 0) {\n            throw new IllegalArgumentException(\"长度不是偶数\");\n        }\n        byte[] b2 = new byte[b.length / 2];\n        for (int n = 0; n < b.length; n += size) {\n            String item = new String(b, n, 2);\n            b2[n / 2] = (byte) Integer.parseInt(item, 16);\n        }\n        return b2;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.poi.excel.BigExcelWriter;\nimport cn.hutool.poi.excel.ExcelUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.exception.BadRequestException;\nimport org.apache.poi.util.IOUtils;\nimport org.apache.poi.xssf.streaming.SXSSFSheet;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.ServletOutputStream;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.*;\nimport java.nio.file.Files;\nimport java.security.MessageDigest;\nimport java.text.DecimalFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * File工具类，扩展 hutool 工具包\n *\n * @author Zheng Jie\n * @date 2018-12-27\n */\n@Slf4j\npublic class FileUtil extends cn.hutool.core.io.FileUtil {\n\n    /**\n     * 系统临时目录\n     * <br>\n     * windows 包含路径分割符，但Linux 不包含,\n     * 在windows \\\\==\\ 前提下，\n     * 为安全起见 同意拼装 路径分割符，\n     * <pre>\n     *       java.io.tmpdir\n     *       windows : C:\\Users/xxx\\AppData\\Local\\Temp\\\n     *       linux: /temp\n     * </pre>\n     */\n    public static final String SYS_TEM_DIR = System.getProperty(\"java.io.tmpdir\") + File.separator;\n    /**\n     * 定义GB的计算常量\n     */\n    private static final int GB = 1024 * 1024 * 1024;\n    /**\n     * 定义MB的计算常量\n     */\n    private static final int MB = 1024 * 1024;\n    /**\n     * 定义KB的计算常量\n     */\n    private static final int KB = 1024;\n\n    /**\n     * 格式化小数\n     */\n    private static final DecimalFormat DF = new DecimalFormat(\"0.00\");\n\n    public static final String IMAGE = \"图片\";\n    public static final String TXT = \"文档\";\n    public static final String MUSIC = \"音乐\";\n    public static final String VIDEO = \"视频\";\n    public static final String OTHER = \"其他\";\n\n\n    /**\n     * MultipartFile转File\n     */\n    public static File toFile(MultipartFile multipartFile) {\n        // 获取文件名\n        String fileName = multipartFile.getOriginalFilename();\n        // 获取文件后缀\n        String prefix = \".\" + getExtensionName(fileName);\n        File file = null;\n        try {\n            // 用uuid作为文件名，防止生成的临时文件重复\n            file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix);\n            // MultipartFile to File\n            multipartFile.transferTo(file);\n        } catch (IOException e) {\n            log.error(e.getMessage(), e);\n        }\n        return file;\n    }\n\n    /**\n     * 获取文件扩展名，不带 .\n     */\n    public static String getExtensionName(String filename) {\n        if ((filename != null) && (!filename.isEmpty())) {\n            int dot = filename.lastIndexOf('.');\n            if ((dot > -1) && (dot < (filename.length() - 1))) {\n                return filename.substring(dot + 1);\n            }\n        }\n        return filename;\n    }\n\n    /**\n     * Java文件操作 获取不带扩展名的文件名\n     */\n    public static String getFileNameNoEx(String filename) {\n        if ((filename != null) && (!filename.isEmpty())) {\n            int dot = filename.lastIndexOf('.');\n            if (dot > -1) {\n                return filename.substring(0, dot);\n            }\n        }\n        return filename;\n    }\n\n    /**\n     * 文件大小转换\n     */\n    public static String getSize(long size) {\n        String resultSize;\n        if (size / GB >= 1) {\n            //如果当前Byte的值大于等于1GB\n            resultSize = DF.format(size / (float) GB) + \"GB\";\n        } else if (size / MB >= 1) {\n            //如果当前Byte的值大于等于1MB\n            resultSize = DF.format(size / (float) MB) + \"MB\";\n        } else if (size / KB >= 1) {\n            //如果当前Byte的值大于等于1KB\n            resultSize = DF.format(size / (float) KB) + \"KB\";\n        } else {\n            resultSize = size + \"B\";\n        }\n        return resultSize;\n    }\n\n    /**\n     * inputStream 转 File\n     */\n    static File inputStreamToFile(InputStream ins, String name){\n        File file = new File(SYS_TEM_DIR + name);\n        if (file.exists()) {\n            return file;\n        }\n        OutputStream os = null;\n        try {\n            os = Files.newOutputStream(file.toPath());\n            int bytesRead;\n            int len = 8192;\n            byte[] buffer = new byte[len];\n            while ((bytesRead = ins.read(buffer, 0, len)) != -1) {\n                os.write(buffer, 0, bytesRead);\n            }\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        } finally {\n            CloseUtil.close(os);\n            CloseUtil.close(ins);\n        }\n        return file;\n    }\n\n    /**\n     * 将文件名解析成文件的上传路径\n     */\n    public static File upload(MultipartFile file, String filePath) {\n        Date date = new Date();\n        SimpleDateFormat format = new SimpleDateFormat(\"yyyyMMddhhmmssS\");\n        // 过滤非法文件名\n        String name = getFileNameNoEx(verifyFilename(file.getOriginalFilename()));\n        String suffix = getExtensionName(file.getOriginalFilename());\n        String nowStr = \"-\" + format.format(date);\n        try {\n            String fileName = name + nowStr + \".\" + suffix;\n            String path = filePath + fileName;\n            // getCanonicalFile 可解析正确各种路径\n            File dest = new File(path).getCanonicalFile();\n            // 检测是否存在目录\n            if (!dest.getParentFile().exists()) {\n                if (!dest.getParentFile().mkdirs()) {\n                    System.out.println(\"was not successful.\");\n                }\n            }\n            // 文件写入\n            file.transferTo(dest);\n            return dest;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n        return null;\n    }\n\n    /**\n     * 防止 CSV/XLSX 注入（CWE-1236）\n     * <p>\n     * 当单元格值以 {@code = + - @ \\t \\r} 开头时，电子表格软件可能将其解释为公式。\n     * 本方法在这类值前添加单引号前缀，并转义值中已有的单引号，防止公式注入攻击。\n     * </p>\n     *\n     * @param value 原始单元格值\n     * @return 经过安全处理的单元格值\n     * @see <a href=\"https://owasp.org/www-community/attacks/CSV_Injection\">OWASP CSV Injection</a>\n     */\n    public static String sanitizeCellValue(String value) {\n        if (value == null || value.isEmpty()) {\n            return value;\n        }\n        char first = value.charAt(0);\n        if (first == '=' || first == '+' || first == '-' || first == '@'\n                || first == '\\t' || first == '\\r') {\n            // 转义值中已有的单引号，防止攻击者利用单引号逃逸\n            return \"'\" + value.replace(\"'\", \"''\");\n        }\n        return value;\n    }\n\n    /**\n     * 导出excel\n     */\n    public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {\n        String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + \".xlsx\";\n        File file = new File(tempPath);\n        BigExcelWriter writer = ExcelUtil.getBigWriter(file);\n        // 处理数据以防止CSV/XLSX注入（CWE-1236）\n        List<Map<String, Object>> sanitizedList = list.stream().map(map -> {\n            Map<String, Object> sanitizedMap = new LinkedHashMap<>();\n            map.forEach((key, value) -> {\n                if (value instanceof String) {\n                    sanitizedMap.put(key, sanitizeCellValue((String) value));\n                } else {\n                    sanitizedMap.put(key, value);\n                }\n            });\n            return sanitizedMap;\n        }).collect(Collectors.toList());\n        // 一次性写出内容，使用默认样式，强制输出标题\n        writer.write(sanitizedList, true);\n        SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();\n        //上面需要强转SXSSFSheet  不然没有trackAllColumnsForAutoSizing方法\n        sheet.trackAllColumnsForAutoSizing();\n        //列宽自适应\n        writer.autoSizeColumnAll();\n        //response为HttpServletResponse对象\n        response.setContentType(\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8\");\n        //test.xls是弹出下载对话框的文件名，不能为中文，中文请自行编码\n        response.setHeader(\"Content-Disposition\", \"attachment;filename=file.xlsx\");\n        ServletOutputStream out = response.getOutputStream();\n        // 终止后删除临时文件\n        file.deleteOnExit();\n        writer.flush(out, true);\n        //此处记得关闭输出Servlet流\n        IoUtil.close(out);\n    }\n\n    public static String getFileType(String type) {\n        String documents = \"txt doc pdf ppt pps xlsx xls docx\";\n        String music = \"mp3 wav wma mpa ram ra aac aif m4a\";\n        String video = \"avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg\";\n        String image = \"bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg\";\n        if (image.contains(type)) {\n            return IMAGE;\n        } else if (documents.contains(type)) {\n            return TXT;\n        } else if (music.contains(type)) {\n            return MUSIC;\n        } else if (video.contains(type)) {\n            return VIDEO;\n        } else {\n            return OTHER;\n        }\n    }\n\n    public static void checkSize(long maxSize, long size) {\n        // 1M\n        int len = 1024 * 1024;\n        if (size > (maxSize * len)) {\n            throw new BadRequestException(\"文件超出规定大小:\" + maxSize + \"MB\");\n        }\n    }\n\n    /**\n     * 判断两个文件是否相同\n     */\n    public static boolean check(File file1, File file2) {\n        String img1Md5 = getMd5(file1);\n        String img2Md5 = getMd5(file2);\n        if(img1Md5 != null){\n            return img1Md5.equals(img2Md5);\n        }\n        return false;\n    }\n\n    /**\n     * 判断两个文件是否相同\n     */\n    public static boolean check(String file1Md5, String file2Md5) {\n        return file1Md5.equals(file2Md5);\n    }\n\n    private static byte[] getByte(File file) {\n        // 得到文件长度\n        byte[] b = new byte[(int) file.length()];\n        InputStream in = null;\n        try {\n            in = Files.newInputStream(file.toPath());\n            try {\n                System.out.println(in.read(b));\n            } catch (IOException e) {\n                log.error(e.getMessage(), e);\n            }\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return null;\n        } finally {\n            CloseUtil.close(in);\n        }\n        return b;\n    }\n\n    private static String getMd5(byte[] bytes) {\n        // 16进制字符\n        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};\n        try {\n            MessageDigest mdTemp = MessageDigest.getInstance(\"MD5\");\n            mdTemp.update(bytes);\n            byte[] md = mdTemp.digest();\n            int j = md.length;\n            char[] str = new char[j * 2];\n            int k = 0;\n            // 移位 输出字符串\n            for (byte byte0 : md) {\n                str[k++] = hexDigits[byte0 >>> 4 & 0xf];\n                str[k++] = hexDigits[byte0 & 0xf];\n            }\n            return new String(str);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n        return null;\n    }\n\n    /**\n     * 下载文件\n     *\n     * @param request  /\n     * @param response /\n     * @param file     /\n     */\n    public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) {\n        response.setCharacterEncoding(request.getCharacterEncoding());\n        response.setContentType(\"application/octet-stream\");\n        FileInputStream fis = null;\n        try {\n            fis = new FileInputStream(file);\n            response.setHeader(\"Content-Disposition\", \"attachment; filename=\" + file.getName());\n            IOUtils.copy(fis, response.getOutputStream());\n            response.flushBuffer();\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        } finally {\n            if (fis != null) {\n                try {\n                    fis.close();\n                    if (deleteOnExit) {\n                        file.deleteOnExit();\n                    }\n                } catch (IOException e) {\n                    log.error(e.getMessage(), e);\n                }\n            }\n        }\n    }\n\n    /**\n     * 验证并过滤非法的文件名\n     * @param fileName 文件名\n     * @return 文件名\n     */\n    public static String verifyFilename(String fileName) {\n        // 过滤掉特殊字符\n        fileName = fileName.replaceAll(\"[\\\\\\\\/:*?\\\"<>|~\\\\s]\", \"\");\n\n        // 去掉文件名开头和结尾的空格和点\n        fileName = fileName.trim().replaceAll(\"^[. ]+|[. ]+$\", \"\");\n\n        // 不允许文件名超过255（在Mac和Linux中）或260（在Windows中）个字符\n        int maxFileNameLength = 255;\n        if (System.getProperty(\"os.name\").startsWith(\"Windows\")) {\n            maxFileNameLength = 260;\n        }\n        if (fileName.length() > maxFileNameLength) {\n            fileName = fileName.substring(0, maxFileNameLength);\n        }\n\n        // 过滤掉控制字符\n        fileName = fileName.replaceAll(\"[\\\\p{Cntrl}]\", \"\");\n\n        // 过滤掉 \"..\" 路径\n        fileName = fileName.replaceAll(\"\\\\.{2,}\", \"\");\n\n        // 去掉文件名开头的 \"..\"\n        fileName = fileName.replaceAll(\"^\\\\.+/\", \"\");\n\n        // 保留文件名中最后一个 \".\" 字符，过滤掉其他 \".\"\n        fileName = fileName.replaceAll(\"^(.*)(\\\\.[^.]*)$\", \"$1\").replaceAll(\"\\\\.\", \"\") +\n                fileName.replaceAll(\"^(.*)(\\\\.[^.]*)$\", \"$2\");\n\n        return fileName;\n    }\n\n    public static String getMd5(File file) {\n        return getMd5(getByte(file));\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java",
    "content": "package me.zhengjie.utils;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 分页结果封装类\n * @author Zheng Jie\n * @date 2018-11-23\n * @param <T>\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class PageResult<T> implements Serializable {\n\n    private List<T> content;\n\n    private long totalElements;\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport org.springframework.data.domain.Page;\nimport java.util.*;\n\n/**\n * 分页工具\n * @author Zheng Jie\n * @date 2018-12-10\n */\npublic class PageUtil extends cn.hutool.core.util.PageUtil {\n\n    /**\n     * List 分页\n     */\n    public static <T> List<T> paging(int page, int size , List<T> list) {\n        int fromIndex = page * size;\n        int toIndex = page * size + size;\n        if(fromIndex > list.size()){\n            return Collections.emptyList();\n        } else if(toIndex >= list.size()) {\n            return list.subList(fromIndex,list.size());\n        } else {\n            return list.subList(fromIndex,toIndex);\n        }\n    }\n\n    /**\n     * Page 数据处理，预防redis反序列化报错\n     */\n    public static <T> PageResult<T> toPage(Page<T> page) {\n        return new PageResult<>(page.getContent(), page.getTotalElements());\n    }\n\n    /**\n     * 自定义分页\n     */\n    public static <T> PageResult<T> toPage(List<T> list, long totalElements) {\n        return new PageResult<>(list, totalElements);\n    }\n\n    /**\n     * 返回空数据\n     */\n    public static <T> PageResult<T> noData () {\n        return new PageResult<>(null, 0);\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.DataPermission;\nimport me.zhengjie.annotation.Query;\nimport javax.persistence.criteria.*;\nimport java.lang.reflect.Field;\nimport java.util.*;\n\n/**\n * @author Zheng Jie\n * @date 2019-6-4 14:59:48\n */\n@Slf4j\n@SuppressWarnings({\"unchecked\",\"all\"})\npublic class QueryHelp {\n\n    public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {\n        List<Predicate> list = new ArrayList<>();\n        if(query == null){\n            return cb.and(list.toArray(new Predicate[0]));\n        }\n        // 数据权限验证\n        DataPermission permission = query.getClass().getAnnotation(DataPermission.class);\n        if(permission != null){\n            // 获取数据权限\n            List<Long> dataScopes = SecurityUtils.getCurrentUserDataScope();\n            if(CollectionUtil.isNotEmpty(dataScopes)){\n                if(StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {\n                    Join join = root.join(permission.joinName(), JoinType.LEFT);\n                    list.add(getExpression(permission.fieldName(),join, root).in(dataScopes));\n                } else if (StringUtils.isBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {\n                    list.add(getExpression(permission.fieldName(),null, root).in(dataScopes));\n                }\n            }\n        }\n        try {\n            Map<String, Join> joinKey = new HashMap<>();\n            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());\n            for (Field field : fields) {\n                boolean accessible = field.isAccessible();\n                // 设置对象的访问权限，保证对private的属性的访\n                field.setAccessible(true);\n                Query q = field.getAnnotation(Query.class);\n                if (q != null) {\n                    String propName = q.propName();\n                    String joinName = q.joinName();\n                    String blurry = q.blurry();\n                    String attributeName = isBlank(propName) ? field.getName() : propName;\n                    Class<?> fieldType = field.getType();\n                    Object val = field.get(query);\n                    if (ObjectUtil.isNull(val) || \"\".equals(val)) {\n                        continue;\n                    }\n                    Join join = null;\n                    // 模糊多字段\n                    if (ObjectUtil.isNotEmpty(blurry)) {\n                        String[] blurrys = blurry.split(\",\");\n                        List<Predicate> orPredicate = new ArrayList<>();\n                        for (String s : blurrys) {\n                            orPredicate.add(cb.like(root.get(s).as(String.class), \"%\" + val.toString() + \"%\"));\n                        }\n                        Predicate[] p = new Predicate[orPredicate.size()];\n                        list.add(cb.or(orPredicate.toArray(p)));\n                        continue;\n                    }\n                    if (ObjectUtil.isNotEmpty(joinName)) {\n                        join = joinKey.get(joinName);\n                        if(join == null){\n                            String[] joinNames = joinName.split(\">\");\n                            for (String name : joinNames) {\n                                switch (q.join()) {\n                                    case LEFT:\n                                        if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){\n                                            join = join.join(name, JoinType.LEFT);\n                                        } else {\n                                            join = root.join(name, JoinType.LEFT);\n                                        }\n                                        break;\n                                    case RIGHT:\n                                        if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){\n                                            join = join.join(name, JoinType.RIGHT);\n                                        } else {\n                                            join = root.join(name, JoinType.RIGHT);\n                                        }\n                                        break;\n                                    case INNER:\n                                        if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){\n                                            join = join.join(name, JoinType.INNER);\n                                        } else {\n                                            join = root.join(name, JoinType.INNER);\n                                        }\n                                        break;\n                                    default: break;\n                                }\n                            }\n                            joinKey.put(joinName, join);\n                        }\n                    }\n                    switch (q.type()) {\n                        case EQUAL:\n                            list.add(cb.equal(getExpression(attributeName,join,root)\n                                    .as((Class<? extends Comparable>) fieldType),val));\n                            break;\n                        case GREATER_THAN:\n                            list.add(cb.greaterThanOrEqualTo(getExpression(attributeName,join,root)\n                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));\n                            break;\n                        case LESS_THAN:\n                            list.add(cb.lessThanOrEqualTo(getExpression(attributeName,join,root)\n                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));\n                            break;\n                        case LESS_THAN_NQ:\n                            list.add(cb.lessThan(getExpression(attributeName,join,root)\n                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));\n                            break;\n                        case INNER_LIKE:\n                            list.add(cb.like(getExpression(attributeName,join,root)\n                                    .as(String.class), \"%\" + val.toString() + \"%\"));\n                            break;\n                        case LEFT_LIKE:\n                            list.add(cb.like(getExpression(attributeName,join,root)\n                                    .as(String.class), \"%\" + val.toString()));\n                            break;\n                        case RIGHT_LIKE:\n                            list.add(cb.like(getExpression(attributeName,join,root)\n                                    .as(String.class), val.toString() + \"%\"));\n                            break;\n                        case IN:\n                            if (CollUtil.isNotEmpty((Collection<Object>)val)) {\n                                list.add(getExpression(attributeName,join,root).in((Collection<Object>) val));\n                            }\n                            break;\n                        case NOT_IN:\n                            if (CollUtil.isNotEmpty((Collection<Object>)val)) {\n                                list.add(getExpression(attributeName,join,root).in((Collection<Object>) val).not());\n                            }\n                            break;\n                        case NOT_EQUAL:\n                            list.add(cb.notEqual(getExpression(attributeName,join,root), val));\n                            break;\n                        case NOT_NULL:\n                            list.add(cb.isNotNull(getExpression(attributeName,join,root)));\n                            break;\n                        case IS_NULL:\n                            list.add(cb.isNull(getExpression(attributeName,join,root)));\n                            break;\n                        case BETWEEN:\n                            List<Object> between = new ArrayList<>((List<Object>)val);\n                            if(between.size() == 2){\n                                list.add(cb.between(getExpression(attributeName, join, root).as((Class<? extends Comparable>) between.get(0).getClass()),\n                                        (Comparable) between.get(0), (Comparable) between.get(1)));\n                            }\n                            break;\n                        case FIND_IN_SET:\n                            list.add(cb.greaterThan(cb.function(\"FIND_IN_SET\", Integer.class,\n                                    cb.literal(val.toString()), root.get(attributeName)), 0));\n                            break;\n                        default: break;\n                    }\n                }\n                field.setAccessible(accessible);\n            }\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n        int size = list.size();\n        return cb.and(list.toArray(new Predicate[size]));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static <T, R> Expression<T> getExpression(String attributeName, Join join, Root<R> root) {\n        if (ObjectUtil.isNotEmpty(join)) {\n            return join.get(attributeName);\n        } else {\n            return root.get(attributeName);\n        }\n    }\n\n    private static boolean isBlank(final CharSequence cs) {\n        int strLen;\n        if (cs == null || (strLen = cs.length()) == 0) {\n            return true;\n        }\n        for (int i = 0; i < strLen; i++) {\n            if (!Character.isWhitespace(cs.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static List<Field> getAllFields(Class clazz, List<Field> fields) {\n        if (clazz != null) {\n            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));\n            getAllFields(clazz.getSuperclass(), fields);\n        }\n        return fields;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.alibaba.fastjson2.JSON;\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Sets;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\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.serializer.StringRedisSerializer;\nimport org.springframework.stereotype.Component;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n * @author /\n */\n@Component\n@SuppressWarnings({\"all\"})\npublic class RedisUtils {\n    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);\n\n    private RedisTemplate<Object, Object> redisTemplate;\n\n    public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {\n        this.redisTemplate = redisTemplate;\n        this.redisTemplate.setKeySerializer(new StringRedisSerializer());\n        this.redisTemplate.setHashKeySerializer(new StringRedisSerializer());\n    }\n\n    /**\n     * 指定缓存失效时间\n     *\n     * @param key  键\n     * @param time 时间(秒) 注意:这里将会替换原有的时间\n     */\n    public boolean expire(String key, long time) {\n        try {\n            if (time > 0) {\n                redisTemplate.expire(key, time, TimeUnit.SECONDS);\n            }\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 指定缓存失效时间\n     *\n     * @param key      键\n     * @param time     时间(秒) 注意:这里将会替换原有的时间\n     * @param timeUnit 单位\n     */\n    public boolean expire(String key, long time, TimeUnit timeUnit) {\n        try {\n            if (time > 0) {\n                redisTemplate.expire(key, time, timeUnit);\n            }\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 根据 key 获取过期时间\n     *\n     * @param key 键 不能为null\n     * @return 时间(秒) 返回0代表为永久有效\n     */\n    public long getExpire(Object key) {\n        return redisTemplate.getExpire(key, TimeUnit.SECONDS);\n    }\n\n    /**\n     * 查找匹配key\n     *\n     * @param pattern key\n     * @return /\n     */\n    public List<String> scan(String pattern) {\n        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();\n        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();\n        RedisConnection rc = Objects.requireNonNull(factory).getConnection();\n        Cursor<byte[]> cursor = rc.scan(options);\n        List<String> result = new ArrayList<>();\n        while (cursor.hasNext()) {\n            result.add(new String(cursor.next()));\n        }\n        try {\n            RedisConnectionUtils.releaseConnection(rc, factory);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n        return result;\n    }\n\n    /**\n     * 分页查询 key\n     *\n     * @param patternKey key\n     * @param page       页码\n     * @param size       每页数目\n     * @return /\n     */\n    public List<String> findKeysForPage(String patternKey, int page, int size) {\n        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();\n        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();\n        RedisConnection rc = Objects.requireNonNull(factory).getConnection();\n        Cursor<byte[]> cursor = rc.scan(options);\n        List<String> result = new ArrayList<>(size);\n        int tmpIndex = 0;\n        int fromIndex = page * size;\n        int toIndex = page * size + size;\n        while (cursor.hasNext()) {\n            if (tmpIndex >= fromIndex && tmpIndex < toIndex) {\n                result.add(new String(cursor.next()));\n                tmpIndex++;\n                continue;\n            }\n            // 获取到满足条件的数据后,就可以退出了\n            if (tmpIndex >= toIndex) {\n                break;\n            }\n            tmpIndex++;\n            cursor.next();\n        }\n        try {\n            RedisConnectionUtils.releaseConnection(rc, factory);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n        return result;\n    }\n\n    /**\n     * 判断key是否存在\n     *\n     * @param key 键\n     * @return true 存在 false不存在\n     */\n    public boolean hasKey(String key) {\n        try {\n            return redisTemplate.hasKey(key);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 删除缓存\n     *\n     * @param key 可以传一个值 或多个\n     */\n    public void del(String... keys) {\n        if (keys != null && keys.length > 0) {\n            if (keys.length == 1) {\n                boolean result = redisTemplate.delete(keys[0]);\n                log.debug(\"--------------------------------------------\");\n                log.debug(new StringBuilder(\"删除缓存：\").append(keys[0]).append(\"，结果：\").append(result).toString());\n                log.debug(\"--------------------------------------------\");\n            } else {\n                Set<Object> keySet = new HashSet<>();\n                for (String key : keys) {\n                    if (redisTemplate.hasKey(key))\n                        keySet.add(key);\n                }\n                long count = redisTemplate.delete(keySet);\n                log.debug(\"--------------------------------------------\");\n                log.debug(\"成功删除缓存：\" + keySet.toString());\n                log.debug(\"缓存删除数量：\" + count + \"个\");\n                log.debug(\"--------------------------------------------\");\n            }\n        }\n    }\n\n    /**\n     * 批量模糊删除key\n     * @param pattern\n     */\n    public void scanDel(String pattern){\n        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();\n        try (Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(\n                (RedisCallback<Cursor<byte[]>>) connection -> (Cursor<byte[]>) new ConvertingCursor<>(\n                        connection.scan(options), redisTemplate.getKeySerializer()::deserialize))) {\n            while (cursor.hasNext()) {\n                redisTemplate.delete(cursor.next());\n            }\n        }\n    }\n\n    // ============================String=============================\n\n    /**\n     * 普通缓存获取\n     *\n     * @param key 键\n     * @return 值\n     */\n    public Object get(String key) {\n        return key == null ? null : redisTemplate.opsForValue().get(key);\n    }\n\n    /**\n     * 普通缓存获取\n     *\n     * @param key 键\n     * @return 值\n     */\n    public <T> T get(String key, Class<T> clazz) {\n        Object value = key == null ? null : redisTemplate.opsForValue().get(key);\n        if (value == null) {\n            return null;\n        }\n        // 如果 value 不是目标类型，则尝试将其反序列化为 clazz 类型\n        if (!clazz.isInstance(value)) {\n            return JSON.parseObject(value.toString(), clazz);\n        } else if (clazz.isInstance(value)) {\n            return clazz.cast(value);\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * 普通缓存获取\n     *\n     * @param key 键\n     * @param clazz 列表中元素的类型\n     * @return 值\n     */\n    public <T> List<T> getList(String key, Class<T> clazz) {\n        Object value = key == null ? null : redisTemplate.opsForValue().get(key);\n        if (value == null) {\n            return null;\n        }\n        if (value instanceof List<?>) {\n            List<?> list = (List<?>) value;\n            // 检查每个元素是否为指定类型\n            if (list.stream().allMatch(clazz::isInstance)) {\n                return list.stream().map(clazz::cast).collect(Collectors.toList());\n            }\n        }\n        return null;\n    }\n\n\n    /**\n     * 普通缓存获取\n     *\n     * @param key 键\n     * @return 值\n     */\n    public String getStr(String key) {\n        if(StrUtil.isBlank(key)){\n            return null;\n        }\n        Object value = redisTemplate.opsForValue().get(key);\n        if (value == null) {\n            return null;\n        } else {\n            return String.valueOf(value);\n        }\n    }\n\n    /**\n     * 批量获取\n     *\n     * @param keys\n     * @return\n     */\n    public List<Object> multiGet(List<String> keys) {\n        List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys));\n        List resultList = Lists.newArrayList();\n        Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add)));\n        return resultList;\n    }\n\n    /**\n     * 普通缓存放入\n     *\n     * @param key   键\n     * @param value 值\n     * @return true成功 false失败\n     */\n    public boolean set(String key, Object value) {\n        int attempt = 0;\n        while (attempt < 3) {\n            try {\n                redisTemplate.opsForValue().set(key, value);\n                return true;\n            } catch (Exception e) {\n                attempt++;\n                log.error(\"Attempt {} failed: {}\", attempt, e.getMessage(), e);\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 普通缓存放入并设置时间\n     *\n     * @param key   键\n     * @param value 值\n     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期，注意:这里将会替换原有的时间\n     * @return true成功 false 失败\n     */\n    public boolean set(String key, Object value, long time) {\n        try {\n            if (time > 0) {\n                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);\n            } else {\n                set(key, value);\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 普通缓存放入并设置时间\n     *\n     * @param key      键\n     * @param value    值\n     * @param time     时间，注意:这里将会替换原有的时间\n     * @param timeUnit 类型\n     * @return true成功 false 失败\n     */\n    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {\n        try {\n            if (time > 0) {\n                redisTemplate.opsForValue().set(key, value, time, timeUnit);\n            } else {\n                set(key, value);\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    // ================================Map=================================\n\n    /**\n     * HashGet\n     *\n     * @param key  键 不能为null\n     * @param item 项 不能为null\n     * @return 值\n     */\n    public Object hget(String key, String item) {\n        return redisTemplate.opsForHash().get(key, item);\n    }\n\n    /**\n     * 获取hashKey对应的所有键值\n     *\n     * @param key 键\n     * @return 对应的多个键值\n     */\n    public Map<Object, Object> hmget(String key) {\n        return redisTemplate.opsForHash().entries(key);\n\n    }\n\n    /**\n     * HashSet\n     *\n     * @param key 键\n     * @param map 对应多个键值\n     * @return true 成功 false 失败\n     */\n    public boolean hmset(String key, Map<String, Object> map) {\n        try {\n            redisTemplate.opsForHash().putAll(key, map);\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * HashSet\n     *\n     * @param key  键\n     * @param map  对应多个键值\n     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间\n     * @return true成功 false失败\n     */\n    public boolean hmset(String key, Map<String, Object> map, long time) {\n        try {\n            redisTemplate.opsForHash().putAll(key, map);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 向一张hash表中放入数据,如果不存在将创建\n     *\n     * @param key   键\n     * @param item  项\n     * @param value 值\n     * @return true 成功 false失败\n     */\n    public boolean hset(String key, String item, Object value) {\n        try {\n            redisTemplate.opsForHash().put(key, item, value);\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 向一张hash表中放入数据,如果不存在将创建\n     *\n     * @param key   键\n     * @param item  项\n     * @param value 值\n     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间\n     * @return true 成功 false失败\n     */\n    public boolean hset(String key, String item, Object value, long time) {\n        try {\n            redisTemplate.opsForHash().put(key, item, value);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 删除hash表中的值\n     *\n     * @param key  键 不能为null\n     * @param item 项 可以使多个 不能为null\n     */\n    public void hdel(String key, Object... item) {\n        redisTemplate.opsForHash().delete(key, item);\n    }\n\n    /**\n     * 判断hash表中是否有该项的值\n     *\n     * @param key  键 不能为null\n     * @param item 项 不能为null\n     * @return true 存在 false不存在\n     */\n    public boolean hHasKey(String key, String item) {\n        return redisTemplate.opsForHash().hasKey(key, item);\n    }\n\n    /**\n     * hash递增 如果不存在,就会创建一个 并把新增后的值返回\n     *\n     * @param key  键\n     * @param item 项\n     * @param by   要增加几(大于0)\n     * @return\n     */\n    public double hincr(String key, String item, double by) {\n        return redisTemplate.opsForHash().increment(key, item, by);\n    }\n\n    /**\n     * hash递减\n     *\n     * @param key  键\n     * @param item 项\n     * @param by   要减少记(小于0)\n     * @return\n     */\n    public double hdecr(String key, String item, double by) {\n        return redisTemplate.opsForHash().increment(key, item, -by);\n    }\n\n    // ============================set=============================\n\n    /**\n     * 根据key获取Set中的所有值\n     *\n     * @param key 键\n     * @return\n     */\n    public Set<Object> sGet(String key) {\n        try {\n            return redisTemplate.opsForSet().members(key);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return null;\n        }\n    }\n\n    /**\n     * 根据value从一个set中查询,是否存在\n     *\n     * @param key   键\n     * @param value 值\n     * @return true 存在 false不存在\n     */\n    public boolean sHasKey(String key, Object value) {\n        try {\n            return redisTemplate.opsForSet().isMember(key, value);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 将数据放入set缓存\n     *\n     * @param key    键\n     * @param values 值 可以是多个\n     * @return 成功个数\n     */\n    public long sSet(String key, Object... values) {\n        try {\n            return redisTemplate.opsForSet().add(key, values);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return 0;\n        }\n    }\n\n    /**\n     * 将set数据放入缓存\n     *\n     * @param key    键\n     * @param time   时间(秒) 注意:这里将会替换原有的时间\n     * @param values 值 可以是多个\n     * @return 成功个数\n     */\n    public long sSetAndTime(String key, long time, Object... values) {\n        try {\n            Long count = redisTemplate.opsForSet().add(key, values);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return count;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return 0;\n        }\n    }\n\n    /**\n     * 获取set缓存的长度\n     *\n     * @param key 键\n     * @return\n     */\n    public long sGetSetSize(String key) {\n        try {\n            return redisTemplate.opsForSet().size(key);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return 0;\n        }\n    }\n\n    /**\n     * 移除值为value的\n     *\n     * @param key    键\n     * @param values 值 可以是多个\n     * @return 移除的个数\n     */\n    public long setRemove(String key, Object... values) {\n        try {\n            Long count = redisTemplate.opsForSet().remove(key, values);\n            return count;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return 0;\n        }\n    }\n\n    // ===============================list=================================\n\n    /**\n     * 获取list缓存的内容\n     *\n     * @param key   键\n     * @param start 开始\n     * @param end   结束 0 到 -1代表所有值\n     * @return\n     */\n    public List<Object> lGet(String key, long start, long end) {\n        try {\n            return redisTemplate.opsForList().range(key, start, end);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return null;\n        }\n    }\n\n    /**\n     * 获取list缓存的长度\n     *\n     * @param key 键\n     * @return\n     */\n    public long lGetListSize(String key) {\n        try {\n            return redisTemplate.opsForList().size(key);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return 0;\n        }\n    }\n\n    /**\n     * 通过索引 获取list中的值\n     *\n     * @param key   键\n     * @param index 索引 index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推\n     * @return\n     */\n    public Object lGetIndex(String key, long index) {\n        try {\n            return redisTemplate.opsForList().index(key, index);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return null;\n        }\n    }\n\n    /**\n     * 将list放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @return\n     */\n    public boolean lSet(String key, Object value) {\n        try {\n            redisTemplate.opsForList().rightPush(key, value);\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 将list放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @param time  时间(秒) 注意:这里将会替换原有的时间\n     * @return\n     */\n    public boolean lSet(String key, Object value, long time) {\n        try {\n            redisTemplate.opsForList().rightPush(key, value);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 将list放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @return\n     */\n    public boolean lSet(String key, List<Object> value) {\n        try {\n            redisTemplate.opsForList().rightPushAll(key, value);\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 将list放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @param time  时间(秒) 注意:这里将会替换原有的时间\n     * @return\n     */\n    public boolean lSet(String key, List<Object> value, long time) {\n        try {\n            redisTemplate.opsForList().rightPushAll(key, value);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 根据索引修改list中的某条数据\n     *\n     * @param key   键\n     * @param index 索引\n     * @param value 值\n     * @return /\n     */\n    public boolean lUpdateIndex(String key, long index, Object value) {\n        try {\n            redisTemplate.opsForList().set(key, index, value);\n            return true;\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    /**\n     * 移除N个值为value\n     *\n     * @param key   键\n     * @param count 移除多少个\n     * @param value 值\n     * @return 移除的个数\n     */\n    public long lRemove(String key, long count, Object value) {\n        try {\n            return redisTemplate.opsForList().remove(key, count, value);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n            return 0;\n        }\n    }\n\n    /**\n     * @param prefix 前缀\n     * @param ids    id\n     */\n    public void delByKeys(String prefix, Set<Long> ids) {\n        Set<Object> keys = new HashSet<>();\n        for (Long id : ids) {\n            keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));\n        }\n        long count = redisTemplate.delete(keys);\n    }\n\n    // ============================incr=============================\n\n    /**\n     * 递增\n     * @param key\n     * @return\n     */\n    public Long increment(String key) {\n        return redisTemplate.opsForValue().increment(key);\n    }\n\n    /**\n     * 递减\n     * @param key\n     * @return\n     */\n    public Long decrement(String key) {\n        return redisTemplate.opsForValue().decrement(key);\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.Objects;\n\n/**\n * 获取 HttpServletRequest\n * @author Zheng Jie\n * @date 2018-11-24\n */\npublic class RequestHolder {\n\n    public static HttpServletRequest getHttpServletRequest() {\n        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java",
    "content": "package me.zhengjie.utils;\n\nimport org.apache.commons.codec.binary.Base64;\nimport javax.crypto.Cipher;\nimport java.io.ByteArrayOutputStream;\nimport java.security.*;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\n\n/**\n * @author https://www.cnblogs.com/nihaorz/p/10690643.html\n * @description Rsa 工具类，公钥私钥生成，加解密\n * @date 2020-05-18\n **/\npublic class RsaUtils {\n\n    private static final String SRC = \"123456\";\n\n    public static void main(String[] args) throws Exception {\n        System.out.println(\"\\n\");\n        RsaKeyPair keyPair = generateKeyPair();\n        System.out.println(\"公钥：\" + keyPair.getPublicKey());\n        System.out.println(\"私钥：\" + keyPair.getPrivateKey());\n        System.out.println(\"\\n\");\n        test1(keyPair);\n        System.out.println(\"\\n\");\n        test2(keyPair);\n        System.out.println(\"\\n\");\n    }\n\n    /**\n     * 公钥加密私钥解密\n     */\n    private static void test1(RsaKeyPair keyPair) throws Exception {\n        System.out.println(\"***************** 公钥加密私钥解密开始 *****************\");\n        String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);\n        String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);\n        System.out.println(\"加密前：\" + RsaUtils.SRC);\n        System.out.println(\"加密后：\" + text1);\n        System.out.println(\"解密后：\" + text2);\n        if (RsaUtils.SRC.equals(text2)) {\n            System.out.println(\"解密字符串和原始字符串一致，解密成功\");\n        } else {\n            System.out.println(\"解密字符串和原始字符串不一致，解密失败\");\n        }\n        System.out.println(\"***************** 公钥加密私钥解密结束 *****************\");\n    }\n\n    /**\n     * 私钥加密公钥解密\n     * @throws Exception /\n     */\n    private static void test2(RsaKeyPair keyPair) throws Exception {\n        System.out.println(\"***************** 私钥加密公钥解密开始 *****************\");\n        String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);\n        String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);\n        System.out.println(\"加密前：\" + RsaUtils.SRC);\n        System.out.println(\"加密后：\" + text1);\n        System.out.println(\"解密后：\" + text2);\n        if (RsaUtils.SRC.equals(text2)) {\n            System.out.println(\"解密字符串和原始字符串一致，解密成功\");\n        } else {\n            System.out.println(\"解密字符串和原始字符串不一致，解密失败\");\n        }\n        System.out.println(\"***************** 私钥加密公钥解密结束 *****************\");\n    }\n\n    /**\n     * 公钥解密\n     *\n     * @param publicKeyText 公钥\n     * @param text 待解密的信息\n     * @return /\n     * @throws Exception /\n     */\n    public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {\n        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));\n        KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\");\n        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);\n        Cipher cipher = Cipher.getInstance(\"RSA\");\n        cipher.init(Cipher.DECRYPT_MODE, publicKey);\n        byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));\n        return new String(result);\n    }\n\n    /**\n     * 私钥加密\n     *\n     * @param privateKeyText 私钥\n     * @param text 待加密的信息\n     * @return /\n     * @throws Exception /\n     */\n    public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {\n        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));\n        KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\");\n        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);\n        Cipher cipher = Cipher.getInstance(\"RSA\");\n        cipher.init(Cipher.ENCRYPT_MODE, privateKey);\n        byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());\n        return Base64.encodeBase64String(result);\n    }\n\n    /**\n     * 私钥解密\n     *\n     * @param privateKeyText 私钥\n     * @param text 待解密的文本\n     * @return /\n     * @throws Exception /\n     */\n    public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {\n        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));\n        KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\");\n        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);\n        Cipher cipher = Cipher.getInstance(\"RSA\");\n        cipher.init(Cipher.DECRYPT_MODE, privateKey);\n        byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));\n        return new String(result);\n    }\n\n    /**\n     * 公钥加密\n     *\n     * @param publicKeyText 公钥\n     * @param text 待加密的文本\n     * @return /\n     */\n    public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {\n        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));\n        KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\");\n        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);\n        Cipher cipher = Cipher.getInstance(\"RSA\");\n        cipher.init(Cipher.ENCRYPT_MODE, publicKey);\n        byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());\n        return Base64.encodeBase64String(result);\n    }\n\n    private static byte[] doLongerCipherFinal(int opMode,Cipher cipher, byte[] source) throws Exception {\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        if (opMode == Cipher.DECRYPT_MODE) {\n            out.write(cipher.doFinal(source));\n        } else {\n            int offset = 0;\n            int totalSize = source.length;\n            while (totalSize - offset > 0) {\n                int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset);\n                out.write(cipher.doFinal(source, offset, size));\n                offset += size;\n            }\n        }\n        out.close();\n        return out.toByteArray();\n    }\n\n    /**\n     * 构建RSA密钥对\n     *\n     * @return /\n     * @throws NoSuchAlgorithmException /\n     */\n    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {\n        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\");\n        keyPairGenerator.initialize(1024);\n        KeyPair keyPair = keyPairGenerator.generateKeyPair();\n        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();\n        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();\n        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());\n        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());\n        return new RsaKeyPair(publicKeyString, privateKeyString);\n    }\n\n\n    /**\n     * RSA密钥对对象\n     */\n    public static class RsaKeyPair {\n\n        private final String publicKey;\n        private final String privateKey;\n\n        public RsaKeyPair(String publicKey, String privateKey) {\n            this.publicKey = publicKey;\n            this.privateKey = privateKey;\n        }\n\n        public String getPublicKey() {\n            return publicKey;\n        }\n\n        public String getPrivateKey() {\n            return privateKey;\n        }\n\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.jwt.JWT;\nimport cn.hutool.jwt.JWTUtil;\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.JSONArray;\nimport com.alibaba.fastjson2.JSONObject;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.utils.enums.DataScopeEnum;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 获取当前登录的用户\n * @author Zheng Jie\n * @date 2019-01-17\n */\n@Slf4j\n@Component\npublic class SecurityUtils {\n\n    public static String header;\n\n    public static String tokenStartWith;\n\n    @Value(\"${jwt.header}\")\n    public void setHeader(String header) {\n        SecurityUtils.header = header;\n    }\n\n    @Value(\"${jwt.token-start-with}\")\n    public void setTokenStartWith(String tokenStartWith) {\n        SecurityUtils.tokenStartWith = tokenStartWith;\n    }\n\n    /**\n     * 获取当前登录的用户\n     * @return UserDetails\n     */\n    public static UserDetails getCurrentUser() {\n        UserDetailsService userDetailsService = SpringBeanHolder.getBean(UserDetailsService.class);\n        return userDetailsService.loadUserByUsername(getCurrentUsername());\n    }\n\n    /**\n     * 获取当前用户的数据权限\n     * @return /\n     */\n    public static List<Long> getCurrentUserDataScope(){\n        UserDetails userDetails = getCurrentUser();\n        // 将 Java 对象转换为 JSONObject 对象\n        JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);\n        JSONArray jsonArray = jsonObject.getJSONArray(\"dataScopes\");\n        return JSON.parseArray(jsonArray.toJSONString(), Long.class);\n    }\n\n    /**\n     * 获取数据权限级别\n     * @return 级别\n     */\n    public static String getDataScopeType() {\n        List<Long> dataScopes = getCurrentUserDataScope();\n        if(CollUtil.isEmpty(dataScopes)){\n            return \"\";\n        }\n        return DataScopeEnum.ALL.getValue();\n    }\n\n    /**\n     * 获取用户ID\n     * @return 系统用户ID\n     */\n    public static Long getCurrentUserId() {\n        return getCurrentUserId(getToken());\n    }\n\n    /**\n     * 获取用户ID\n     * @return 系统用户ID\n     */\n    public static Long getCurrentUserId(String token) {\n        JWT jwt = JWTUtil.parseToken(token);\n        return Long.valueOf(jwt.getPayload(\"userId\").toString());\n    }\n\n    /**\n     * 获取系统用户名称\n     *\n     * @return 系统用户名称\n     */\n    public static String getCurrentUsername() {\n        return getCurrentUsername(getToken());\n    }\n\n    /**\n     * 获取系统用户名称\n     *\n     * @return 系统用户名称\n     */\n    public static String getCurrentUsername(String token) {\n        JWT jwt = JWTUtil.parseToken(token);\n        return jwt.getPayload(\"sub\").toString();\n    }\n\n    /**\n     * 获取Token\n     * @return /\n     */\n    public static String getToken() {\n        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder\n                .getRequestAttributes())).getRequest();\n        String bearerToken = request.getHeader(header);\n        if (bearerToken != null && bearerToken.startsWith(tokenStartWith)) {\n            // 去掉令牌前缀\n            return bearerToken.replace(tokenStartWith, \"\");\n        } else {\n            log.debug(\"非法Token：{}\", bearerToken);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.DisposableBean;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.core.env.Environment;\nimport org.springframework.stereotype.Service;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author Jie\n * @date 2019-01-07\n */\n@Slf4j\n@SuppressWarnings({\"unchecked\",\"all\"})\npublic class SpringBeanHolder implements ApplicationContextAware, DisposableBean {\n\n    private static ApplicationContext applicationContext = null;\n    private static final List<SpringBeanHolder.CallBack> CALL_BACKS = new ArrayList<>();\n    private static boolean addCallback = true;\n\n    /**\n     * 针对 某些初始化方法，在SpringContextHolder 未初始化时 提交回调方法。\n     * 在SpringContextHolder 初始化后，进行回调使用\n     *\n     * @param callBack 回调函数\n     */\n    public synchronized static void addCallBacks(SpringBeanHolder.CallBack callBack) {\n        if (addCallback) {\n            SpringBeanHolder.CALL_BACKS.add(callBack);\n        } else {\n            log.warn(\"CallBack：{} 已无法添加！立即执行\", callBack.getCallBackName());\n            callBack.executor();\n        }\n    }\n\n    /**\n     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.\n     */\n    public static <T> T getBean(String name) {\n        assertContextInjected();\n        return (T) applicationContext.getBean(name);\n    }\n\n    /**\n     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.\n     */\n    public static <T> T getBean(Class<T> requiredType) {\n        assertContextInjected();\n        return applicationContext.getBean(requiredType);\n    }\n\n    /**\n     * 获取SpringBoot 配置信息\n     *\n     * @param property     属性key\n     * @param defaultValue 默认值\n     * @param requiredType 返回类型\n     * @return /\n     */\n    public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {\n        T result = defaultValue;\n        try {\n            result = getBean(Environment.class).getProperty(property, requiredType);\n        } catch (Exception ignored) {}\n        return result;\n    }\n\n    /**\n     * 获取SpringBoot 配置信息\n     *\n     * @param property 属性key\n     * @return /\n     */\n    public static String getProperties(String property) {\n        return getProperties(property, null, String.class);\n    }\n\n    /**\n     * 获取SpringBoot 配置信息\n     *\n     * @param property     属性key\n     * @param requiredType 返回类型\n     * @return /\n     */\n    public static <T> T getProperties(String property, Class<T> requiredType) {\n        return getProperties(property, null, requiredType);\n    }\n\n    /**\n     * 检查ApplicationContext不为空.\n     */\n    private static void assertContextInjected() {\n        if (applicationContext == null) {\n            throw new IllegalStateException(\"applicaitonContext属性未注入, 请在applicationContext\" +\n                    \".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.\");\n        }\n    }\n\n    /**\n     * 清除SpringContextHolder中的ApplicationContext为Null.\n     */\n    private static void clearHolder() {\n        log.debug(\"清除SpringContextHolder中的ApplicationContext:\"\n                + applicationContext);\n        applicationContext = null;\n    }\n\n    @Override\n    public void destroy() {\n        SpringBeanHolder.clearHolder();\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n        if (SpringBeanHolder.applicationContext != null) {\n            log.warn(\"SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:\" + SpringBeanHolder.applicationContext);\n        }\n        SpringBeanHolder.applicationContext = applicationContext;\n        if (addCallback) {\n            for (SpringBeanHolder.CallBack callBack : SpringBeanHolder.CALL_BACKS) {\n                callBack.executor();\n            }\n            CALL_BACKS.clear();\n        }\n        SpringBeanHolder.addCallback = false;\n    }\n\n    /**\n     * 获取 @Service 的所有 bean 名称\n     * @return /\n     */\n    public static List<String> getAllServiceBeanName() {\n        return new ArrayList<>(Arrays.asList(applicationContext\n                .getBeanNamesForAnnotation(Service.class)));\n    }\n\n    interface CallBack {\n\n        /**\n         * 回调执行方法\n         */\n        void executor();\n\n        /**\n         * 本回调任务名称\n         * @return /\n         */\n        default String getCallBackName() {\n            return Thread.currentThread().getId() + \":\" + this.getClass().getName();\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.http.useragent.UserAgent;\nimport cn.hutool.http.useragent.UserAgentUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport net.dreamlu.mica.ip2region.core.Ip2regionSearcher;\nimport net.dreamlu.mica.ip2region.core.IpInfo;\nimport javax.servlet.http.HttpServletRequest;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.UnknownHostException;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.Enumeration;\n\n/**\n * @author Zheng Jie\n * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类\n */\n@Slf4j\npublic class StringUtils extends org.apache.commons.lang3.StringUtils {\n\n    private static final char SEPARATOR = '_';\n    private static final String UNKNOWN = \"unknown\";\n\n    /**\n     * 注入bean\n     */\n    private final static Ip2regionSearcher IP_SEARCHER = SpringBeanHolder.getBean(Ip2regionSearcher.class);\n\n    /**\n     * 驼峰命名法工具\n     *\n     * @return toCamelCase(\" hello_world \") == \"helloWorld\"\n     * toCapitalizeCamelCase(\"hello_world\") == \"HelloWorld\"\n     * toUnderScoreCase(\"helloWorld\") = \"hello_world\"\n     */\n    public static String toCamelCase(String s) {\n        if (s == null) {\n            return null;\n        }\n\n        s = s.toLowerCase();\n\n        StringBuilder sb = new StringBuilder(s.length());\n        boolean upperCase = false;\n        for (int i = 0; i < s.length(); i++) {\n            char c = s.charAt(i);\n\n            if (c == SEPARATOR) {\n                upperCase = true;\n            } else if (upperCase) {\n                sb.append(Character.toUpperCase(c));\n                upperCase = false;\n            } else {\n                sb.append(c);\n            }\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * 驼峰命名法工具\n     *\n     * @return toCamelCase(\" hello_world \") == \"helloWorld\"\n     * toCapitalizeCamelCase(\"hello_world\") == \"HelloWorld\"\n     * toUnderScoreCase(\"helloWorld\") = \"hello_world\"\n     */\n    public static String toCapitalizeCamelCase(String s) {\n        if (s == null) {\n            return null;\n        }\n        s = toCamelCase(s);\n        return s.substring(0, 1).toUpperCase() + s.substring(1);\n    }\n\n    /**\n     * 驼峰命名法工具\n     *\n     * @return toCamelCase(\" hello_world \") == \"helloWorld\"\n     * toCapitalizeCamelCase(\"hello_world\") == \"HelloWorld\"\n     * toUnderScoreCase(\"helloWorld\") = \"hello_world\"\n     */\n    static String toUnderScoreCase(String s) {\n        if (s == null) {\n            return null;\n        }\n\n        StringBuilder sb = new StringBuilder();\n        boolean upperCase = false;\n        for (int i = 0; i < s.length(); i++) {\n            char c = s.charAt(i);\n\n            boolean nextUpperCase = true;\n\n            if (i < (s.length() - 1)) {\n                nextUpperCase = Character.isUpperCase(s.charAt(i + 1));\n            }\n\n            if ((i > 0) && Character.isUpperCase(c)) {\n                if (!upperCase || !nextUpperCase) {\n                    sb.append(SEPARATOR);\n                }\n                upperCase = true;\n            } else {\n                upperCase = false;\n            }\n\n            sb.append(Character.toLowerCase(c));\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * 获取ip地址\n     */\n    public static String getIp(HttpServletRequest request) {\n        String ip = request.getHeader(\"x-forwarded-for\");\n        if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"Proxy-Client-IP\");\n        }\n        if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"WL-Proxy-Client-IP\");\n        }\n        if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {\n            ip = request.getRemoteAddr();\n        }\n        String comma = \",\";\n        String localhost = \"127.0.0.1\";\n        if (ip.contains(comma)) {\n            ip = ip.split(\",\")[0];\n        }\n        if (localhost.equals(ip)) {\n            // 获取本机真正的ip地址\n            try {\n                ip = InetAddress.getLocalHost().getHostAddress();\n            } catch (UnknownHostException e) {\n                log.error(e.getMessage(), e);\n            }\n        }\n        return ip;\n    }\n\n    /**\n     * 根据ip获取详细地址\n     */\n    public static String getCityInfo(String ip) {\n        IpInfo ipInfo = IP_SEARCHER.memorySearch(ip);\n        if(ipInfo != null){\n            return ipInfo.getAddress();\n        }\n        return null;\n    }\n\n    /**\n     * 获取浏览器\n     */\n    public static String getBrowser(HttpServletRequest request) {\n        UserAgent ua = UserAgentUtil.parse(request.getHeader(\"User-Agent\"));\n        String browser = ua.getBrowser().toString() + \" \" + ua.getVersion();\n        return browser.replace(\".0.0.0\",\"\");\n    }\n\n    /**\n     * 获得当天是周几\n     */\n    public static String getWeekDay() {\n        String[] weekDays = {\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"};\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(new Date());\n\n        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;\n        if (w < 0) {\n            w = 0;\n        }\n        return weekDays[w];\n    }\n\n    /**\n     * 获取当前机器的IP\n     *\n     * @return /\n     */\n    public static String getLocalIp() {\n        try {\n            InetAddress candidateAddress = null;\n            // 遍历所有的网络接口\n            for (Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); interfaces.hasMoreElements();) {\n                NetworkInterface anInterface = interfaces.nextElement();\n                // 在所有的接口下再遍历IP\n                for (Enumeration<InetAddress> inetAddresses = anInterface.getInetAddresses(); inetAddresses.hasMoreElements();) {\n                    InetAddress inetAddr = inetAddresses.nextElement();\n                    // 排除loopback类型地址\n                    if (!inetAddr.isLoopbackAddress()) {\n                        if (inetAddr.isSiteLocalAddress()) {\n                            // 如果是site-local地址，就是它了\n                            return inetAddr.getHostAddress();\n                        } else if (candidateAddress == null) {\n                            // site-local类型的地址未被发现，先记录候选地址\n                            candidateAddress = inetAddr;\n                        }\n                    }\n                }\n            }\n            if (candidateAddress != null) {\n                return candidateAddress.getHostAddress();\n            }\n            // 如果没有发现 non-loopback地址.只能用最次选的方案\n            InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();\n            if (jdkSuppliedAddress == null) {\n                return \"\";\n            }\n            return jdkSuppliedAddress.getHostAddress();\n        } catch (Exception e) {\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\n/**\n * 异常工具 2019-01-06\n * @author Zheng Jie\n */\npublic class ThrowableUtil {\n\n    /**\n     * 获取堆栈信息\n     */\n    public static String getStackTrace(Throwable throwable){\n        StringWriter sw = new StringWriter();\n        try (PrintWriter pw = new PrintWriter(sw)) {\n            throwable.printStackTrace(pw);\n            return sw.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.core.lang.Validator;\nimport cn.hutool.core.util.ObjectUtil;\nimport me.zhengjie.exception.BadRequestException;\n\n/**\n * 验证工具\n *\n * @author Zheng Jie\n * @date 2018-11-23\n */\npublic class ValidationUtil {\n\n    /**\n     * 验证空\n     */\n    public static void isNull(Object obj, String entity, String parameter , Object value){\n        if(ObjectUtil.isNull(obj)){\n            String msg = entity + \" 不存在: \"+ parameter +\" is \"+ value;\n            throw new BadRequestException(msg);\n        }\n    }\n\n  /**\n   * 验证是否为邮箱\n   */\n  public static boolean isEmail(String email) {\n    return Validator.isEmail(email);\n  }\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * <p>\n * 验证码业务场景\n * </p>\n * @author Zheng Jie\n * @date 2020-05-02\n */\n@Getter\n@AllArgsConstructor\npublic enum CodeBiEnum {\n\n    /* 旧邮箱修改邮箱 */\n    ONE(1, \"旧邮箱修改邮箱\"),\n\n    /* 通过邮箱修改密码 */\n    TWO(2, \"通过邮箱修改密码\");\n\n    private final Integer code;\n    private final String description;\n\n    public static CodeBiEnum find(Integer code) {\n        for (CodeBiEnum value : CodeBiEnum.values()) {\n            if (value.getCode().equals(code)) {\n                return value;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * <p>\n * 验证码业务场景对应的 Redis 中的 key\n * </p>\n * @author Zheng Jie\n * @date 2020-05-02\n */\n@Getter\n@AllArgsConstructor\npublic enum CodeEnum {\n\n    /* 通过手机号码重置邮箱 */\n    PHONE_RESET_EMAIL_CODE(\"phone_reset_email_code_\", \"通过手机号码重置邮箱\"),\n\n    /* 通过旧邮箱重置邮箱 */\n    EMAIL_RESET_EMAIL_CODE(\"email_reset_email_code_\", \"通过旧邮箱重置邮箱\"),\n\n    /* 通过手机号码重置密码 */\n    PHONE_RESET_PWD_CODE(\"phone_reset_pwd_code_\", \"通过手机号码重置密码\"),\n\n    /* 通过邮箱重置密码 */\n    EMAIL_RESET_PWD_CODE(\"email_reset_pwd_code_\", \"通过邮箱重置密码\");\n\n    private final String key;\n    private final String description;\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * <p>\n * 数据权限枚举\n * </p>\n * @author Zheng Jie\n * @date 2020-05-07\n */\n@Getter\n@AllArgsConstructor\npublic enum DataScopeEnum {\n\n    /* 全部的数据权限 */\n    ALL(\"全部\", \"全部的数据权限\"),\n\n    /* 自己部门的数据权限 */\n    THIS_LEVEL(\"本级\", \"自己部门的数据权限\"),\n\n    /* 自定义的数据权限 */\n    CUSTOMIZE(\"自定义\", \"自定义的数据权限\");\n\n    private final String value;\n    private final String description;\n\n    public static DataScopeEnum find(String val) {\n        for (DataScopeEnum dataScopeEnum : DataScopeEnum.values()) {\n            if (dataScopeEnum.getValue().equals(val)) {\n                return dataScopeEnum;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author Zheng Jie\n * @website https://eladmin.vip\n * @description\n * @date 2020-06-10\n **/\n@Getter\n@AllArgsConstructor\npublic enum RequestMethodEnum {\n\n    /**\n     * 搜寻 @AnonymousGetMapping\n     */\n    GET(\"GET\"),\n\n    /**\n     * 搜寻 @AnonymousPostMapping\n     */\n    POST(\"POST\"),\n\n    /**\n     * 搜寻 @AnonymousPutMapping\n     */\n    PUT(\"PUT\"),\n\n    /**\n     * 搜寻 @AnonymousPatchMapping\n     */\n    PATCH(\"PATCH\"),\n\n    /**\n     * 搜寻 @AnonymousDeleteMapping\n     */\n    DELETE(\"DELETE\"),\n\n    /**\n     * 否则就是所有 Request 接口都放行\n     */\n    ALL(\"All\");\n\n    /**\n     * Request 类型\n     */\n    private final String type;\n\n    public static RequestMethodEnum find(String type) {\n        for (RequestMethodEnum value : RequestMethodEnum.values()) {\n            if (value.getType().equals(type)) {\n                return value;\n            }\n        }\n        return ALL;\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java",
    "content": "package me.zhengjie.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.time.LocalDateTime;\nimport java.util.Date;\n\npublic class DateUtilsTest {\n    @Test\n    public void test1() {\n        long l = System.currentTimeMillis() / 1000;\n        LocalDateTime localDateTime = DateUtil.fromTimeStamp(l);\n        System.out.print(DateUtil.localDateTimeFormatyMdHms(localDateTime));\n    }\n\n    @Test\n    public void test2() {\n        LocalDateTime now = LocalDateTime.now();\n        System.out.println(DateUtil.localDateTimeFormatyMdHms(now));\n        Date date = DateUtil.toDate(now);\n        LocalDateTime localDateTime = DateUtil.toLocalDateTime(date);\n        System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime));\n        LocalDateTime localDateTime1 = DateUtil.fromTimeStamp(date.getTime() / 1000);\n        System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime1));\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java",
    "content": "package me.zhengjie.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static me.zhengjie.utils.EncryptUtils.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class EncryptUtilsTest {\n\n    /**\n     * 对称加密\n     */\n    @Test\n    public void testDesEncrypt() {\n        try {\n            assertEquals(\"7772841DC6099402\", desEncrypt(\"123456\"));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 对称解密\n     */\n    @Test\n    public void testDesDecrypt() {\n        try {\n            assertEquals(\"123456\", desDecrypt(\"7772841DC6099402\"));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java",
    "content": "package me.zhengjie.utils;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.mock.web.MockMultipartFile;\n\nimport static me.zhengjie.utils.FileUtil.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class FileUtilTest {\n\n    @Test\n    public void testToFile() {\n        long retval = toFile(new MockMultipartFile(\"foo\", (byte[]) null)).getTotalSpace();\n        assertEquals(500695072768L, retval);\n    }\n\n    @Test\n    public void testGetExtensionName() {\n        assertEquals(\"foo\", getExtensionName(\"foo\"));\n        assertEquals(\"exe\", getExtensionName(\"bar.exe\"));\n    }\n\n    @Test\n    public void testGetFileNameNoEx() {\n        assertEquals(\"foo\", getFileNameNoEx(\"foo\"));\n        assertEquals(\"bar\", getFileNameNoEx(\"bar.txt\"));\n    }\n\n    @Test\n    public void testGetSize() {\n        assertEquals(\"1000B   \", getSize(1000));\n        assertEquals(\"1.00KB   \", getSize(1024));\n        assertEquals(\"1.00MB   \", getSize(1048576));\n        assertEquals(\"1.00GB   \", getSize(1073741824));\n    }\n}\n"
  },
  {
    "path": "eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java",
    "content": "package me.zhengjie.utils;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.mock.web.MockHttpServletRequest;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\nimport static me.zhengjie.utils.StringUtils.getIp;\nimport static me.zhengjie.utils.StringUtils.getWeekDay;\nimport static me.zhengjie.utils.StringUtils.toCamelCase;\nimport static me.zhengjie.utils.StringUtils.toCapitalizeCamelCase;\nimport static me.zhengjie.utils.StringUtils.toUnderScoreCase;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\npublic class StringUtilsTest {\n\n    @Test\n    public void testToCamelCase() {\n        assertNull(toCamelCase(null));\n    }\n\n    @Test\n    public void testToCapitalizeCamelCase() {\n        assertNull(StringUtils.toCapitalizeCamelCase(null));\n        assertEquals(\"HelloWorld\", toCapitalizeCamelCase(\"hello_world\"));\n    }\n\n    @Test\n    public void testToUnderScoreCase() {\n        assertNull(StringUtils.toUnderScoreCase(null));\n        assertEquals(\"hello_world\", toUnderScoreCase(\"helloWorld\"));\n        assertEquals(\"\\u0000\\u0000\", toUnderScoreCase(\"\\u0000\\u0000\"));\n        assertEquals(\"\\u0000_a\", toUnderScoreCase(\"\\u0000A\"));\n    }\n\n    @Test\n    public void testGetWeekDay() {\n        SimpleDateFormat simpleDateformat = new SimpleDateFormat(\"E\");\n        assertEquals(simpleDateformat.format(new Date()), getWeekDay());\n    }\n\n    @Test\n    public void testGetIP() {\n        assertEquals(\"127.0.0.1\", getIp(new MockHttpServletRequest()));\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/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\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>eladmin</artifactId>\n        <groupId>me.zhengjie</groupId>\n        <version>2.7</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>eladmin-generator</artifactId>\n    <name>代码生成模块</name>\n\n    <properties>\n        <configuration.version>1.10</configuration.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>me.zhengjie</groupId>\n            <artifactId>eladmin-common</artifactId>\n            <version>2.7</version>\n        </dependency>\n\n        <!--模板引擎-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-freemarker</artifactId>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/commons-configuration/commons-configuration -->\n        <dependency>\n            <groupId>commons-configuration</groupId>\n            <artifactId>commons-configuration</artifactId>\n            <version>${configuration.version}</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.NoArgsConstructor;\nimport lombok.Setter;\nimport me.zhengjie.utils.GenUtil;\nimport javax.persistence.*;\nimport java.io.Serializable;\n\n/**\n * 列的数据信息\n * @author Zheng Jie\n * @date 2019-01-02\n */\n@Getter\n@Setter\n@Entity\n@NoArgsConstructor\n@Table(name = \"code_column\")\npublic class ColumnInfo implements Serializable {\n\n    @Id\n    @Column(name = \"column_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @ApiModelProperty(value = \"表名\")\n    private String tableName;\n\n    @ApiModelProperty(value = \"数据库字段名称\")\n    private String columnName;\n\n    @ApiModelProperty(value = \"数据库字段类型\")\n    private String columnType;\n\n    @ApiModelProperty(value = \"数据库字段键类型\")\n    private String keyType;\n\n    @ApiModelProperty(value = \"字段额外的参数\")\n    private String extra;\n\n    @ApiModelProperty(value = \"数据库字段描述\")\n    private String remark;\n\n    @ApiModelProperty(value = \"是否必填\")\n    private Boolean notNull;\n\n    @ApiModelProperty(value = \"是否在列表显示\")\n    private Boolean listShow;\n\n    @ApiModelProperty(value = \"是否表单显示\")\n    private Boolean formShow;\n\n    @ApiModelProperty(value = \"表单类型\")\n    private String formType;\n\n    @ApiModelProperty(value = \"查询 1:模糊 2：精确\")\n    private String queryType;\n\n    @ApiModelProperty(value = \"字典名称\")\n    private String dictName;\n\n    @ApiModelProperty(value = \"日期注解\")\n    private String dateAnnotation;\n\n    public ColumnInfo(String tableName, String columnName, Boolean notNull, String columnType, String remark, String keyType, String extra) {\n        this.tableName = tableName;\n        this.columnName = columnName;\n        this.columnType = columnType;\n        this.keyType = keyType;\n        this.extra = extra;\n        this.notNull = notNull;\n        if(GenUtil.PK.equalsIgnoreCase(keyType) && GenUtil.EXTRA.equalsIgnoreCase(extra)){\n            this.notNull = false;\n        }\n        this.remark = remark;\n        this.listShow = true;\n        this.formShow = true;\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.NoArgsConstructor;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport java.io.Serializable;\n\n/**\n * 代码生成配置\n * @author Zheng Jie\n * @date 2019-01-03\n */\n@Getter\n@Setter\n@Entity\n@NoArgsConstructor\n@Table(name = \"code_config\")\npublic class GenConfig implements Serializable {\n\n    public GenConfig(String tableName) {\n        this.tableName = tableName;\n    }\n\n    @Id\n    @Column(name = \"config_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @NotBlank\n    @ApiModelProperty(value = \"表名\")\n    private String tableName;\n\n    @ApiModelProperty(value = \"接口名称\")\n    private String apiAlias;\n\n    @NotBlank\n    @ApiModelProperty(value = \"包路径\")\n    private String pack;\n\n    @NotBlank\n    @ApiModelProperty(value = \"模块名\")\n    private String moduleName;\n\n    @NotBlank\n    @ApiModelProperty(value = \"前端文件路径\")\n    private String path;\n\n    @ApiModelProperty(value = \"前端文件路径\")\n    private String apiPath;\n\n    @ApiModelProperty(value = \"作者\")\n    private String author;\n\n    @ApiModelProperty(value = \"表前缀\")\n    private String prefix;\n\n    @ApiModelProperty(value = \"是否覆盖\")\n    private Boolean cover = false;\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain.vo;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n/**\n * 表的数据信息\n * @author Zheng Jie\n * @date 2019-01-02\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class TableInfo {\n\n    @ApiModelProperty(value = \"表名称\")\n    private Object tableName;\n\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd HH:mm:ss\")\n    @ApiModelProperty(value = \"创建日期：yyyy-MM-dd HH:mm:ss\")\n    private Object createTime;\n\n    @ApiModelProperty(value = \"数据库引擎\")\n    private Object engine;\n\n    @ApiModelProperty(value = \"编码集\")\n    private Object coding;\n\n    @ApiModelProperty(value = \"备注\")\n    private Object remark;\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.ColumnInfo;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-14\n */\npublic interface ColumnInfoRepository extends JpaRepository<ColumnInfo,Long> {\n\n    /**\n     * 查询表信息\n     * @param tableName 表格名\n     * @return 表信息\n     */\n    List<ColumnInfo> findByTableNameOrderByIdAsc(String tableName);\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.GenConfig;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-14\n */\npublic interface GenConfigRepository extends JpaRepository<GenConfig,Long> {\n\n    /**\n     * 查询表配置\n     * @param tableName 表名\n     * @return /\n     */\n    GenConfig findByTableName(String tableName);\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.GenConfig;\nimport me.zhengjie.service.GenConfigService;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-14\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/genConfig\")\n@Api(tags = \"系统：代码生成器配置管理\")\npublic class GenConfigController {\n\n    private final GenConfigService genConfigService;\n\n    @ApiOperation(\"查询\")\n    @GetMapping(value = \"/{tableName}\")\n    public ResponseEntity<GenConfig> queryGenConfig(@PathVariable String tableName){\n        return new ResponseEntity<>(genConfigService.find(tableName), HttpStatus.OK);\n    }\n\n    @PutMapping\n    @ApiOperation(\"修改\")\n    public ResponseEntity<Object> updateGenConfig(@Validated @RequestBody GenConfig genConfig){\n        return new ResponseEntity<>(genConfigService.update(genConfig.getTableName(), genConfig),HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.ColumnInfo;\nimport me.zhengjie.domain.vo.TableInfo;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.service.GenConfigService;\nimport me.zhengjie.service.GeneratorService;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.PageUtil;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-02\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/generator\")\n@Api(tags = \"系统：代码生成管理\")\npublic class GeneratorController {\n\n    private final GeneratorService generatorService;\n    private final GenConfigService genConfigService;\n\n    @Value(\"${generator.enabled}\")\n    private Boolean generatorEnabled;\n\n    @ApiOperation(\"查询数据库数据\")\n    @GetMapping(value = \"/tables/all\")\n    public ResponseEntity<Object> queryAllTables(){\n        return new ResponseEntity<>(generatorService.getTables(), HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询数据库数据\")\n    @GetMapping(value = \"/tables\")\n    public ResponseEntity<PageResult<TableInfo>> queryTables(@RequestParam(defaultValue = \"\") String name,\n                                                             @RequestParam(defaultValue = \"0\")Integer page,\n                                                             @RequestParam(defaultValue = \"10\")Integer size){\n        int[] startEnd = PageUtil.transToStartEnd(page, size);\n        return new ResponseEntity<>(generatorService.getTables(name,startEnd), HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询字段数据\")\n    @GetMapping(value = \"/columns\")\n    public ResponseEntity<PageResult<ColumnInfo>> queryColumns(@RequestParam String tableName){\n        List<ColumnInfo> columnInfos = generatorService.getColumns(tableName);\n        return new ResponseEntity<>(PageUtil.toPage(columnInfos,columnInfos.size()), HttpStatus.OK);\n    }\n\n    @ApiOperation(\"保存字段数据\")\n    @PutMapping\n    public ResponseEntity<HttpStatus> saveColumn(@RequestBody List<ColumnInfo> columnInfos){\n        generatorService.save(columnInfos);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @ApiOperation(\"同步字段数据\")\n    @PostMapping(value = \"sync\")\n    public ResponseEntity<HttpStatus> syncColumn(@RequestBody List<String> tables){\n        for (String table : tables) {\n            generatorService.sync(generatorService.getColumns(table), generatorService.query(table));\n        }\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @ApiOperation(\"生成代码\")\n    @PostMapping(value = \"/{tableName}/{type}\")\n    public ResponseEntity<Object> generatorCode(@PathVariable String tableName, @PathVariable Integer type, HttpServletRequest request, HttpServletResponse response){\n        if(!generatorEnabled && type == 0){\n            throw new BadRequestException(\"此环境不允许生成代码，请选择预览或者下载查看！\");\n        }\n        switch (type){\n            // 生成代码\n            case 0: generatorService.generator(genConfigService.find(tableName), generatorService.getColumns(tableName));\n                    break;\n            // 预览\n            case 1: return generatorService.preview(genConfigService.find(tableName), generatorService.getColumns(tableName));\n            // 打包\n            case 2: generatorService.download(genConfigService.find(tableName), generatorService.getColumns(tableName), request, response);\n                    break;\n            default: throw new BadRequestException(\"没有这个选项\");\n        }\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.GenConfig;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-14\n */\npublic interface GenConfigService {\n\n    /**\n     * 查询表配置\n     * @param tableName 表名\n     * @return 表配置\n     */\n    GenConfig find(String tableName);\n\n    /**\n     * 更新表配置\n     * @param tableName 表名\n     * @param genConfig 表配置\n     * @return 表配置\n     */\n    GenConfig update(String tableName, GenConfig genConfig);\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.GenConfig;\nimport me.zhengjie.domain.ColumnInfo;\nimport me.zhengjie.domain.vo.TableInfo;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.http.ResponseEntity;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-02\n */\npublic interface GeneratorService {\n\n    /**\n     * 查询数据库元数据\n     * @param name 表名\n     * @param startEnd 分页参数\n     * @return /\n     */\n    PageResult<TableInfo> getTables(String name, int[] startEnd);\n\n    /**\n     * 得到数据表的元数据\n     * @param name 表名\n     * @return /\n     */\n    List<ColumnInfo> getColumns(String name);\n\n    /**\n     * 同步表数据\n     * @param columnInfos /\n     * @param columnInfoList /\n     */\n    void sync(List<ColumnInfo> columnInfos, List<ColumnInfo> columnInfoList);\n\n    /**\n     * 保持数据\n     * @param columnInfos /\n     */\n    void save(List<ColumnInfo> columnInfos);\n\n    /**\n     * 获取所有table\n     * @return /\n     */\n    Object getTables();\n\n    /**\n     * 代码生成\n     * @param genConfig 配置信息\n     * @param columns 字段信息\n     */\n    void generator(GenConfig genConfig, List<ColumnInfo> columns);\n\n    /**\n     * 预览\n     * @param genConfig 配置信息\n     * @param columns 字段信息\n     * @return /\n     */\n    ResponseEntity<Object> preview(GenConfig genConfig, List<ColumnInfo> columns);\n\n    /**\n     * 打包下载\n     * @param genConfig 配置信息\n     * @param columns 字段信息\n     * @param request /\n     * @param response /\n     */\n    void download(GenConfig genConfig, List<ColumnInfo> columns, HttpServletRequest request, HttpServletResponse response);\n\n    /**\n     * 查询数据库的表字段数据数据\n     * @param table /\n     * @return /\n     */\n    List<ColumnInfo> query(String table);\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.GenConfig;\nimport me.zhengjie.repository.GenConfigRepository;\nimport me.zhengjie.service.GenConfigService;\nimport org.springframework.stereotype.Service;\nimport java.io.File;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-14\n */\n@Service\n@RequiredArgsConstructor\n@SuppressWarnings({\"unchecked\",\"all\"})\npublic class GenConfigServiceImpl implements GenConfigService {\n\n    private final GenConfigRepository genConfigRepository;\n\n    @Override\n    public GenConfig find(String tableName) {\n        GenConfig genConfig = genConfigRepository.findByTableName(tableName);\n        if(genConfig == null){\n            return new GenConfig(tableName);\n        }\n        return genConfig;\n    }\n\n    @Override\n    public GenConfig update(String tableName, GenConfig genConfig) {\n        String separator = File.separator;\n        String[] paths;\n        String symbol = \"\\\\\";\n        if (symbol.equals(separator)) {\n            paths = genConfig.getPath().split(\"\\\\\\\\\");\n        } else {\n            paths = genConfig.getPath().split(File.separator);\n        }\n        StringBuilder api = new StringBuilder();\n        for (String path : paths) {\n            api.append(path);\n            api.append(separator);\n            if (\"src\".equals(path)) {\n                api.append(\"api\");\n                break;\n            }\n        }\n        genConfig.setApiPath(api.toString());\n        return genConfigRepository.save(genConfig);\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.ZipUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.GenConfig;\nimport me.zhengjie.domain.ColumnInfo;\nimport me.zhengjie.domain.vo.TableInfo;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.repository.ColumnInfoRepository;\nimport me.zhengjie.service.GeneratorService;\nimport me.zhengjie.utils.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Service;\nimport javax.persistence.EntityManager;\nimport javax.persistence.PersistenceContext;\nimport javax.persistence.Query;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-02\n */\n@Service\n@RequiredArgsConstructor\n@SuppressWarnings({\"unchecked\",\"all\"})\npublic class GeneratorServiceImpl implements GeneratorService {\n    private static final Logger log = LoggerFactory.getLogger(GeneratorServiceImpl.class);\n    @PersistenceContext\n    private EntityManager em;\n\n    private final ColumnInfoRepository columnInfoRepository;\n\n    private final String CONFIG_MESSAGE = \"请先配置生成器\";\n    @Override\n    public Object getTables() {\n        // 使用预编译防止sql注入\n        String sql = \"select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables \" +\n                \"where table_schema = (select database()) \" +\n                \"order by create_time desc\";\n        Query query = em.createNativeQuery(sql);\n        return query.getResultList();\n    }\n\n    @Override\n    public PageResult<TableInfo> getTables(String name, int[] startEnd) {\n        // 使用预编译防止sql注入\n        String sql = \"select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables \" +\n                \"where table_schema = (select database()) \" +\n                \"and table_name like :table order by create_time desc\";\n        Query query = em.createNativeQuery(sql);\n        query.setFirstResult(startEnd[0]);\n        query.setMaxResults(startEnd[1] - startEnd[0]);\n        query.setParameter(\"table\", StringUtils.isNotBlank(name) ? (\"%\" + name + \"%\") : \"%%\");\n        List result = query.getResultList();\n        List<TableInfo> tableInfos = new ArrayList<>();\n        for (Object obj : result) {\n            Object[] arr = (Object[]) obj;\n            tableInfos.add(new TableInfo(arr[0], arr[1], arr[2], arr[3], ObjectUtil.isNotEmpty(arr[4]) ? arr[4] : \"-\"));\n        }\n        String countSql = \"select count(1) from information_schema.tables \" +\n                \"where table_schema = (select database()) and table_name like :table\";\n        Query queryCount = em.createNativeQuery(countSql);\n        queryCount.setParameter(\"table\", StringUtils.isNotBlank(name) ? (\"%\" + name + \"%\") : \"%%\");\n        BigInteger totalElements = (BigInteger) queryCount.getSingleResult();\n        return PageUtil.toPage(tableInfos, totalElements.longValue());\n    }\n\n    @Override\n    public List<ColumnInfo> getColumns(String tableName) {\n        List<ColumnInfo> columnInfos = columnInfoRepository.findByTableNameOrderByIdAsc(tableName);\n        if (CollectionUtil.isNotEmpty(columnInfos)) {\n            return columnInfos;\n        } else {\n            columnInfos = query(tableName);\n            return columnInfoRepository.saveAll(columnInfos);\n        }\n    }\n\n    @Override\n    public List<ColumnInfo> query(String tableName) {\n        // 使用预编译防止sql注入\n        String sql = \"select column_name, is_nullable, data_type, column_comment, column_key, extra from information_schema.columns \" +\n                \"where table_name = ? and table_schema = (select database()) order by ordinal_position\";\n        Query query = em.createNativeQuery(sql);\n        query.setParameter(1, tableName);\n        List result = query.getResultList();\n        List<ColumnInfo> columnInfos = new ArrayList<>();\n        for (Object obj : result) {\n            Object[] arr = (Object[]) obj;\n            columnInfos.add(\n                    new ColumnInfo(\n                            tableName,\n                            arr[0].toString(),\n                            \"NO\".equals(arr[1]),\n                            arr[2].toString(),\n                            ObjectUtil.isNotNull(arr[3]) ? arr[3].toString() : null,\n                            ObjectUtil.isNotNull(arr[4]) ? arr[4].toString() : null,\n                            ObjectUtil.isNotNull(arr[5]) ? arr[5].toString() : null)\n            );\n        }\n        return columnInfos;\n    }\n\n    @Override\n    public void sync(List<ColumnInfo> columnInfos, List<ColumnInfo> columnInfoList) {\n        // 第一种情况，数据库类字段改变或者新增字段\n        for (ColumnInfo columnInfo : columnInfoList) {\n            // 根据字段名称查找\n            List<ColumnInfo> columns = columnInfos.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());\n            // 如果能找到，就修改部分可能被字段\n            if (CollectionUtil.isNotEmpty(columns)) {\n                ColumnInfo column = columns.get(0);\n                column.setColumnType(columnInfo.getColumnType());\n                column.setExtra(columnInfo.getExtra());\n                column.setKeyType(columnInfo.getKeyType());\n                if (StringUtils.isBlank(column.getRemark())) {\n                    column.setRemark(columnInfo.getRemark());\n                }\n                columnInfoRepository.save(column);\n            } else {\n                // 如果找不到，则保存新字段信息\n                columnInfoRepository.save(columnInfo);\n            }\n        }\n        // 第二种情况，数据库字段删除了\n        for (ColumnInfo columnInfo : columnInfos) {\n            // 根据字段名称查找\n            List<ColumnInfo> columns = columnInfoList.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());\n            // 如果找不到，就代表字段被删除了，则需要删除该字段\n            if (CollectionUtil.isEmpty(columns)) {\n                columnInfoRepository.delete(columnInfo);\n            }\n        }\n    }\n\n    @Override\n    public void save(List<ColumnInfo> columnInfos) {\n        columnInfoRepository.saveAll(columnInfos);\n    }\n\n    @Override\n    public void generator(GenConfig genConfig, List<ColumnInfo> columns) {\n        if (genConfig.getId() == null) {\n            throw new BadRequestException(CONFIG_MESSAGE);\n        }\n        try {\n            GenUtil.generatorCode(columns, genConfig);\n        } catch (IOException e) {\n            log.error(e.getMessage(), e);\n            throw new BadRequestException(\"生成失败，请手动处理已生成的文件\");\n        }\n    }\n\n    @Override\n    public ResponseEntity<Object> preview(GenConfig genConfig, List<ColumnInfo> columns) {\n        if (genConfig.getId() == null) {\n            throw new BadRequestException(CONFIG_MESSAGE);\n        }\n        List<Map<String, Object>> genList = GenUtil.preview(columns, genConfig);\n        return new ResponseEntity<>(genList, HttpStatus.OK);\n    }\n\n    @Override\n    public void download(GenConfig genConfig, List<ColumnInfo> columns, HttpServletRequest request, HttpServletResponse response) {\n        if (genConfig.getId() == null) {\n            throw new BadRequestException(CONFIG_MESSAGE);\n        }\n        try {\n            File file = new File(GenUtil.download(columns, genConfig));\n            String zipPath = file.getPath() + \".zip\";\n            ZipUtil.zip(file.getPath(), zipPath);\n            FileUtil.downloadFile(request, response, new File(zipPath), true);\n        } catch (IOException e) {\n            throw new BadRequestException(\"打包失败\");\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport org.apache.commons.configuration.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * sql字段转java\n *\n * @author Zheng Jie\n * @date 2019-01-03\n */\npublic class ColUtil {\n    private static final Logger log = LoggerFactory.getLogger(ColUtil.class);\n\n    /**\n     * 转换mysql数据类型为java数据类型\n     *\n     * @param type 数据库字段类型\n     * @return String\n     */\n    static String cloToJava(String type) {\n        Configuration config = getConfig();\n        assert config != null;\n        return config.getString(type, \"unknowType\");\n    }\n\n    /**\n     * 获取配置信息\n     */\n    public static PropertiesConfiguration getConfig() {\n        try {\n            return new PropertiesConfiguration(\"gen.properties\");\n        } catch (ConfigurationException e) {\n            log.error(e.getMessage(), e);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.template.*;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.domain.GenConfig;\nimport me.zhengjie.domain.ColumnInfo;\nimport org.springframework.util.ObjectUtils;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.time.LocalDate;\nimport java.util.*;\n\nimport static me.zhengjie.utils.FileUtil.SYS_TEM_DIR;\n\n/**\n * 代码生成\n *\n * @author Zheng Jie\n * @date 2019-01-02\n */\n@Slf4j\n@SuppressWarnings({\"unchecked\", \"all\"})\npublic class GenUtil {\n\n    private static final String TIMESTAMP = \"Timestamp\";\n\n    private static final String BIGDECIMAL = \"BigDecimal\";\n\n    public static final String PK = \"PRI\";\n\n    public static final String EXTRA = \"auto_increment\";\n\n    /**\n     * 获取后端代码模板名称\n     *\n     * @return List\n     */\n    private static List<String> getAdminTemplateNames() {\n        List<String> templateNames = new ArrayList<>();\n        templateNames.add(\"Entity\");\n        templateNames.add(\"Dto\");\n        templateNames.add(\"Mapper\");\n        templateNames.add(\"Controller\");\n        templateNames.add(\"QueryCriteria\");\n        templateNames.add(\"Service\");\n        templateNames.add(\"ServiceImpl\");\n        templateNames.add(\"Repository\");\n        return templateNames;\n    }\n\n    /**\n     * 获取前端代码模板名称\n     *\n     * @return List\n     */\n    private static List<String> getFrontTemplateNames() {\n        List<String> templateNames = new ArrayList<>();\n        templateNames.add(\"index\");\n        templateNames.add(\"api\");\n        return templateNames;\n    }\n\n    public static List<Map<String, Object>> preview(List<ColumnInfo> columns, GenConfig genConfig) {\n        Map<String, Object> genMap = getGenMap(columns, genConfig);\n        List<Map<String, Object>> genList = new ArrayList<>();\n        // 获取后端模版\n        List<String> templates = getAdminTemplateNames();\n        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(\"template\", TemplateConfig.ResourceMode.CLASSPATH));\n        for (String templateName : templates) {\n            Map<String, Object> map = new HashMap<>(1);\n            Template template = engine.getTemplate(\"admin/\" + templateName + \".ftl\");\n            map.put(\"content\", template.render(genMap));\n            map.put(\"name\", templateName);\n            genList.add(map);\n        }\n        // 获取前端模版\n        templates = getFrontTemplateNames();\n        for (String templateName : templates) {\n            Map<String, Object> map = new HashMap<>(1);\n            Template template = engine.getTemplate(\"front/\" + templateName + \".ftl\");\n            map.put(templateName, template.render(genMap));\n            map.put(\"content\", template.render(genMap));\n            map.put(\"name\", templateName);\n            genList.add(map);\n        }\n        return genList;\n    }\n\n    public static String download(List<ColumnInfo> columns, GenConfig genConfig) throws IOException {\n        // 拼接的路径：/tmpeladmin-gen-temp/，这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败，更改为： /tmp/eladmin-gen-temp/\n        // String tempPath =SYS_TEM_DIR + \"eladmin-gen-temp\" + File.separator + genConfig.getTableName() + File.separator;\n        String tempPath = SYS_TEM_DIR + \"eladmin-gen-temp\" + File.separator + genConfig.getTableName() + File.separator;\n        Map<String, Object> genMap = getGenMap(columns, genConfig);\n        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(\"template\", TemplateConfig.ResourceMode.CLASSPATH));\n        // 生成后端代码\n        List<String> templates = getAdminTemplateNames();\n        for (String templateName : templates) {\n            Template template = engine.getTemplate(\"admin/\" + templateName + \".ftl\");\n            String filePath = getAdminFilePath(templateName, genConfig, genMap.get(\"className\").toString(), tempPath + \"eladmin\" + File.separator);\n            assert filePath != null;\n            File file = new File(filePath);\n            // 如果非覆盖生成\n            if (!genConfig.getCover() && FileUtil.exist(file)) {\n                continue;\n            }\n            // 生成代码\n            genFile(file, template, genMap);\n        }\n        // 生成前端代码\n        templates = getFrontTemplateNames();\n        for (String templateName : templates) {\n            Template template = engine.getTemplate(\"front/\" + templateName + \".ftl\");\n            String path = tempPath + \"eladmin-web\" + File.separator;\n            String apiPath = path + \"src\" + File.separator + \"api\" + File.separator;\n            String srcPath = path + \"src\" + File.separator + \"views\" + File.separator + genMap.get(\"changeClassName\").toString() + File.separator;\n            String filePath = getFrontFilePath(templateName, apiPath, srcPath, genMap.get(\"changeClassName\").toString());\n            assert filePath != null;\n            File file = new File(filePath);\n            // 如果非覆盖生成\n            if (!genConfig.getCover() && FileUtil.exist(file)) {\n                continue;\n            }\n            // 生成代码\n            genFile(file, template, genMap);\n        }\n        return tempPath;\n    }\n\n    public static void generatorCode(List<ColumnInfo> columnInfos, GenConfig genConfig) throws IOException {\n        Map<String, Object> genMap = getGenMap(columnInfos, genConfig);\n        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(\"template\", TemplateConfig.ResourceMode.CLASSPATH));\n        // 生成后端代码\n        List<String> templates = getAdminTemplateNames();\n        for (String templateName : templates) {\n            Template template = engine.getTemplate(\"admin/\" + templateName + \".ftl\");\n            String rootPath = System.getProperty(\"user.dir\");\n            String filePath = getAdminFilePath(templateName, genConfig, genMap.get(\"className\").toString(), rootPath);\n\n            assert filePath != null;\n            File file = new File(filePath);\n\n            // 如果非覆盖生成\n            if (!genConfig.getCover() && FileUtil.exist(file)) {\n                continue;\n            }\n            // 生成代码\n            genFile(file, template, genMap);\n        }\n\n        // 生成前端代码\n        templates = getFrontTemplateNames();\n        for (String templateName : templates) {\n            Template template = engine.getTemplate(\"front/\" + templateName + \".ftl\");\n            String filePath = getFrontFilePath(templateName, genConfig.getApiPath(), genConfig.getPath(), genMap.get(\"changeClassName\").toString());\n\n            assert filePath != null;\n            File file = new File(filePath);\n\n            // 如果非覆盖生成\n            if (!genConfig.getCover() && FileUtil.exist(file)) {\n                continue;\n            }\n            // 生成代码\n            genFile(file, template, genMap);\n        }\n    }\n\n    // 获取模版数据\n    private static Map<String, Object> getGenMap(List<ColumnInfo> columnInfos, GenConfig genConfig) {\n        // 存储模版字段数据\n        Map<String, Object> genMap = new HashMap<>(16);\n        // 接口别名\n        genMap.put(\"apiAlias\", genConfig.getApiAlias());\n        // 包名称\n        genMap.put(\"package\", genConfig.getPack());\n        // 模块名称\n        genMap.put(\"moduleName\", genConfig.getModuleName());\n        // 作者\n        genMap.put(\"author\", genConfig.getAuthor());\n        // 创建日期\n        genMap.put(\"date\", LocalDate.now().toString());\n        // 表名\n        genMap.put(\"tableName\", genConfig.getTableName());\n        // 大写开头的类名\n        String className = StringUtils.toCapitalizeCamelCase(genConfig.getTableName());\n        // 小写开头的类名\n        String changeClassName = StringUtils.toCamelCase(genConfig.getTableName());\n        // 判断是否去除表前缀\n        if (StringUtils.isNotEmpty(genConfig.getPrefix())) {\n            className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(genConfig.getTableName(), genConfig.getPrefix()));\n            changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(genConfig.getTableName(), genConfig.getPrefix()));\n            changeClassName = StringUtils.uncapitalize(changeClassName);\n        }\n        // 保存类名\n        genMap.put(\"className\", className);\n        // 保存小写开头的类名\n        genMap.put(\"changeClassName\", changeClassName);\n        // 存在 Timestamp 字段\n        genMap.put(\"hasTimestamp\", false);\n        // 查询类中存在 Timestamp 字段\n        genMap.put(\"queryHasTimestamp\", false);\n        // 存在 BigDecimal 字段\n        genMap.put(\"hasBigDecimal\", false);\n        // 查询类中存在 BigDecimal 字段\n        genMap.put(\"queryHasBigDecimal\", false);\n        // 是否需要创建查询\n        genMap.put(\"hasQuery\", false);\n        // 自增主键\n        genMap.put(\"auto\", false);\n        // 存在字典\n        genMap.put(\"hasDict\", false);\n        // 存在日期注解\n        genMap.put(\"hasDateAnnotation\", false);\n        // 保存字段信息\n        List<Map<String, Object>> columns = new ArrayList<>();\n        // 保存查询字段的信息\n        List<Map<String, Object>> queryColumns = new ArrayList<>();\n        // 存储字典信息\n        List<String> dicts = new ArrayList<>();\n        // 存储 between 信息\n        List<Map<String, Object>> betweens = new ArrayList<>();\n        // 存储不为空的字段信息\n        List<Map<String, Object>> isNotNullColumns = new ArrayList<>();\n\n        for (ColumnInfo column : columnInfos) {\n            Map<String, Object> listMap = new HashMap<>(16);\n            // 字段描述\n            listMap.put(\"remark\", column.getRemark());\n            // 字段类型\n            listMap.put(\"columnKey\", column.getKeyType());\n            // 主键类型\n            String colType = ColUtil.cloToJava(column.getColumnType());\n            // 小写开头的字段名\n            String changeColumnName = StringUtils.toCamelCase(column.getColumnName());\n            // 大写开头的字段名\n            String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getColumnName());\n            if (PK.equals(column.getKeyType())) {\n                // 存储主键类型\n                genMap.put(\"pkColumnType\", colType);\n                // 存储小写开头的字段名\n                genMap.put(\"pkChangeColName\", changeColumnName);\n                // 存储大写开头的字段名\n                genMap.put(\"pkCapitalColName\", capitalColumnName);\n            }\n            // 是否存在 Timestamp 类型的字段\n            if (TIMESTAMP.equals(colType)) {\n                genMap.put(\"hasTimestamp\", true);\n            }\n            // 是否存在 BigDecimal 类型的字段\n            if (BIGDECIMAL.equals(colType)) {\n                genMap.put(\"hasBigDecimal\", true);\n            }\n            // 主键是否自增\n            if (EXTRA.equals(column.getExtra())) {\n                genMap.put(\"auto\", true);\n            }\n            // 主键存在字典\n            if (StringUtils.isNotBlank(column.getDictName())) {\n                genMap.put(\"hasDict\", true);\n                if(!dicts.contains(column.getDictName()))\n                    dicts.add(column.getDictName());\n            }\n\n            // 存储字段类型\n            listMap.put(\"columnType\", colType);\n            // 存储字原始段名称\n            listMap.put(\"columnName\", column.getColumnName());\n            // 不为空\n            listMap.put(\"istNotNull\", column.getNotNull());\n            // 字段列表显示\n            listMap.put(\"columnShow\", column.getListShow());\n            // 表单显示\n            listMap.put(\"formShow\", column.getFormShow());\n            // 表单组件类型\n            listMap.put(\"formType\", StringUtils.isNotBlank(column.getFormType()) ? column.getFormType() : \"Input\");\n            // 小写开头的字段名称\n            listMap.put(\"changeColumnName\", changeColumnName);\n            //大写开头的字段名称\n            listMap.put(\"capitalColumnName\", capitalColumnName);\n            // 字典名称\n            listMap.put(\"dictName\", column.getDictName());\n            // 日期注解\n            listMap.put(\"dateAnnotation\", column.getDateAnnotation());\n            if (StringUtils.isNotBlank(column.getDateAnnotation())) {\n                genMap.put(\"hasDateAnnotation\", true);\n            }\n            // 添加非空字段信息\n            if (column.getNotNull()) {\n                isNotNullColumns.add(listMap);\n            }\n            // 判断是否有查询，如有则把查询的字段set进columnQuery\n            if (!StringUtils.isBlank(column.getQueryType())) {\n                // 查询类型\n                listMap.put(\"queryType\", column.getQueryType());\n                // 是否存在查询\n                genMap.put(\"hasQuery\", true);\n                if (TIMESTAMP.equals(colType)) {\n                    // 查询中存储 Timestamp 类型\n                    genMap.put(\"queryHasTimestamp\", true);\n                }\n                if (BIGDECIMAL.equals(colType)) {\n                    // 查询中存储 BigDecimal 类型\n                    genMap.put(\"queryHasBigDecimal\", true);\n                }\n                if (\"between\".equalsIgnoreCase(column.getQueryType())) {\n                    betweens.add(listMap);\n                } else {\n                    // 添加到查询列表中\n                    queryColumns.add(listMap);\n                }\n            }\n            // 添加到字段列表中\n            columns.add(listMap);\n        }\n        // 保存字段列表\n        genMap.put(\"columns\", columns);\n        // 保存查询列表\n        genMap.put(\"queryColumns\", queryColumns);\n        // 保存字段列表\n        genMap.put(\"dicts\", dicts);\n        // 保存查询列表\n        genMap.put(\"betweens\", betweens);\n        // 保存非空字段信息\n        genMap.put(\"isNotNullColumns\", isNotNullColumns);\n        return genMap;\n    }\n\n    /**\n     * 定义后端文件路径以及名称\n     */\n    private static String getAdminFilePath(String templateName, GenConfig genConfig, String className, String rootPath) {\n        String projectPath = rootPath + File.separator + genConfig.getModuleName();\n        String packagePath = projectPath + File.separator + \"src\" + File.separator + \"main\" + File.separator + \"java\" + File.separator;\n        if (!ObjectUtils.isEmpty(genConfig.getPack())) {\n            packagePath += genConfig.getPack().replace(\".\", File.separator) + File.separator;\n        }\n\n        if (\"Entity\".equals(templateName)) {\n            return packagePath + \"domain\" + File.separator + className + \".java\";\n        }\n\n        if (\"Controller\".equals(templateName)) {\n            return packagePath + \"rest\" + File.separator + className + \"Controller.java\";\n        }\n\n        if (\"Service\".equals(templateName)) {\n            return packagePath + \"service\" + File.separator + className + \"Service.java\";\n        }\n\n        if (\"ServiceImpl\".equals(templateName)) {\n            return packagePath + \"service\" + File.separator + \"impl\" + File.separator + className + \"ServiceImpl.java\";\n        }\n\n        if (\"Dto\".equals(templateName)) {\n            return packagePath + \"service\" + File.separator + \"dto\" + File.separator + className + \"Dto.java\";\n        }\n\n        if (\"QueryCriteria\".equals(templateName)) {\n            return packagePath + \"service\" + File.separator + \"dto\" + File.separator + className + \"QueryCriteria.java\";\n        }\n\n        if (\"Mapper\".equals(templateName)) {\n            return packagePath + \"service\" + File.separator + \"mapstruct\" + File.separator + className + \"Mapper.java\";\n        }\n\n        if (\"Repository\".equals(templateName)) {\n            return packagePath + \"repository\" + File.separator + className + \"Repository.java\";\n        }\n\n        return null;\n    }\n\n    /**\n     * 定义前端文件路径以及名称\n     */\n    private static String getFrontFilePath(String templateName, String apiPath, String path, String apiName) {\n\n        if (\"api\".equals(templateName)) {\n            return apiPath + File.separator + apiName + \".js\";\n        }\n\n        if (\"index\".equals(templateName)) {\n            return path + File.separator + \"index.vue\";\n        }\n\n        return null;\n    }\n\n    private static void genFile(File file, Template template, Map<String, Object> map) throws IOException {\n        // 生成目标文件\n        Writer writer = null;\n        try {\n            FileUtil.touch(file);\n            writer = new FileWriter(file);\n            template.render(map, writer);\n        } catch (TemplateException | IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            assert writer != null;\n            writer.close();\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/resources/gen.properties",
    "content": "# Database type to Java type\ntinyint=Integer\nsmallint=Integer\nmediumint=Integer\nint=Integer\ninteger=Integer\n\nbigint=Long\n\nfloat=Float\n\ndouble=Double\n\ndecimal=BigDecimal\n\nbit=Boolean\n\nchar=String\nvarchar=String\ntinytext=String\ntext=String\nmediumtext=String\nlongtext=String\n\ndate=Timestamp\ndatetime=Timestamp\ntimestamp=Timestamp"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/Controller.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.rest;\n\nimport me.zhengjie.annotation.Log;\nimport ${package}.domain.${className};\nimport ${package}.service.${className}Service;\nimport ${package}.service.dto.${className}QueryCriteria;\nimport org.springframework.data.domain.Pageable;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport io.swagger.annotations.*;\nimport java.io.IOException;\nimport javax.servlet.http.HttpServletResponse;\nimport me.zhengjie.utils.PageResult;\nimport ${package}.service.dto.${className}Dto;\n\n/**\n* @website https://eladmin.vip\n* @author ${author}\n* @date ${date}\n**/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"${apiAlias}\")\n@RequestMapping(\"/api/${changeClassName}\")\npublic class ${className}Controller {\n\n    private final ${className}Service ${changeClassName}Service;\n\n    @ApiOperation(\"导出数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('${changeClassName}:list')\")\n    public void export${className}(HttpServletResponse response, ${className}QueryCriteria criteria) throws IOException {\n        ${changeClassName}Service.download(${changeClassName}Service.queryAll(criteria), response);\n    }\n\n    @GetMapping\n    @ApiOperation(\"查询${apiAlias}\")\n    @PreAuthorize(\"@el.check('${changeClassName}:list')\")\n    public ResponseEntity<PageResult<${className}Dto>> query${className}(${className}QueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(${changeClassName}Service.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @PostMapping\n    @Log(\"新增${apiAlias}\")\n    @ApiOperation(\"新增${apiAlias}\")\n    @PreAuthorize(\"@el.check('${changeClassName}:add')\")\n    public ResponseEntity<Object> create${className}(@Validated @RequestBody ${className} resources){\n        ${changeClassName}Service.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @PutMapping\n    @Log(\"修改${apiAlias}\")\n    @ApiOperation(\"修改${apiAlias}\")\n    @PreAuthorize(\"@el.check('${changeClassName}:edit')\")\n    public ResponseEntity<Object> update${className}(@Validated @RequestBody ${className} resources){\n        ${changeClassName}Service.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @DeleteMapping\n    @Log(\"删除${apiAlias}\")\n    @ApiOperation(\"删除${apiAlias}\")\n    @PreAuthorize(\"@el.check('${changeClassName}:del')\")\n    public ResponseEntity<Object> delete${className}(@ApiParam(value = \"传ID数组[]\") @RequestBody ${pkColumnType}[] ids) {\n        ${changeClassName}Service.deleteAll(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/Dto.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.service.dto;\n\nimport lombok.Data;\n<#if hasTimestamp>\nimport java.sql.Timestamp;\n</#if>\n<#if hasBigDecimal>\nimport java.math.BigDecimal;\n</#if>\nimport java.io.Serializable;\n<#if !auto && pkColumnType = 'Long'>\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport com.alibaba.fastjson2.serializer.ToStringSerializer;\n</#if>\nimport io.swagger.annotations.ApiModelProperty;\n\n/**\n* @website https://eladmin.vip\n* @description /\n* @author ${author}\n* @date ${date}\n**/\n@Data\npublic class ${className}Dto implements Serializable {\n<#if columns??>\n    <#list columns as column>\n\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    <#if column.columnKey = 'PRI'>\n    <#if !auto && pkColumnType = 'Long'>\n    /** 防止精度丢失 */\n    @JSONField(serializeUsing = ToStringSerializer.class)\n    </#if>\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n    </#list>\n</#if>\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/Entity.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.domain;\n\nimport lombok.Data;\nimport cn.hutool.core.bean.BeanUtil;\nimport io.swagger.annotations.ApiModelProperty;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport javax.persistence.*;\n<#if isNotNullColumns??>\nimport javax.validation.constraints.*;\n</#if>\n<#if hasDateAnnotation>\nimport javax.persistence.Entity;\nimport javax.persistence.Table;\nimport org.hibernate.annotations.*;\n</#if>\n<#if hasTimestamp>\nimport java.sql.Timestamp;\n</#if>\n<#if hasBigDecimal>\nimport java.math.BigDecimal;\n</#if>\n<#assign notBlankUsed = false>\n<#assign notNullUsed = false>\n<#if columns??>\n    <#list columns as column>\n        <#if column.istNotNull && column.columnKey != 'PRI'>\n            <#if column.columnType = 'String'>\n                <#assign notBlankUsed = true>\n            <#else>\n                <#assign notNullUsed = true>\n            </#if>\n        </#if>\n    </#list>\n</#if>\n<#if notBlankUsed>\nimport javax.validation.constraints.NotBlank;\n</#if>\n<#if notNullUsed>\nimport javax.validation.constraints.NotNull;\n</#if>\nimport java.io.Serializable;\n\n/**\n* @website https://eladmin.vip\n* @description /\n* @author ${author}\n* @date ${date}\n**/\n@Entity\n@Data\n@Table(name=\"${tableName}\")\npublic class ${className} implements Serializable {\n<#if columns??>\n    <#list columns as column>\n\n    <#if column.columnKey = 'PRI'>\n    @Id\n    <#if auto>\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    </#if>\n    </#if>\n    @Column(name = \"`${column.columnName}`\"<#if column.columnKey = 'UNI'>,unique = true</#if><#if column.istNotNull && column.columnKey != 'PRI'>,nullable = false</#if>)\n    <#if column.istNotNull && column.columnKey != 'PRI'>\n        <#if column.columnType = 'String'>\n    @NotBlank\n        <#else>\n    @NotNull\n        </#if>\n    </#if>\n    <#if (column.dateAnnotation)?? && column.dateAnnotation != ''>\n    <#if column.dateAnnotation = 'CreationTimestamp'>\n    @CreationTimestamp\n    <#else>\n    @UpdateTimestamp\n    </#if>\n    </#if>\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n    </#list>\n</#if>\n\n    public void copy(${className} source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}\n"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/Mapper.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport ${package}.domain.${className};\nimport ${package}.service.dto.${className}Dto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @website https://eladmin.vip\n* @author ${author}\n* @date ${date}\n**/\n@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface ${className}Mapper extends BaseMapper<${className}Dto, ${className}> {\n\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/QueryCriteria.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.service.dto;\n\nimport lombok.Data;\n<#if queryHasTimestamp>\nimport java.sql.Timestamp;\n</#if>\n<#if queryHasBigDecimal>\nimport java.math.BigDecimal;\n</#if>\n<#if betweens??>\nimport java.util.List;\n</#if>\n<#if queryColumns??>\nimport me.zhengjie.annotation.Query;\n</#if>\nimport io.swagger.annotations.ApiModelProperty;\n\n/**\n* @website https://eladmin.vip\n* @author ${author}\n* @date ${date}\n**/\n@Data\npublic class ${className}QueryCriteria{\n<#if queryColumns??>\n    <#list queryColumns as column>\n\n<#if column.queryType = '='>\n    /** 精确 */\n    @Query\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n</#if>\n<#if column.queryType = 'Like'>\n    /** 模糊 */\n    @Query(type = Query.Type.INNER_LIKE)\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n</#if>\n<#if column.queryType = '!='>\n    /** 不等于 */\n    @Query(type = Query.Type.NOT_EQUAL)\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n</#if>\n<#if column.queryType = 'NotNull'>\n    /** 不为空 */\n    @Query(type = Query.Type.NOT_NULL)\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n</#if>\n<#if column.queryType = '>='>\n    /** 大于等于 */\n    @Query(type = Query.Type.GREATER_THAN)\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n</#if>\n<#if column.queryType = '<='>\n    /** 小于等于 */\n    @Query(type = Query.Type.LESS_THAN)\n    <#if column.remark != ''>\n    @ApiModelProperty(value = \"${column.remark}\")\n    <#else>\n    @ApiModelProperty(value = \"${column.changeColumnName}\")\n    </#if>\n    private ${column.columnType} ${column.changeColumnName};\n</#if>\n    </#list>\n</#if>\n<#if betweens??>\n    <#list betweens as column>\n    /** BETWEEN */\n    @Query(type = Query.Type.BETWEEN)\n    private List<${column.columnType}> ${column.changeColumnName};\n    </#list>\n</#if>\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/Repository.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.repository;\n\nimport ${package}.domain.${className};\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @website https://eladmin.vip\n* @author ${author}\n* @date ${date}\n**/\npublic interface ${className}Repository extends JpaRepository<${className}, ${pkColumnType}>, JpaSpecificationExecutor<${className}> {\n<#if columns??>\n    <#list columns as column>\n        <#if column.columnKey = 'UNI'>\n    /**\n    * 根据 ${column.capitalColumnName} 查询\n    * @param ${column.columnName} /\n    * @return /\n    */\n    ${className} findBy${column.capitalColumnName}(${column.columnType} ${column.columnName});\n        </#if>\n    </#list>\n</#if>\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/Service.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.service;\n\nimport ${package}.domain.${className};\nimport ${package}.service.dto.${className}Dto;\nimport ${package}.service.dto.${className}QueryCriteria;\nimport org.springframework.data.domain.Pageable;\nimport java.util.Map;\nimport java.util.List;\nimport java.io.IOException;\nimport javax.servlet.http.HttpServletResponse;\nimport me.zhengjie.utils.PageResult;\n\n/**\n* @website https://eladmin.vip\n* @description 服务接口\n* @author ${author}\n* @date ${date}\n**/\npublic interface ${className}Service {\n\n    /**\n    * 查询数据分页\n    * @param criteria 条件\n    * @param pageable 分页参数\n    * @return Map<String,Object>\n    */\n    PageResult<${className}Dto> queryAll(${className}QueryCriteria criteria, Pageable pageable);\n\n    /**\n    * 查询所有数据不分页\n    * @param criteria 条件参数\n    * @return List<${className}Dto>\n    */\n    List<${className}Dto> queryAll(${className}QueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param ${pkChangeColName} ID\n     * @return ${className}Dto\n     */\n    ${className}Dto findById(${pkColumnType} ${pkChangeColName});\n\n    /**\n    * 创建\n    * @param resources /\n    */\n    void create(${className} resources);\n\n    /**\n    * 编辑\n    * @param resources /\n    */\n    void update(${className} resources);\n\n    /**\n    * 多选删除\n    * @param ids /\n    */\n    void deleteAll(${pkColumnType}[] ids);\n\n    /**\n    * 导出数据\n    * @param all 待导出的数据\n    * @param response /\n    * @throws IOException /\n    */\n    void download(List<${className}Dto> all, HttpServletResponse response) throws IOException;\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/admin/ServiceImpl.ftl",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 ${package}.service.impl;\n\nimport ${package}.domain.${className};\n<#if columns??>\n    <#list columns as column>\n        <#if column.columnKey = 'UNI'>\n            <#if column_index = 1>\nimport me.zhengjie.exception.EntityExistException;\n            </#if>\n        </#if>\n    </#list>\n</#if>\nimport me.zhengjie.utils.ValidationUtil;\nimport me.zhengjie.utils.FileUtil;\nimport lombok.RequiredArgsConstructor;\nimport ${package}.repository.${className}Repository;\nimport ${package}.service.${className}Service;\nimport ${package}.service.dto.${className}Dto;\nimport ${package}.service.dto.${className}QueryCriteria;\nimport ${package}.service.mapstruct.${className}Mapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n<#if !auto && pkColumnType = 'Long'>\nimport cn.hutool.core.lang.Snowflake;\nimport cn.hutool.core.util.IdUtil;\n</#if>\n<#if !auto && pkColumnType = 'String'>\nimport cn.hutool.core.util.IdUtil;\n</#if>\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport me.zhengjie.utils.PageUtil;\nimport me.zhengjie.utils.QueryHelp;\nimport java.util.List;\nimport java.util.Map;\nimport java.io.IOException;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport me.zhengjie.utils.PageResult;\n\n/**\n* @website https://eladmin.vip\n* @description 服务实现\n* @author ${author}\n* @date ${date}\n**/\n@Service\n@RequiredArgsConstructor\npublic class ${className}ServiceImpl implements ${className}Service {\n\n    private final ${className}Repository ${changeClassName}Repository;\n    private final ${className}Mapper ${changeClassName}Mapper;\n\n    @Override\n    public PageResult<${className}Dto> queryAll(${className}QueryCriteria criteria, Pageable pageable){\n        Page<${className}> page = ${changeClassName}Repository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(${changeClassName}Mapper::toDto));\n    }\n\n    @Override\n    public List<${className}Dto> queryAll(${className}QueryCriteria criteria){\n        return ${changeClassName}Mapper.toDto(${changeClassName}Repository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));\n    }\n\n    @Override\n    @Transactional\n    public ${className}Dto findById(${pkColumnType} ${pkChangeColName}) {\n        ${className} ${changeClassName} = ${changeClassName}Repository.findById(${pkChangeColName}).orElseGet(${className}::new);\n        ValidationUtil.isNull(${changeClassName}.get${pkCapitalColName}(),\"${className}\",\"${pkChangeColName}\",${pkChangeColName});\n        return ${changeClassName}Mapper.toDto(${changeClassName});\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(${className} resources) {\n<#if !auto && pkColumnType = 'Long'>\n        Snowflake snowflake = IdUtil.createSnowflake(1, 1);\n        resources.set${pkCapitalColName}(snowflake.nextId()); \n</#if>\n<#if !auto && pkColumnType = 'String'>\n        resources.set${pkCapitalColName}(IdUtil.simpleUUID()); \n</#if>\n<#if columns??>\n    <#list columns as column>\n    <#if column.columnKey = 'UNI'>\n        if(${changeClassName}Repository.findBy${column.capitalColumnName}(resources.get${column.capitalColumnName}()) != null){\n            throw new EntityExistException(${className}.class,\"${column.columnName}\",resources.get${column.capitalColumnName}());\n        }\n    </#if>\n    </#list>\n</#if>\n        ${changeClassName}Repository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(${className} resources) {\n        ${className} ${changeClassName} = ${changeClassName}Repository.findById(resources.get${pkCapitalColName}()).orElseGet(${className}::new);\n        ValidationUtil.isNull( ${changeClassName}.get${pkCapitalColName}(),\"${className}\",\"id\",resources.get${pkCapitalColName}());\n<#if columns??>\n    <#list columns as column>\n        <#if column.columnKey = 'UNI'>\n        <#if column_index = 1>\n        ${className} ${changeClassName}1 = null;\n        </#if>\n        ${changeClassName}1 = ${changeClassName}Repository.findBy${column.capitalColumnName}(resources.get${column.capitalColumnName}());\n        if(${changeClassName}1 != null && !${changeClassName}1.get${pkCapitalColName}().equals(${changeClassName}.get${pkCapitalColName}())){\n            throw new EntityExistException(${className}.class,\"${column.columnName}\",resources.get${column.capitalColumnName}());\n        }\n        </#if>\n    </#list>\n</#if>\n        ${changeClassName}.copy(resources);\n        ${changeClassName}Repository.save(${changeClassName});\n    }\n\n    @Override\n    public void deleteAll(${pkColumnType}[] ids) {\n        for (${pkColumnType} ${pkChangeColName} : ids) {\n            ${changeClassName}Repository.deleteById(${pkChangeColName});\n        }\n    }\n\n    @Override\n    public void download(List<${className}Dto> all, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (${className}Dto ${changeClassName} : all) {\n            Map<String,Object> map = new LinkedHashMap<>();\n        <#list columns as column>\n            <#if column.columnKey != 'PRI'>\n            <#if column.remark != ''>\n            map.put(\"${column.remark}\", ${changeClassName}.get${column.capitalColumnName}());\n            <#else>\n            map.put(\" ${column.changeColumnName}\",  ${changeClassName}.get${column.capitalColumnName}());\n            </#if>\n            </#if>\n        </#list>\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/front/api.ftl",
    "content": "import request from '@/utils/request'\n\nexport function add(data) {\n  return request({\n    url: 'api/${changeClassName}',\n    method: 'post',\n    data\n  })\n}\n\nexport function del(ids) {\n  return request({\n    url: 'api/${changeClassName}/',\n    method: 'delete',\n    data: ids\n  })\n}\n\nexport function edit(data) {\n  return request({\n    url: 'api/${changeClassName}',\n    method: 'put',\n    data\n  })\n}\n\nexport default { add, edit, del }\n"
  },
  {
    "path": "eladmin-generator/src/main/resources/template/front/index.ftl",
    "content": "<#--noinspection ALL-->\n<template>\n  <div class=\"app-container\">\n    <!--工具栏-->\n    <div class=\"head-container\">\n    <#if hasQuery>\n      <div v-if=\"crud.props.searchToggle\">\n        <!-- 搜索 -->\n        <#if queryColumns??>\n          <#list queryColumns as column>\n            <#if column.queryType != 'BetWeen'>\n        <label class=\"el-form-item-label\"><#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if></label>\n        <el-input v-model=\"query.${column.changeColumnName}\" clearable placeholder=\"<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>\" style=\"width: 185px;\" class=\"filter-item\" @keyup.enter.native=\"crud.toQuery\" />\n            </#if>\n          </#list>\n        </#if>\n  <#if betweens??>\n    <#list betweens as column>\n      <#if column.queryType = 'BetWeen'>\n        <date-range-picker\n          v-model=\"query.${column.changeColumnName}\"\n          start-placeholder=\"${column.changeColumnName}Start\"\n          end-placeholder=\"${column.changeColumnName}Start\"\n          class=\"date-item\"\n        />\n      </#if>\n    </#list>\n  </#if>\n        <rrOperation :crud=\"crud\" />\n      </div>\n    </#if>\n      <!--如果想在工具栏加入更多按钮，可以使用插槽方式， slot = 'left' or 'right'-->\n      <crudOperation :permission=\"permission\" />\n      <!--表单组件-->\n      <el-dialog :close-on-click-modal=\"false\" :before-close=\"crud.cancelCU\" :visible.sync=\"crud.status.cu > 0\" :title=\"crud.status.title\" width=\"500px\">\n        <el-form ref=\"form\" :model=\"form\" <#if isNotNullColumns??>:rules=\"rules\"</#if> size=\"small\" label-width=\"80px\">\n    <#if columns??>\n      <#list columns as column>\n        <#if column.formShow>\n          <el-form-item label=\"<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>\"<#if column.istNotNull> prop=\"${column.changeColumnName}\"</#if>>\n            <#if column.formType = 'Input'>\n            <el-input v-model=\"form.${column.changeColumnName}\" style=\"width: 370px;\" />\n            <#elseif column.formType = 'Textarea'>\n            <el-input v-model=\"form.${column.changeColumnName}\" :rows=\"3\" type=\"textarea\" style=\"width: 370px;\" />\n            <#elseif column.formType = 'Radio'>\n              <#if (column.dictName)?? && (column.dictName)!=\"\">\n            <el-radio v-model=\"form.${column.changeColumnName}\" v-for=\"item in dict.${column.dictName}\" :key=\"item.id\" :label=\"item.value\">{{ item.label }}</el-radio>\n              <#else>\n                未设置字典，请手动设置 Radio\n              </#if>\n            <#elseif column.formType = 'Select'>\n              <#if (column.dictName)?? && (column.dictName)!=\"\">\n            <el-select v-model=\"form.${column.changeColumnName}\" filterable placeholder=\"请选择\">\n              <el-option\n                v-for=\"item in dict.${column.dictName}\"\n                :key=\"item.id\"\n                :label=\"item.label\"\n                :value=\"item.value\" />\n            </el-select>\n              <#else>\n            未设置字典，请手动设置 Select\n              </#if>\n            <#else>\n            <el-date-picker v-model=\"form.${column.changeColumnName}\" type=\"datetime\" style=\"width: 370px;\" />\n            </#if>\n          </el-form-item>\n        </#if>\n      </#list>\n    </#if>\n        </el-form>\n        <div slot=\"footer\" class=\"dialog-footer\">\n          <el-button type=\"text\" @click=\"crud.cancelCU\">取消</el-button>\n          <el-button :loading=\"crud.status.cu === 2\" type=\"primary\" @click=\"crud.submitCU\">确认</el-button>\n        </div>\n      </el-dialog>\n      <!--表格渲染-->\n      <el-table ref=\"table\" v-loading=\"crud.loading\" :data=\"crud.data\" size=\"small\" style=\"width: 100%;\" @selection-change=\"crud.selectionChangeHandler\">\n        <el-table-column type=\"selection\" width=\"55\" />\n        <#if columns??>\n            <#list columns as column>\n            <#if column.columnShow>\n          <#if (column.dictName)?? && (column.dictName)!=\"\">\n        <el-table-column prop=\"${column.changeColumnName}\" label=\"<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>\">\n          <template slot-scope=\"scope\">\n            {{ dict.label.${column.dictName}[scope.row.${column.changeColumnName}] }}\n          </template>\n        </el-table-column>\n                <#else>\n        <el-table-column prop=\"${column.changeColumnName}\" label=\"<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>\" />\n                </#if>\n            </#if>\n            </#list>\n        </#if>\n        <el-table-column v-if=\"checkPer(['admin','${changeClassName}:edit','${changeClassName}:del'])\" label=\"操作\" width=\"150px\" align=\"center\">\n          <template slot-scope=\"scope\">\n            <udOperation\n              :data=\"scope.row\"\n              :permission=\"permission\"\n            />\n          </template>\n        </el-table-column>\n      </el-table>\n      <!--分页组件-->\n      <pagination />\n    </div>\n  </div>\n</template>\n\n<script>\nimport crud${className} from '@/api/${changeClassName}'\nimport CRUD, { presenter, header, form, crud } from '@crud/crud'\nimport rrOperation from '@crud/RR.operation'\nimport crudOperation from '@crud/CRUD.operation'\nimport udOperation from '@crud/UD.operation'\nimport pagination from '@crud/Pagination'\n\nconst defaultForm = { <#if columns??><#list columns as column>${column.changeColumnName}: null<#if column_has_next>, </#if></#list></#if> }\nexport default {\n  name: '${className}',\n  components: { pagination, crudOperation, rrOperation, udOperation },\n  mixins: [presenter(), header(), form(defaultForm), crud()],\n  <#if hasDict>\n  dicts: [<#if hasDict??><#list dicts as dict>'${dict}'<#if dict_has_next>, </#if></#list></#if>],\n  </#if>\n  cruds() {\n    return CRUD({ title: '${apiAlias}', url: 'api/${changeClassName}', idField: '${pkChangeColName}', sort: '${pkChangeColName},desc', crudMethod: { ...crud${className} }})\n  },\n  data() {\n    return {\n      permission: {\n        add: ['admin', '${changeClassName}:add'],\n        edit: ['admin', '${changeClassName}:edit'],\n        del: ['admin', '${changeClassName}:del']\n      },\n      rules: {\n        <#if isNotNullColumns??>\n        <#list isNotNullColumns as column>\n        <#if column.istNotNull>\n        ${column.changeColumnName}: [\n          { required: true, message: '<#if column.remark != ''>${column.remark}</#if>不能为空', trigger: 'blur' }\n        ]<#if column_has_next>,</#if>\n        </#if>\n        </#list>\n        </#if>\n      }<#if hasQuery>,\n      queryTypeOptions: [\n        <#if queryColumns??>\n        <#list queryColumns as column>\n        <#if column.queryType != 'BetWeen'>\n        { key: '${column.changeColumnName}', display_name: '<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>' }<#if column_has_next>,</#if>\n        </#if>\n        </#list>\n        </#if>\n      ]\n      </#if>\n    }\n  },\n  methods: {\n    // 钩子：在获取表格数据之前执行，false 则代表不获取数据\n    [CRUD.HOOK.beforeRefresh]() {\n      return true\n    }\n  }\n}\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "eladmin-logging/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\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>eladmin</artifactId>\n        <groupId>me.zhengjie</groupId>\n        <version>2.7</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>eladmin-logging</artifactId>\n    <name>日志模块</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>me.zhengjie</groupId>\n            <artifactId>eladmin-common</artifactId>\n            <version>2.7</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Log {\n    String value() default \"\";\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.aspect;\n\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.domain.SysLog;\nimport me.zhengjie.service.SysLogService;\nimport me.zhengjie.utils.RequestHolder;\nimport me.zhengjie.utils.SecurityUtils;\nimport me.zhengjie.utils.StringUtils;\nimport me.zhengjie.utils.ThrowableUtil;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.AfterThrowing;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.springframework.stereotype.Component;\nimport javax.servlet.http.HttpServletRequest;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\n@Component\n@Aspect\n@Slf4j\npublic class LogAspect {\n\n    private final SysLogService sysLogService;\n\n    ThreadLocal<Long> currentTime = new ThreadLocal<>();\n\n    public LogAspect(SysLogService sysLogService) {\n        this.sysLogService = sysLogService;\n    }\n\n    /**\n     * 配置切入点\n     */\n    @Pointcut(\"@annotation(me.zhengjie.annotation.Log)\")\n    public void logPointcut() {\n        // 该方法无方法体,主要为了让同类中其他方法使用此切入点\n    }\n\n    /**\n     * 配置环绕通知,使用在方法logPointcut()上注册的切入点\n     *\n     * @param joinPoint join point for advice\n     */\n    @Around(\"logPointcut()\")\n    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {\n        Object result;\n        currentTime.set(System.currentTimeMillis());\n        result = joinPoint.proceed();\n        SysLog sysLog = new SysLog(\"INFO\",System.currentTimeMillis() - currentTime.get());\n        currentTime.remove();\n        HttpServletRequest request = RequestHolder.getHttpServletRequest();\n        sysLogService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request),joinPoint, sysLog);\n        return result;\n    }\n\n    /**\n     * 配置异常通知\n     *\n     * @param joinPoint join point for advice\n     * @param e exception\n     */\n    @AfterThrowing(pointcut = \"logPointcut()\", throwing = \"e\")\n    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {\n        SysLog sysLog = new SysLog(\"ERROR\",System.currentTimeMillis() - currentTime.get());\n        currentTime.remove();\n        sysLog.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());\n        HttpServletRequest request = RequestHolder.getHttpServletRequest();\n        sysLogService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request), (ProceedingJoinPoint)joinPoint, sysLog);\n    }\n\n    /**\n     * 获取用户名\n     * @return /\n     */\n    public String getUsername() {\n        try {\n            return SecurityUtils.getCurrentUsername();\n        }catch (Exception e){\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/domain/SysLog.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.NoArgsConstructor;\nimport lombok.Setter;\nimport org.hibernate.annotations.CreationTimestamp;\nimport javax.persistence.*;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\n@Entity\n@Getter\n@Setter\n@Table(name = \"sys_log\")\n@NoArgsConstructor\npublic class SysLog implements Serializable {\n\n    @Id\n    @Column(name = \"log_id\")\n    @ApiModelProperty(value = \"ID\")\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @ApiModelProperty(value = \"操作用户\")\n    private String username;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"方法名\")\n    private String method;\n\n    @ApiModelProperty(value = \"参数\")\n    private String params;\n\n    @ApiModelProperty(value = \"日志类型\")\n    private String logType;\n\n    @ApiModelProperty(value = \"请求ip\")\n    private String requestIp;\n\n    @ApiModelProperty(value = \"地址\")\n    private String address;\n\n    @ApiModelProperty(value = \"浏览器\")\n    private String browser;\n\n    @ApiModelProperty(value = \"请求耗时\")\n    private Long time;\n\n    @ApiModelProperty(value = \"异常详细\")\n    private byte[] exceptionDetail;\n\n    /** 创建日期 */\n    @CreationTimestamp\n    @ApiModelProperty(value = \"创建日期：yyyy-MM-dd HH:mm:ss\")\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd HH:mm:ss\")\n    private Timestamp createTime;\n\n    public SysLog(String logType, Long time) {\n        this.logType = logType;\n        this.time = time;\n    }\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.SysLog;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.stereotype.Repository;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\n@Repository\npublic interface LogRepository extends JpaRepository<SysLog,Long>, JpaSpecificationExecutor<SysLog> {\n\n    /**\n     * 根据日志类型删除信息\n     * @param logType 日志类型\n     */\n    @Modifying\n    @Query(value = \"delete from sys_log where log_type = ?1\", nativeQuery = true)\n    void deleteByLogType(String logType);\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/rest/SysLogController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.service.SysLogService;\nimport me.zhengjie.service.dto.SysLogQueryCriteria;\nimport me.zhengjie.service.dto.SysLogSmallDto;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.SecurityUtils;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/logs\")\n@Api(tags = \"系统：日志管理\")\npublic class SysLogController {\n\n    private final SysLogService sysLogService;\n\n    @Log(\"导出数据\")\n    @ApiOperation(\"导出数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check()\")\n    public void exportLog(HttpServletResponse response, SysLogQueryCriteria criteria) throws IOException {\n        criteria.setLogType(\"INFO\");\n        sysLogService.download(sysLogService.queryAll(criteria), response);\n    }\n\n    @Log(\"导出错误数据\")\n    @ApiOperation(\"导出错误数据\")\n    @GetMapping(value = \"/error/download\")\n    @PreAuthorize(\"@el.check()\")\n    public void exportErrorLog(HttpServletResponse response, SysLogQueryCriteria criteria) throws IOException {\n        criteria.setLogType(\"ERROR\");\n        sysLogService.download(sysLogService.queryAll(criteria), response);\n    }\n    @GetMapping\n    @ApiOperation(\"日志查询\")\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<Object> queryLog(SysLogQueryCriteria criteria, Pageable pageable){\n        criteria.setLogType(\"INFO\");\n        return new ResponseEntity<>(sysLogService.queryAll(criteria,pageable), HttpStatus.OK);\n    }\n\n    @GetMapping(value = \"/user\")\n    @ApiOperation(\"用户日志查询\")\n    public ResponseEntity<PageResult<SysLogSmallDto>> queryUserLog(SysLogQueryCriteria criteria, Pageable pageable){\n        criteria.setLogType(\"INFO\");\n        criteria.setUsername(SecurityUtils.getCurrentUsername());\n        return new ResponseEntity<>(sysLogService.queryAllByUser(criteria,pageable), HttpStatus.OK);\n    }\n\n    @GetMapping(value = \"/error\")\n    @ApiOperation(\"错误日志查询\")\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<Object> queryErrorLog(SysLogQueryCriteria criteria, Pageable pageable){\n        criteria.setLogType(\"ERROR\");\n        return new ResponseEntity<>(sysLogService.queryAll(criteria,pageable), HttpStatus.OK);\n    }\n\n    @GetMapping(value = \"/error/{id}\")\n    @ApiOperation(\"日志异常详情查询\")\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<Object> queryErrorLogDetail(@PathVariable Long id){\n        return new ResponseEntity<>(sysLogService.findByErrDetail(id), HttpStatus.OK);\n    }\n    @DeleteMapping(value = \"/del/error\")\n    @Log(\"删除所有ERROR日志\")\n    @ApiOperation(\"删除所有ERROR日志\")\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<Object> delAllErrorLog(){\n        sysLogService.delAllByError();\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @DeleteMapping(value = \"/del/info\")\n    @Log(\"删除所有INFO日志\")\n    @ApiOperation(\"删除所有INFO日志\")\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<Object> delAllInfoLog(){\n        sysLogService.delAllByInfo();\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/SysLogService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.SysLog;\nimport me.zhengjie.service.dto.SysLogQueryCriteria;\nimport me.zhengjie.service.dto.SysLogSmallDto;\nimport me.zhengjie.utils.PageResult;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.scheduling.annotation.Async;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\npublic interface SysLogService {\n\n    /**\n     * 分页查询\n     * @param criteria 查询条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    Object queryAll(SysLogQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param criteria 查询条件\n     * @return /\n     */\n    List<SysLog> queryAll(SysLogQueryCriteria criteria);\n\n    /**\n     * 查询用户日志\n     * @param criteria 查询条件\n     * @param pageable 分页参数\n     * @return -\n     */\n    PageResult<SysLogSmallDto> queryAllByUser(SysLogQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 保存日志数据\n     * @param username 用户\n     * @param browser 浏览器\n     * @param ip 请求IP\n     * @param joinPoint /\n     * @param sysLog 日志实体\n     */\n    @Async\n    void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog);\n\n    /**\n     * 查询异常详情\n     * @param id 日志ID\n     * @return Object\n     */\n    Object findByErrDetail(Long id);\n\n    /**\n     * 导出日志\n     * @param sysLogs 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<SysLog> sysLogs, HttpServletResponse response) throws IOException;\n\n    /**\n     * 删除所有错误日志\n     */\n    void delAllByError();\n\n    /**\n     * 删除所有INFO日志\n     */\n    void delAllByInfo();\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogErrorDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n* @author Zheng Jie\n* @date 2019-5-22\n*/\n@Data\npublic class SysLogErrorDto implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"方法\")\n    private String method;\n\n    @ApiModelProperty(value = \"参数\")\n    private String params;\n\n    @ApiModelProperty(value = \"浏览器\")\n    private String browser;\n\n    @ApiModelProperty(value = \"请求ip\")\n    private String requestIp;\n\n    @ApiModelProperty(value = \"地址\")\n    private String address;\n\n    @ApiModelProperty(value = \"创建时间\")\n    private Timestamp createTime;\n}"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n * 日志查询类\n * @author Zheng Jie\n * @date 2019-6-4 09:23:07\n */\n@Data\npublic class SysLogQueryCriteria {\n\n    @ApiModelProperty(value = \"模糊查询\")\n    @Query(blurry = \"username,description,address,requestIp,method,params\")\n    private String blurry;\n\n    @Query\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @Query\n    @ApiModelProperty(value = \"日志类型\")\n    private String logType;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogSmallDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n * @author Zheng Jie\n * @date 2019-5-22\n */\n@Data\npublic class SysLogSmallDto implements Serializable {\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"请求IP\")\n    private String requestIp;\n\n    @ApiModelProperty(value = \"耗时\")\n    private Long time;\n\n    @ApiModelProperty(value = \"地址\")\n    private String address;\n\n    @ApiModelProperty(value = \"浏览器\")\n    private String browser;\n\n    @ApiModelProperty(value = \"创建时间\")\n    private Timestamp createTime;\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport cn.hutool.core.lang.Dict;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.JSONArray;\nimport com.alibaba.fastjson2.JSONObject;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.SysLog;\nimport me.zhengjie.repository.LogRepository;\nimport me.zhengjie.service.SysLogService;\nimport me.zhengjie.service.dto.SysLogQueryCriteria;\nimport me.zhengjie.service.dto.SysLogSmallDto;\nimport me.zhengjie.service.mapstruct.LogErrorMapper;\nimport me.zhengjie.service.mapstruct.LogSmallMapper;\nimport me.zhengjie.utils.*;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\nimport java.util.*;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-24\n */\n@Service\n@RequiredArgsConstructor\npublic class SysLogServiceImpl implements SysLogService {\n\n    private final LogRepository logRepository;\n    private final LogErrorMapper logErrorMapper;\n    private final LogSmallMapper logSmallMapper;\n    // 定义敏感字段常量数组\n    private static final String[] SENSITIVE_KEYS = {\"password\"};\n\n    @Override\n    public Object queryAll(SysLogQueryCriteria criteria, Pageable pageable) {\n        Page<SysLog> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);\n        String status = \"ERROR\";\n        if (status.equals(criteria.getLogType())) {\n            return PageUtil.toPage(page.map(logErrorMapper::toDto));\n        }\n        return PageUtil.toPage(page);\n    }\n\n    @Override\n    public List<SysLog> queryAll(SysLogQueryCriteria criteria) {\n        return logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)));\n    }\n\n    @Override\n    public PageResult<SysLogSmallDto> queryAllByUser(SysLogQueryCriteria criteria, Pageable pageable) {\n        Page<SysLog> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);\n        return PageUtil.toPage(page.map(logSmallMapper::toDto));\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog) {\n        if (sysLog == null) {\n            throw new IllegalArgumentException(\"Log 不能为 null!\");\n        }\n\n        // 获取方法签名\n        MethodSignature signature = (MethodSignature) joinPoint.getSignature();\n        Method method = signature.getMethod();\n        me.zhengjie.annotation.Log aopLog = method.getAnnotation(me.zhengjie.annotation.Log.class);\n\n        // 方法路径\n        String methodName = joinPoint.getTarget().getClass().getName() + \".\" + signature.getName() + \"()\";\n\n        // 获取参数\n        JSONObject params = getParameter(method, joinPoint.getArgs());\n\n        // 填充基本信息\n        sysLog.setRequestIp(ip);\n        sysLog.setAddress(StringUtils.getCityInfo(sysLog.getRequestIp()));\n        sysLog.setMethod(methodName);\n        sysLog.setUsername(username);\n        sysLog.setParams(JSON.toJSONString(params));\n        sysLog.setBrowser(browser);\n        sysLog.setDescription(aopLog.value());\n\n        // 如果没有获取到用户名，尝试从参数中获取\n        if(StringUtils.isBlank(sysLog.getUsername())){\n            sysLog.setUsername(params.getString(\"username\"));\n        }\n\n        // 保存\n        logRepository.save(sysLog);\n    }\n\n    /**\n     * 根据方法和传入的参数获取请求参数\n     */\n    private JSONObject getParameter(Method method, Object[] args) {\n        JSONObject params = new JSONObject();\n        Parameter[] parameters = method.getParameters();\n        for (int i = 0; i < parameters.length; i++) {\n            // 过滤掉 MultiPartFile\n            if (args[i] instanceof MultipartFile) {\n                continue;\n            }\n            // 过滤掉 HttpServletResponse\n            if (args[i] instanceof HttpServletResponse) {\n                continue;\n            }\n            // 过滤掉 HttpServletRequest\n            if (args[i] instanceof HttpServletRequest) {\n                continue;\n            }\n            // 将RequestBody注解修饰的参数作为请求参数\n            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);\n            if (requestBody != null) {\n                // [el-async-1] ERROR o.s.a.i.SimpleAsyncUncaughtExceptionHandler - Unexpected exception occurred invoking async method: public void me.zhengjie.service.impl.SysLogServiceImpl.save(java.lang.String,java.lang.String,java.lang.String,org.aspectj.lang.ProceedingJoinPoint,me.zhengjie.domain.SysLog)\n                // java.lang.ClassCastException: com.alibaba.fastjson2.JSONArray cannot be cast to com.alibaba.fastjson2.JSONObject\n                Object json = JSON.toJSON(args[i]);\n                if (json instanceof JSONArray) {\n                    params.put(\"reqBodyList\", json);\n                } else {\n                    params.putAll((JSONObject) json);\n                }\n            } else {\n                String key = parameters[i].getName();\n                params.put(key, args[i]);\n            }\n        }\n        // 遍历敏感字段数组并替换值\n        Set<String> keys = params.keySet();\n        for (String key : SENSITIVE_KEYS) {\n            if (keys.contains(key)) {\n                params.put(key, \"******\");\n            }\n        }\n        // 返回参数\n        return params;\n    }\n\n    @Override\n    public Object findByErrDetail(Long id) {\n        SysLog sysLog = logRepository.findById(id).orElseGet(SysLog::new);\n        ValidationUtil.isNull(sysLog.getId(), \"Log\", \"id\", id);\n        byte[] details = sysLog.getExceptionDetail();\n        return Dict.create().set(\"exception\", new String(ObjectUtil.isNotNull(details) ? details : \"\".getBytes()));\n    }\n\n    @Override\n    public void download(List<SysLog> sysLogs, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (SysLog sysLog : sysLogs) {\n            Map<String, Object> map = new LinkedHashMap<>();\n            map.put(\"用户名\", sysLog.getUsername());\n            map.put(\"IP\", sysLog.getRequestIp());\n            map.put(\"IP来源\", sysLog.getAddress());\n            map.put(\"描述\", sysLog.getDescription());\n            map.put(\"浏览器\", sysLog.getBrowser());\n            map.put(\"请求耗时/毫秒\", sysLog.getTime());\n            map.put(\"异常详情\", new String(ObjectUtil.isNotNull(sysLog.getExceptionDetail()) ? sysLog.getExceptionDetail() : \"\".getBytes()));\n            map.put(\"创建日期\", sysLog.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delAllByError() {\n        logRepository.deleteByLogType(\"ERROR\");\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delAllByInfo() {\n        logRepository.deleteByLogType(\"INFO\");\n    }\n}\n"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogErrorMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.domain.SysLog;\nimport me.zhengjie.service.dto.SysLogErrorDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n * @author Zheng Jie\n * @date 2019-5-22\n */\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface LogErrorMapper extends BaseMapper<SysLogErrorDto, SysLog> {\n\n}"
  },
  {
    "path": "eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogSmallMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.domain.SysLog;\nimport me.zhengjie.service.dto.SysLogSmallDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n * @author Zheng Jie\n * @date 2019-5-22\n */\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface LogSmallMapper extends BaseMapper<SysLogSmallDto, SysLog> {\n\n}"
  },
  {
    "path": "eladmin-system/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\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>eladmin</artifactId>\n        <groupId>me.zhengjie</groupId>\n        <version>2.7</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>eladmin-system</artifactId>\n    <name>核心模块</name>\n\n    <properties>\n        <jjwt.version>0.11.5</jjwt.version>\n        <!-- oshi监控需要指定jna版本, 问题详见 https://github.com/oshi/oshi/issues/1040 -->\n        <jna.version>5.8.0</jna.version>\n    </properties>\n\n    <dependencies>\n        <!-- 代码生成模块 -->\n        <dependency>\n            <groupId>me.zhengjie</groupId>\n            <artifactId>eladmin-generator</artifactId>\n            <version>2.7</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>me.zhengjie</groupId>\n                    <artifactId>eladmin-common</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!-- tools 模块包含了 common 和 logging 模块 -->\n        <dependency>\n            <groupId>me.zhengjie</groupId>\n            <artifactId>eladmin-tools</artifactId>\n            <version>2.7</version>\n        </dependency>\n\n        <!-- quartz -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-quartz</artifactId>\n        </dependency>\n\n        <!-- jwt -->\n        <dependency>\n            <groupId>io.jsonwebtoken</groupId>\n            <artifactId>jjwt-api</artifactId>\n            <version>${jjwt.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.jsonwebtoken</groupId>\n            <artifactId>jjwt-impl</artifactId>\n            <version>${jjwt.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.jsonwebtoken</groupId>\n            <artifactId>jjwt-jackson</artifactId>\n            <version>${jjwt.version}</version>\n        </dependency>\n\n        <!-- linux的管理 -->\n        <dependency>\n            <groupId>ch.ethz.ganymed</groupId>\n            <artifactId>ganymed-ssh2</artifactId>\n            <version>build210</version>\n        </dependency>\n        <dependency>\n            <groupId>com.jcraft</groupId>\n            <artifactId>jsch</artifactId>\n            <version>0.1.55</version>\n        </dependency>\n\n        <!-- 获取系统信息 -->\n        <dependency>\n            <groupId>com.github.oshi</groupId>\n            <artifactId>oshi-core</artifactId>\n            <version>6.6.5</version>\n        </dependency>\n    </dependencies>\n\n    <!-- 打包 -->\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n            <!-- 跳过单元测试 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <configuration>\n                    <skipTests>true</skipTests>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/AppRun.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie;\n\nimport io.swagger.annotations.Api;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.rest.AnonymousGetMapping;\nimport me.zhengjie.utils.SpringBeanHolder;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.context.ApplicationPidFileWriter;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.data.jpa.repository.config.EnableJpaAuditing;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.transaction.annotation.EnableTransactionManagement;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * 开启审计功能 -> @EnableJpaAuditing\n *\n * @author Zheng Jie\n * @date 2018/11/15 9:20:19\n */\n@Slf4j\n@EnableAsync\n@RestController\n@Api(hidden = true)\n@SpringBootApplication\n@EnableTransactionManagement\n@EnableJpaAuditing(auditorAwareRef = \"auditorAware\")\npublic class AppRun {\n\n    public static void main(String[] args) {\n        SpringApplication springApplication = new SpringApplication(AppRun.class);\n        // 监控应用的PID，启动时可指定PID路径：--spring.pid.file=/home/eladmin/app.pid\n        // 或者在 application.yml 添加文件路径，方便 kill，kill `cat /home/eladmin/app.pid`\n        springApplication.addListeners(new ApplicationPidFileWriter());\n        ConfigurableApplicationContext context = springApplication.run(args);\n        String port = context.getEnvironment().getProperty(\"server.port\");\n        log.info(\"---------------------------------------------\");\n        log.info(\"Local: http://localhost:{}\", port);\n        log.info(\"Swagger: http://localhost:{}/doc.html\", port);\n        log.info(\"---------------------------------------------\");\n    }\n\n    @Bean\n    public SpringBeanHolder springContextHolder() {\n        return new SpringBeanHolder();\n    }\n\n    /**\n     * 访问首页提示\n     *\n     * @return /\n     */\n    @AnonymousGetMapping(\"/\")\n    public String index() {\n        return \"Backend service started successfully\";\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/App.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport java.io.Serializable;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"mnt_app\")\npublic class App extends BaseEntity implements Serializable {\n\n    @Id\n\t@Column(name = \"app_id\")\n\t@ApiModelProperty(value = \"ID\", hidden = true)\n\t@GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n\t@ApiModelProperty(value = \"名称\")\n    private String name;\n\n\t@ApiModelProperty(value = \"端口\")\n\tprivate int port;\n\n\t@ApiModelProperty(value = \"上传路径\")\n\tprivate String uploadPath;\n\n\t@ApiModelProperty(value = \"部署路径\")\n\tprivate String deployPath;\n\n\t@ApiModelProperty(value = \"备份路径\")\n\tprivate String backupPath;\n\n\t@ApiModelProperty(value = \"启动脚本\")\n\tprivate String startScript;\n\n\t@ApiModelProperty(value = \"部署脚本\")\n\tprivate String deployScript;\n\n    public void copy(App source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Database.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport java.io.Serializable;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"mnt_database\")\npublic class Database extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"db_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    private String id;\n\n    @ApiModelProperty(value = \"数据库名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"数据库连接地址\")\n    private String jdbcUrl;\n\n    @ApiModelProperty(value = \"数据库密码\")\n    private String pwd;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String userName;\n\n    public void copy(Database source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Deploy.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport java.io.Serializable;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"mnt_deploy\")\npublic class Deploy extends BaseEntity implements Serializable {\n\n    @Id\n\t@Column(name = \"deploy_id\")\n\t@ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n\t@ManyToMany\n\t@ApiModelProperty(name = \"服务器\", hidden = true)\n\t@JoinTable(name = \"mnt_deploy_server\",\n\t\t\tjoinColumns = {@JoinColumn(name = \"deploy_id\",referencedColumnName = \"deploy_id\")},\n\t\t\tinverseJoinColumns = {@JoinColumn(name = \"server_id\",referencedColumnName = \"server_id\")})\n\tprivate Set<ServerDeploy> deploys;\n\n\t@ManyToOne\n    @JoinColumn(name = \"app_id\")\n\t@ApiModelProperty(value = \"应用编号\")\n    private App app;\n\n    public void copy(Deploy source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/DeployHistory.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.hibernate.annotations.CreationTimestamp;\nimport javax.persistence.*;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"mnt_deploy_history\")\npublic class DeployHistory implements Serializable {\n\n    @Id\n    @Column(name = \"history_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    private String id;\n\n    @ApiModelProperty(value = \"应用名称\")\n    private String appName;\n\n    @ApiModelProperty(value = \"IP\")\n    private String ip;\n\n    @CreationTimestamp\n    @ApiModelProperty(value = \"部署时间\")\n    private Timestamp deployDate;\n\n    @ApiModelProperty(value = \"部署者\")\n    private String deployUser;\n\n    @ApiModelProperty(value = \"部署ID\")\n    private Long deployId;\n\n    public void copy(DeployHistory source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/ServerDeploy.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"mnt_server\")\npublic class ServerDeploy extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"server_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @ApiModelProperty(value = \"服务器名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"IP\")\n    private String ip;\n\n    @ApiModelProperty(value = \"端口\")\n    private Integer port;\n\n    @ApiModelProperty(value = \"账号\")\n    private String account;\n\n    @ApiModelProperty(value = \"密码\")\n    private String password;\n\n    public void copy(ServerDeploy source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        ServerDeploy that = (ServerDeploy) o;\n        return Objects.equals(id, that.id) &&\n                Objects.equals(name, that.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, name);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/enums/DataTypeEnum.java",
    "content": "/*\n * <<\n *  Davinci\n *  ==\n *  Copyright (C) 2016 - 2019 EDP\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 *        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 *\n */\n\npackage me.zhengjie.modules.maint.domain.enums;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author /\n */\n@Slf4j\n@SuppressWarnings({\"unchecked\",\"all\"})\npublic enum DataTypeEnum {\n\n    /** mysql */\n    MYSQL(\"mysql\", \"mysql\", \"com.mysql.jdbc.Driver\", \"`\", \"`\", \"'\", \"'\"),\n\n    /** oracle */\n    ORACLE(\"oracle\", \"oracle\", \"oracle.jdbc.driver.OracleDriver\", \"\\\"\", \"\\\"\", \"\\\"\", \"\\\"\"),\n\n    /** sql server */\n    SQLSERVER(\"sqlserver\", \"sqlserver\", \"com.microsoft.sqlserver.jdbc.SQLServerDriver\", \"\\\"\", \"\\\"\", \"\\\"\", \"\\\"\"),\n\n    /** h2 */\n    H2(\"h2\", \"h2\", \"org.h2.Driver\", \"`\", \"`\", \"\\\"\", \"\\\"\"),\n\n    /** phoenix */\n    PHOENIX(\"phoenix\", \"hbase phoenix\", \"org.apache.phoenix.jdbc.PhoenixDriver\", \"\", \"\", \"\\\"\", \"\\\"\"),\n\n    /** mongo */\n    MONGODB(\"mongo\", \"mongodb\", \"mongodb.jdbc.MongoDriver\", \"`\", \"`\", \"\\\"\", \"\\\"\"),\n\n    /** sql4es */\n    ELASTICSEARCH(\"sql4es\", \"elasticsearch\", \"nl.anchormen.sql4es.jdbc.ESDriver\", \"\", \"\", \"'\", \"'\"),\n\n    /** presto */\n    PRESTO(\"presto\", \"presto\", \"com.facebook.presto.jdbc.PrestoDriver\", \"\", \"\", \"\\\"\", \"\\\"\"),\n\n    /** moonbox */\n    MOONBOX(\"moonbox\", \"moonbox\", \"moonbox.jdbc.MbDriver\", \"`\", \"`\", \"`\", \"`\"),\n\n    /** cassandra */\n    CASSANDRA(\"cassandra\", \"cassandra\", \"com.github.adejanovski.cassandra.jdbc.CassandraDriver\", \"\", \"\", \"'\", \"'\"),\n\n    /** click house */\n    CLICKHOUSE(\"clickhouse\", \"clickhouse\", \"ru.yandex.clickhouse.ClickHouseDriver\", \"\", \"\", \"\\\"\", \"\\\"\"),\n\n    /** kylin */\n    KYLIN(\"kylin\", \"kylin\", \"org.apache.kylin.jdbc.Driver\", \"\\\"\", \"\\\"\", \"\\\"\", \"\\\"\"),\n\n    /** vertica */\n    VERTICA(\"vertica\", \"vertica\", \"com.vertica.jdbc.Driver\", \"\", \"\", \"'\", \"'\"),\n\n    /** sap */\n    HANA(\"sap\", \"sap hana\", \"com.sap.db.jdbc.Driver\", \"\", \"\", \"'\", \"'\"),\n\n    /** impala */\n    IMPALA(\"impala\", \"impala\", \"com.cloudera.impala.jdbc41.Driver\", \"\", \"\", \"'\", \"'\");\n\n    private String feature;\n    private String desc;\n    private String driver;\n    private String keywordPrefix;\n    private String keywordSuffix;\n    private String aliasPrefix;\n    private String aliasSuffix;\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:\";\n\n    DataTypeEnum(String feature, String desc, String driver, String keywordPrefix, String keywordSuffix, String aliasPrefix, String aliasSuffix) {\n        this.feature = feature;\n        this.desc = desc;\n        this.driver = driver;\n        this.keywordPrefix = keywordPrefix;\n        this.keywordSuffix = keywordSuffix;\n        this.aliasPrefix = aliasPrefix;\n        this.aliasSuffix = aliasSuffix;\n    }\n\n    public static DataTypeEnum urlOf(String jdbcUrl) {\n        String url = jdbcUrl.toLowerCase().trim();\n        for (DataTypeEnum dataTypeEnum : values()) {\n            if (url.startsWith(JDBC_URL_PREFIX + dataTypeEnum.feature)) {\n                try {\n                    Class<?> aClass = Class.forName(dataTypeEnum.getDriver());\n                    if (null == aClass) {\n                        throw new RuntimeException(\"Unable to get driver instance for jdbcUrl: \" + jdbcUrl);\n                    }\n                } catch (ClassNotFoundException e) {\n                    throw new RuntimeException(\"Unable to get driver instance: \" + jdbcUrl);\n                }\n                return dataTypeEnum;\n            }\n        }\n        return null;\n    }\n\n    public String getFeature() {\n        return feature;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public String getDriver() {\n        return driver;\n    }\n\n    public String getKeywordPrefix() {\n        return keywordPrefix;\n    }\n\n    public String getKeywordSuffix() {\n        return keywordSuffix;\n    }\n\n    public String getAliasPrefix() {\n        return aliasPrefix;\n    }\n\n    public String getAliasSuffix() {\n        return aliasSuffix;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/AppRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.repository;\n\nimport me.zhengjie.modules.maint.domain.App;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface AppRepository extends JpaRepository<App, Long>, JpaSpecificationExecutor<App> {\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DatabaseRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.repository;\n\nimport me.zhengjie.modules.maint.domain.Database;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface DatabaseRepository extends JpaRepository<Database, String>, JpaSpecificationExecutor<Database> {\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployHistoryRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.repository;\n\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface DeployHistoryRepository extends JpaRepository<DeployHistory, String>, JpaSpecificationExecutor<DeployHistory> {\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.repository;\n\nimport me.zhengjie.modules.maint.domain.Deploy;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface DeployRepository extends JpaRepository<Deploy, Long>, JpaSpecificationExecutor<Deploy> {\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/ServerDeployRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.repository;\n\nimport me.zhengjie.modules.maint.domain.ServerDeploy;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface ServerDeployRepository extends JpaRepository<ServerDeploy, Long>, JpaSpecificationExecutor<ServerDeploy> {\n\n    /**\n     * 根据IP查询\n     * @param ip /\n     * @return /\n     */\n    ServerDeploy findByIp(String ip);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/AppController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.modules.maint.domain.App;\nimport me.zhengjie.modules.maint.service.AppService;\nimport me.zhengjie.modules.maint.service.dto.AppDto;\nimport me.zhengjie.modules.maint.service.dto.AppQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"运维：应用管理\")\n@RequestMapping(\"/api/app\")\npublic class AppController {\n\n    private final AppService appService;\n\n    @ApiOperation(\"导出应用数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('app:list')\")\n    public void exportApp(HttpServletResponse response, AppQueryCriteria criteria) throws IOException {\n        appService.download(appService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(value = \"查询应用\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('app:list')\")\n    public ResponseEntity<PageResult<AppDto>> queryApp(AppQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(appService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @Log(\"新增应用\")\n    @ApiOperation(value = \"新增应用\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('app:add')\")\n    public ResponseEntity<Object> createApp(@Validated @RequestBody App resources){\n        appService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改应用\")\n    @ApiOperation(value = \"修改应用\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('app:edit')\")\n    public ResponseEntity<Object> updateApp(@Validated @RequestBody App resources){\n        appService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除应用\")\n    @ApiOperation(value = \"删除应用\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('app:del')\")\n    public ResponseEntity<Object> deleteApp(@RequestBody Set<Long> ids){\n        appService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DatabaseController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.maint.domain.Database;\nimport me.zhengjie.modules.maint.service.DatabaseService;\nimport me.zhengjie.modules.maint.service.dto.DatabaseDto;\nimport me.zhengjie.modules.maint.service.dto.DatabaseQueryCriteria;\nimport me.zhengjie.modules.maint.util.SqlUtils;\nimport me.zhengjie.utils.FileUtil;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Api(tags = \"运维：数据库管理\")\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/database\")\npublic class DatabaseController {\n\n\tprivate final String fileSavePath = FileUtil.getTmpDirPath()+\"/\";\n    private final DatabaseService databaseService;\n\n\t@ApiOperation(\"导出数据库数据\")\n\t@GetMapping(value = \"/download\")\n\t@PreAuthorize(\"@el.check('database:list')\")\n\tpublic void exportDatabase(HttpServletResponse response, DatabaseQueryCriteria criteria) throws IOException {\n\t\tdatabaseService.download(databaseService.queryAll(criteria), response);\n\t}\n\n    @ApiOperation(value = \"查询数据库\")\n    @GetMapping\n\t@PreAuthorize(\"@el.check('database:list')\")\n    public ResponseEntity<PageResult<DatabaseDto>> queryDatabase(DatabaseQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(databaseService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @Log(\"新增数据库\")\n    @ApiOperation(value = \"新增数据库\")\n    @PostMapping\n\t@PreAuthorize(\"@el.check('database:add')\")\n    public ResponseEntity<Object> createDatabase(@Validated @RequestBody Database resources){\n\t\tdatabaseService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改数据库\")\n    @ApiOperation(value = \"修改数据库\")\n    @PutMapping\n\t@PreAuthorize(\"@el.check('database:edit')\")\n    public ResponseEntity<Object> updateDatabase(@Validated @RequestBody Database resources){\n        databaseService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除数据库\")\n    @ApiOperation(value = \"删除数据库\")\n    @DeleteMapping\n\t@PreAuthorize(\"@el.check('database:del')\")\n    public ResponseEntity<Object> deleteDatabase(@RequestBody Set<String> ids){\n        databaseService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n\t@Log(\"测试数据库链接\")\n\t@ApiOperation(value = \"测试数据库链接\")\n\t@PostMapping(\"/testConnect\")\n\t@PreAuthorize(\"@el.check('database:testConnect')\")\n\tpublic ResponseEntity<Object> testConnect(@Validated @RequestBody Database resources){\n\t\treturn new ResponseEntity<>(databaseService.testConnection(resources),HttpStatus.CREATED);\n\t}\n\n\t@Log(\"执行SQL脚本\")\n\t@ApiOperation(value = \"执行SQL脚本\")\n\t@PostMapping(value = \"/upload\")\n\t@PreAuthorize(\"@el.check('database:add')\")\n\tpublic ResponseEntity<Object> uploadDatabase(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{\n\t\tString id = request.getParameter(\"id\");\n\t\tDatabaseDto database = databaseService.findById(id);\n\t\tString fileName;\n\t\tif(database != null){\n\t\t\tfileName = FileUtil.verifyFilename(file.getOriginalFilename());\n\t\t\tFile executeFile = new File(fileSavePath + fileName);\n\t\t\tFileUtil.del(executeFile);\n\t\t\tfile.transferTo(executeFile);\n\t\t\tString result = SqlUtils.executeFile(database.getJdbcUrl(), database.getUserName(), database.getPwd(), executeFile);\n\t\t\treturn new ResponseEntity<>(result,HttpStatus.OK);\n\t\t}else{\n\t\t\tthrow new BadRequestException(\"Database not exist\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.modules.maint.domain.Deploy;\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport me.zhengjie.modules.maint.service.DeployService;\nimport me.zhengjie.modules.maint.service.dto.DeployDto;\nimport me.zhengjie.modules.maint.service.dto.DeployQueryCriteria;\nimport me.zhengjie.utils.FileUtil;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Slf4j\n@RestController\n@Api(tags = \"运维：部署管理\")\n@RequiredArgsConstructor\n@RequestMapping(\"/api/deploy\")\npublic class DeployController {\n\n\tprivate final String fileSavePath = FileUtil.getTmpDirPath()+\"/\";\n    private final DeployService deployService;\n\n\n\t@ApiOperation(\"导出部署数据\")\n\t@GetMapping(value = \"/download\")\n\t@PreAuthorize(\"@el.check('database:list')\")\n\tpublic void exportDeployData(HttpServletResponse response, DeployQueryCriteria criteria) throws IOException {\n\t\tdeployService.download(deployService.queryAll(criteria), response);\n\t}\n\n    @ApiOperation(value = \"查询部署\")\n    @GetMapping\n\t@PreAuthorize(\"@el.check('deploy:list')\")\n    public ResponseEntity<PageResult<DeployDto>> queryDeployData(DeployQueryCriteria criteria, Pageable pageable){\n\t\treturn new ResponseEntity<>(deployService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @Log(\"新增部署\")\n    @ApiOperation(value = \"新增部署\")\n    @PostMapping\n\t@PreAuthorize(\"@el.check('deploy:add')\")\n    public ResponseEntity<Object> createDeploy(@Validated @RequestBody Deploy resources){\n\t\tdeployService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改部署\")\n    @ApiOperation(value = \"修改部署\")\n    @PutMapping\n\t@PreAuthorize(\"@el.check('deploy:edit')\")\n    public ResponseEntity<Object> updateDeploy(@Validated @RequestBody Deploy resources){\n        deployService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n\t@Log(\"删除部署\")\n\t@ApiOperation(value = \"删除部署\")\n\t@DeleteMapping\n\t@PreAuthorize(\"@el.check('deploy:del')\")\n\tpublic ResponseEntity<Object> deleteDeploy(@RequestBody Set<Long> ids){\n\t\tdeployService.delete(ids);\n\t\treturn new ResponseEntity<>(HttpStatus.OK);\n\t}\n\n\t@Log(\"上传文件部署\")\n\t@ApiOperation(value = \"上传文件部署\")\n\t@PostMapping(value = \"/upload\")\n\t@PreAuthorize(\"@el.check('deploy:edit')\")\n\tpublic ResponseEntity<Object> uploadDeploy(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{\n\t\tLong id = Long.valueOf(request.getParameter(\"id\"));\n\t\tString fileName = \"\";\n\t\tif(file != null){\n\t\t\tfileName = FileUtil.verifyFilename(file.getOriginalFilename());\n\t\t\tFile deployFile = new File(fileSavePath + fileName);\n\t\t\tFileUtil.del(deployFile);\n\t\t\tfile.transferTo(deployFile);\n\t\t\t//文件下一步要根据文件名字来\n\t\t\tdeployService.deploy(fileSavePath + fileName ,id);\n\t\t}else{\n\t\t\tlog.warn(\"没有找到相对应的文件\");\n\t\t}\n\t\tMap<String,Object> map = new HashMap<>(2);\n\t\tmap.put(\"error\",0);\n\t\tmap.put(\"id\",fileName);\n\t\treturn new ResponseEntity<>(map,HttpStatus.OK);\n\t}\n\n\t@Log(\"系统还原\")\n\t@ApiOperation(value = \"系统还原\")\n\t@PostMapping(value = \"/serverReduction\")\n\t@PreAuthorize(\"@el.check('deploy:edit')\")\n\tpublic ResponseEntity<Object> serverReduction(@Validated @RequestBody DeployHistory resources){\n\t\tString result = deployService.serverReduction(resources);\n\t\treturn new ResponseEntity<>(result,HttpStatus.OK);\n\t}\n\n\t@Log(\"服务运行状态\")\n\t@ApiOperation(value = \"服务运行状态\")\n\t@PostMapping(value = \"/serverStatus\")\n\t@PreAuthorize(\"@el.check('deploy:edit')\")\n\tpublic ResponseEntity<Object> serverStatus(@Validated @RequestBody Deploy resources){\n\t\tString result = deployService.serverStatus(resources);\n\t\treturn new ResponseEntity<>(result,HttpStatus.OK);\n\t}\n\n\t@Log(\"启动服务\")\n\t@ApiOperation(value = \"启动服务\")\n\t@PostMapping(value = \"/startServer\")\n\t@PreAuthorize(\"@el.check('deploy:edit')\")\n\tpublic ResponseEntity<Object> startServer(@Validated @RequestBody Deploy resources){\n\t\tString result = deployService.startServer(resources);\n\t\treturn new ResponseEntity<>(result,HttpStatus.OK);\n\t}\n\n\t@Log(\"停止服务\")\n\t@ApiOperation(value = \"停止服务\")\n\t@PostMapping(value = \"/stopServer\")\n\t@PreAuthorize(\"@el.check('deploy:edit')\")\n\tpublic ResponseEntity<Object> stopServer(@Validated @RequestBody Deploy resources){\n\t\tString result = deployService.stopServer(resources);\n\t\treturn new ResponseEntity<>(result,HttpStatus.OK);\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployHistoryController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.modules.maint.service.DeployHistoryService;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryDto;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"运维：部署历史管理\")\n@RequestMapping(\"/api/deployHistory\")\npublic class DeployHistoryController {\n\n    private final DeployHistoryService deployhistoryService;\n\n    @ApiOperation(\"导出部署历史数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('deployHistory:list')\")\n    public void exportDeployHistory(HttpServletResponse response, DeployHistoryQueryCriteria criteria) throws IOException {\n        deployhistoryService.download(deployhistoryService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(value = \"查询部署历史\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('deployHistory:list')\")\n    public ResponseEntity<PageResult<DeployHistoryDto>> queryDeployHistory(DeployHistoryQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(deployhistoryService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @Log(\"删除DeployHistory\")\n    @ApiOperation(value = \"删除部署历史\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('deployHistory:del')\")\n    public ResponseEntity<Object> deleteDeployHistory(@RequestBody Set<String> ids){\n        deployhistoryService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/ServerDeployController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.modules.maint.domain.ServerDeploy;\nimport me.zhengjie.modules.maint.service.ServerDeployService;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployDto;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@RestController\n@Api(tags = \"运维：服务器管理\")\n@RequiredArgsConstructor\n@RequestMapping(\"/api/serverDeploy\")\npublic class ServerDeployController {\n\n    private final ServerDeployService serverDeployService;\n\n    @ApiOperation(\"导出服务器数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('serverDeploy:list')\")\n    public void exportServerDeploy(HttpServletResponse response, ServerDeployQueryCriteria criteria) throws IOException {\n        serverDeployService.download(serverDeployService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(value = \"查询服务器\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('serverDeploy:list')\")\n    public ResponseEntity<PageResult<ServerDeployDto>> queryServerDeploy(ServerDeployQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(serverDeployService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @Log(\"新增服务器\")\n    @ApiOperation(value = \"新增服务器\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('serverDeploy:add')\")\n    public ResponseEntity<Object> createServerDeploy(@Validated @RequestBody ServerDeploy resources){\n        serverDeployService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改服务器\")\n    @ApiOperation(value = \"修改服务器\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('serverDeploy:edit')\")\n    public ResponseEntity<Object> updateServerDeploy(@Validated @RequestBody ServerDeploy resources){\n        serverDeployService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除服务器\")\n    @ApiOperation(value = \"删除Server\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('serverDeploy:del')\")\n    public ResponseEntity<Object> deleteServerDeploy(@RequestBody Set<Long> ids){\n        serverDeployService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @Log(\"测试连接服务器\")\n    @ApiOperation(value = \"测试连接服务器\")\n    @PostMapping(\"/testConnect\")\n    @PreAuthorize(\"@el.check('serverDeploy:add')\")\n    public ResponseEntity<Object> testConnectServerDeploy(@Validated @RequestBody ServerDeploy resources){\n        return new ResponseEntity<>(serverDeployService.testConnect(resources),HttpStatus.CREATED);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/AppService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service;\n\nimport me.zhengjie.modules.maint.domain.App;\nimport me.zhengjie.modules.maint.service.dto.AppDto;\nimport me.zhengjie.modules.maint.service.dto.AppQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface AppService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<AppDto> queryAll(AppQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param criteria 条件\n     * @return /\n     */\n    List<AppDto> queryAll(AppQueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    AppDto findById(Long id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(App resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(App resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 导出数据\n     * @param queryAll /\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<AppDto> queryAll, HttpServletResponse response) throws IOException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DatabaseService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service;\n\nimport me.zhengjie.modules.maint.domain.Database;\nimport me.zhengjie.modules.maint.service.dto.DatabaseDto;\nimport me.zhengjie.modules.maint.service.dto.DatabaseQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author ZhangHouYing\n * @date 2019-08-24\n */\npublic interface DatabaseService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<DatabaseDto> queryAll(DatabaseQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部\n     * @param criteria 条件\n     * @return /\n     */\n    List<DatabaseDto> queryAll(DatabaseQueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    DatabaseDto findById(String id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(Database resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Database resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<String> ids);\n\n\t/**\n\t * 测试连接数据库\n\t * @param resources /\n\t * @return /\n\t */\n\tboolean testConnection(Database resources);\n\n    /**\n     * 导出数据\n     * @param queryAll /\n     * @param response /\n     * @throws IOException e\n     */\n    void download(List<DatabaseDto> queryAll, HttpServletResponse response) throws IOException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployHistoryService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service;\n\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryDto;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author zhanghouying\n */\npublic interface DeployHistoryService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<DeployHistoryDto> queryAll(DeployHistoryQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部\n     * @param criteria 条件\n     * @return /\n     */\n    List<DeployHistoryDto> queryAll(DeployHistoryQueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    DeployHistoryDto findById(String id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(DeployHistory resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<String> ids);\n\n    /**\n     * 导出数据\n     * @param queryAll /\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<DeployHistoryDto> queryAll, HttpServletResponse response) throws IOException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service;\n\nimport me.zhengjie.modules.maint.domain.Deploy;\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport me.zhengjie.modules.maint.service.dto.DeployDto;\nimport me.zhengjie.modules.maint.service.dto.DeployQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface DeployService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<DeployDto> queryAll(DeployQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param criteria 条件\n     * @return /\n     */\n    List<DeployDto> queryAll(DeployQueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    DeployDto findById(Long id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(Deploy resources);\n\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Deploy resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n\t/**\n\t * 部署文件到服务器\n\t * @param fileSavePath 文件路径\n\t * @param appId 应用ID\n     */\n\tvoid deploy(String fileSavePath, Long appId);\n\n    /**\n     * 查询部署状态\n     * @param resources /\n     * @return /\n     */\n    String serverStatus(Deploy resources);\n    /**\n     * 启动服务\n     * @param resources /\n     * @return /\n     */\n    String startServer(Deploy resources);\n    /**\n     * 停止服务\n     * @param resources /\n     * @return /\n     */\n    String stopServer(Deploy resources);\n\n    /**\n     * 停止服务\n     * @param resources /\n     * @return /\n     */\n    String serverReduction(DeployHistory resources);\n\n    /**\n     * 导出数据\n     * @param queryAll /\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<DeployDto> queryAll, HttpServletResponse response) throws IOException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/ServerDeployService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service;\n\nimport me.zhengjie.modules.maint.domain.ServerDeploy;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployDto;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\npublic interface ServerDeployService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<ServerDeployDto> queryAll(ServerDeployQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param criteria 条件\n     * @return /\n     */\n    List<ServerDeployDto> queryAll(ServerDeployQueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    ServerDeployDto findById(Long id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(ServerDeploy resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(ServerDeploy resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 根据IP查询\n     * @param ip /\n     * @return /\n     */\n    ServerDeployDto findByIp(String ip);\n\n\t/**\n\t * 测试登录服务器\n\t * @param resources /\n\t * @return /\n\t */\n\tBoolean testConnect(ServerDeploy resources);\n\n    /**\n     * 导出数据\n     * @param queryAll /\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<ServerDeployDto> queryAll, HttpServletResponse response) throws IOException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Getter\n@Setter\npublic class AppDto extends BaseDTO implements Serializable {\n\n\t@ApiModelProperty(value = \"ID\")\n    private Long id;\n\n\t@ApiModelProperty(value = \"应用名称\")\n\tprivate String name;\n\n\t@ApiModelProperty(value = \"端口\")\n\tprivate Integer port;\n\n\t@ApiModelProperty(value = \"上传目录\")\n\tprivate String uploadPath;\n\n\t@ApiModelProperty(value = \"部署目录\")\n\tprivate String deployPath;\n\n\t@ApiModelProperty(value = \"备份目录\")\n\tprivate String backupPath;\n\n\t@ApiModelProperty(value = \"启动脚本\")\n\tprivate String startScript;\n\n\t@ApiModelProperty(value = \"部署脚本\")\n\tprivate String deployScript;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Data\npublic class AppQueryCriteria{\n\n\t@ApiModelProperty(value = \"模糊\")\n    @Query(type = Query.Type.INNER_LIKE)\n    private String name;\n\n\t@ApiModelProperty(value = \"创建时间\")\n\t@Query(type = Query.Type.BETWEEN)\n\tprivate List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Getter\n@Setter\npublic class DatabaseDto extends BaseDTO implements Serializable {\n\n\t@ApiModelProperty(value = \"ID\")\n    private String id;\n\n\t@ApiModelProperty(value = \"数据库名称\")\n    private String name;\n\n\t@ApiModelProperty(value = \"数据库连接地址\")\n    private String jdbcUrl;\n\n\t@ApiModelProperty(value = \"数据库密码\")\n    private String pwd;\n\n\t@ApiModelProperty(value = \"用户名\")\n    private String userName;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Data\npublic class DatabaseQueryCriteria{\n\n\t@ApiModelProperty(value = \"模糊\")\n    @Query(type = Query.Type.INNER_LIKE)\n    private String name;\n\n    @Query\n\t@ApiModelProperty(value = \"数据库连接地址\")\n    private String jdbcUrl;\n\n\t@ApiModelProperty(value = \"创建时间\")\n\t@Query(type = Query.Type.BETWEEN)\n\tprivate List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Getter\n@Setter\npublic class DeployDto extends BaseDTO implements Serializable {\n\n\t@ApiModelProperty(value = \"ID\")\n    private String id;\n\n\t@ApiModelProperty(value = \"应用\")\n\tprivate AppDto app;\n\n\t@ApiModelProperty(value = \"服务器\")\n\tprivate Set<ServerDeployDto> deploys;\n\n\t@ApiModelProperty(value = \"服务器名称\")\n\tprivate String servers;\n\n\t@ApiModelProperty(value = \"服务状态\")\n\tprivate String status;\n\n\tpublic String getServers() {\n\t\tif(CollectionUtil.isNotEmpty(deploys)){\n\t\t\treturn deploys.stream().map(ServerDeployDto::getName).collect(Collectors.joining(\",\"));\n\t\t}\n\t\treturn servers;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) {\n\t\t\treturn true;\n\t\t}\n\t\tif (o == null || getClass() != o.getClass()) {\n\t\t\treturn false;\n\t\t}\n\t\tDeployDto deployDto = (DeployDto) o;\n\t\treturn Objects.equals(id, deployDto.id);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn Objects.hash(id);\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Data\npublic class DeployHistoryDto implements Serializable {\n\n\t@ApiModelProperty(value = \"ID\")\n    private String id;\n\n\t@ApiModelProperty(value = \"应用名称\")\n    private String appName;\n\n\t@ApiModelProperty(value = \"部署IP\")\n    private String ip;\n\n\t@ApiModelProperty(value = \"部署时间\")\n\tprivate Timestamp deployDate;\n\n\t@ApiModelProperty(value = \"部署人员\")\n\tprivate String deployUser;\n\n\t@ApiModelProperty(value = \"部署编号\")\n\tprivate Long deployId;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Data\npublic class DeployHistoryQueryCriteria{\n\n\t@ApiModelProperty(value = \"模糊查询\")\n\t@Query(blurry = \"appName,ip,deployUser\")\n\tprivate String blurry;\n\n\t@Query\n\t@ApiModelProperty(value = \"部署编号\")\n\tprivate Long deployId;\n\n\t@ApiModelProperty(value = \"部署时间\")\n\t@Query(type = Query.Type.BETWEEN)\n\tprivate List<Timestamp> deployDate;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Data\npublic class DeployQueryCriteria{\n\n\t@ApiModelProperty(value = \"应用名称\")\n    @Query(type = Query.Type.INNER_LIKE, propName = \"name\", joinName = \"app\")\n    private String appName;\n\n\t@ApiModelProperty(value = \"创建时间\")\n\t@Query(type = Query.Type.BETWEEN)\n\tprivate List<Timestamp> createTime;\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Getter\n@Setter\npublic class ServerDeployDto extends BaseDTO implements Serializable {\n\n\t@ApiModelProperty(value = \"ID\")\n    private Long id;\n\n\t@ApiModelProperty(value = \"名称\")\n\tprivate String name;\n\n\t@ApiModelProperty(value = \"IP\")\n\tprivate String ip;\n\n\t@ApiModelProperty(value = \"端口\")\n\tprivate Integer port;\n\n\t@ApiModelProperty(value = \"账号\")\n\tprivate String account;\n\n\t@ApiModelProperty(value = \"密码\")\n\tprivate String password;\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) {\n\t\t\treturn true;\n\t\t}\n\t\tif (o == null || getClass() != o.getClass()) {\n\t\t\treturn false;\n\t\t}\n\t\tServerDeployDto that = (ServerDeployDto) o;\n\t\treturn Objects.equals(id, that.id) &&\n\t\t\t\tObjects.equals(name, that.name);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn Objects.hash(id, name);\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Data\npublic class ServerDeployQueryCriteria{\n\n\t@ApiModelProperty(value = \"模糊查询\")\n\t@Query(blurry = \"name,ip,account\")\n    private String blurry;\n\n\t@ApiModelProperty(value = \"创建时间\")\n\t@Query(type = Query.Type.BETWEEN)\n\tprivate List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.impl;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.maint.domain.App;\nimport me.zhengjie.modules.maint.repository.AppRepository;\nimport me.zhengjie.modules.maint.service.AppService;\nimport me.zhengjie.modules.maint.service.dto.AppDto;\nimport me.zhengjie.modules.maint.service.dto.AppQueryCriteria;\nimport me.zhengjie.modules.maint.service.mapstruct.AppMapper;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Service\n@RequiredArgsConstructor\npublic class AppServiceImpl implements AppService {\n\n    private final AppRepository appRepository;\n    private final AppMapper appMapper;\n\n    @Override\n    public PageResult<AppDto> queryAll(AppQueryCriteria criteria, Pageable pageable){\n        Page<App> page = appRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(appMapper::toDto));\n    }\n\n    @Override\n    public List<AppDto> queryAll(AppQueryCriteria criteria){\n        return appMapper.toDto(appRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));\n    }\n\n    @Override\n    public AppDto findById(Long id) {\n        App app = appRepository.findById(id).orElseGet(App::new);\n        ValidationUtil.isNull(app.getId(),\"App\",\"id\",id);\n        return appMapper.toDto(app);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(App resources) {\n        // 验证应用名称是否存在恶意攻击payload，https://github.com/elunez/eladmin/issues/873\n        String appName = resources.getName();\n        if (appName.contains(\";\") || appName.contains(\"|\") || appName.contains(\"&\")) {\n            throw new IllegalArgumentException(\"非法的应用名称，请勿包含[; | &]等特殊字符\");\n        }\n        verification(resources);\n        appRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(App resources) {\n        // 验证应用名称是否存在恶意攻击payload，https://github.com/elunez/eladmin/issues/873\n        String appName = resources.getName();\n        if (appName.contains(\";\") || appName.contains(\"|\") || appName.contains(\"&\")) {\n            throw new IllegalArgumentException(\"非法的应用名称，请勿包含[; | &]等特殊字符\");\n        }\n        verification(resources);\n        App app = appRepository.findById(resources.getId()).orElseGet(App::new);\n        ValidationUtil.isNull(app.getId(),\"App\",\"id\",resources.getId());\n        app.copy(resources);\n        appRepository.save(app);\n    }\n\n    private void verification(App resources){\n        String opt = \"/opt\";\n        String home = \"/home\";\n        if (!(resources.getUploadPath().startsWith(opt) || resources.getUploadPath().startsWith(home))) {\n            throw new BadRequestException(\"文件只能上传在opt目录或者home目录 \");\n        }\n        if (!(resources.getDeployPath().startsWith(opt) || resources.getDeployPath().startsWith(home))) {\n            throw new BadRequestException(\"文件只能部署在opt目录或者home目录 \");\n        }\n        if (!(resources.getBackupPath().startsWith(opt) || resources.getBackupPath().startsWith(home))) {\n            throw new BadRequestException(\"文件只能备份在opt目录或者home目录 \");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        for (Long id : ids) {\n            appRepository.deleteById(id);\n        }\n    }\n\n    @Override\n    public void download(List<AppDto> queryAll, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (AppDto appDto : queryAll) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"应用名称\", appDto.getName());\n            map.put(\"端口\", appDto.getPort());\n            map.put(\"上传目录\", appDto.getUploadPath());\n            map.put(\"部署目录\", appDto.getDeployPath());\n            map.put(\"备份目录\", appDto.getBackupPath());\n            map.put(\"启动脚本\", appDto.getStartScript());\n            map.put(\"部署脚本\", appDto.getDeployScript());\n            map.put(\"创建日期\", appDto.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DatabaseServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.impl;\n\nimport cn.hutool.core.util.IdUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.modules.maint.domain.Database;\nimport me.zhengjie.modules.maint.repository.DatabaseRepository;\nimport me.zhengjie.modules.maint.service.DatabaseService;\nimport me.zhengjie.modules.maint.service.dto.DatabaseDto;\nimport me.zhengjie.modules.maint.service.dto.DatabaseQueryCriteria;\nimport me.zhengjie.modules.maint.service.mapstruct.DatabaseMapper;\nimport me.zhengjie.modules.maint.util.SqlUtils;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class DatabaseServiceImpl implements DatabaseService {\n\n    private final DatabaseRepository databaseRepository;\n    private final DatabaseMapper databaseMapper;\n\n    @Override\n    public PageResult<DatabaseDto> queryAll(DatabaseQueryCriteria criteria, Pageable pageable){\n        Page<Database> page = databaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(databaseMapper::toDto));\n    }\n\n    @Override\n    public List<DatabaseDto> queryAll(DatabaseQueryCriteria criteria){\n        return databaseMapper.toDto(databaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));\n    }\n\n    @Override\n    public DatabaseDto findById(String id) {\n        Database database = databaseRepository.findById(id).orElseGet(Database::new);\n        ValidationUtil.isNull(database.getId(),\"Database\",\"id\",id);\n        return databaseMapper.toDto(database);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(Database resources) {\n        resources.setId(IdUtil.simpleUUID());\n        databaseRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(Database resources) {\n        Database database = databaseRepository.findById(resources.getId()).orElseGet(Database::new);\n        ValidationUtil.isNull(database.getId(),\"Database\",\"id\",resources.getId());\n        database.copy(resources);\n        databaseRepository.save(database);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<String> ids) {\n        for (String id : ids) {\n            databaseRepository.deleteById(id);\n        }\n    }\n\n    @Override\n    public boolean testConnection(Database resources) {\n        try {\n            return SqlUtils.testConnection(resources.getJdbcUrl(), resources.getUserName(), resources.getPwd());\n        } catch (Exception e) {\n            log.error(e.getMessage());\n            return false;\n        }\n    }\n\n    @Override\n    public void download(List<DatabaseDto> queryAll, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (DatabaseDto databaseDto : queryAll) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"数据库名称\", databaseDto.getName());\n            map.put(\"数据库连接地址\", databaseDto.getJdbcUrl());\n            map.put(\"用户名\", databaseDto.getUserName());\n            map.put(\"创建日期\", databaseDto.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployHistoryServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.impl;\n\nimport cn.hutool.core.util.IdUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport me.zhengjie.modules.maint.repository.DeployHistoryRepository;\nimport me.zhengjie.modules.maint.service.DeployHistoryService;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryDto;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryQueryCriteria;\nimport me.zhengjie.modules.maint.service.mapstruct.DeployHistoryMapper;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Service\n@RequiredArgsConstructor\npublic class DeployHistoryServiceImpl implements DeployHistoryService {\n\n    private final DeployHistoryRepository deployhistoryRepository;\n    private final DeployHistoryMapper deployhistoryMapper;\n\n    @Override\n    public PageResult<DeployHistoryDto> queryAll(DeployHistoryQueryCriteria criteria, Pageable pageable){\n        Page<DeployHistory> page = deployhistoryRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(deployhistoryMapper::toDto));\n    }\n\n    @Override\n    public List<DeployHistoryDto> queryAll(DeployHistoryQueryCriteria criteria){\n        return deployhistoryMapper.toDto(deployhistoryRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));\n    }\n\n    @Override\n    public DeployHistoryDto findById(String id) {\n        DeployHistory deployhistory = deployhistoryRepository.findById(id).orElseGet(DeployHistory::new);\n        ValidationUtil.isNull(deployhistory.getId(),\"DeployHistory\",\"id\",id);\n        return deployhistoryMapper.toDto(deployhistory);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(DeployHistory resources) {\n        resources.setId(IdUtil.simpleUUID());\n        deployhistoryRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<String> ids) {\n        for (String id : ids) {\n            deployhistoryRepository.deleteById(id);\n        }\n    }\n\n    @Override\n    public void download(List<DeployHistoryDto> queryAll, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (DeployHistoryDto deployHistoryDto : queryAll) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"部署编号\", deployHistoryDto.getDeployId());\n            map.put(\"应用名称\", deployHistoryDto.getAppName());\n            map.put(\"部署IP\", deployHistoryDto.getIp());\n            map.put(\"部署时间\", deployHistoryDto.getDeployDate());\n            map.put(\"部署人员\", deployHistoryDto.getDeployUser());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.impl;\n\nimport cn.hutool.core.date.DatePattern;\nimport cn.hutool.core.date.DateUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.maint.domain.App;\nimport me.zhengjie.modules.maint.domain.Deploy;\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport me.zhengjie.modules.maint.domain.ServerDeploy;\nimport me.zhengjie.modules.maint.repository.DeployRepository;\nimport me.zhengjie.modules.maint.service.DeployHistoryService;\nimport me.zhengjie.modules.maint.service.DeployService;\nimport me.zhengjie.modules.maint.service.ServerDeployService;\nimport me.zhengjie.modules.maint.service.dto.AppDto;\nimport me.zhengjie.modules.maint.service.dto.DeployDto;\nimport me.zhengjie.modules.maint.service.dto.DeployQueryCriteria;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployDto;\nimport me.zhengjie.modules.maint.service.mapstruct.DeployMapper;\nimport me.zhengjie.modules.maint.util.ExecuteShellUtil;\nimport me.zhengjie.modules.maint.util.ScpClientUtil;\nimport me.zhengjie.modules.maint.websocket.MsgType;\nimport me.zhengjie.modules.maint.websocket.SocketMsg;\nimport me.zhengjie.modules.maint.websocket.WebSocketServer;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n * @author zhanghouying\n * @date 2019-08-24\n */\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class DeployServiceImpl implements DeployService {\n\n\tprivate final String FILE_SEPARATOR = \"/\";\n\tprivate final DeployRepository deployRepository;\n\tprivate final DeployMapper deployMapper;\n\tprivate final ServerDeployService serverDeployService;\n\tprivate final DeployHistoryService deployHistoryService;\n\t/**\n\t * 循环次数\n\t */\n\tprivate final Integer count = 30;\n\n\n\t@Override\n\tpublic PageResult<DeployDto> queryAll(DeployQueryCriteria criteria, Pageable pageable) {\n\t\tPage<Deploy> page = deployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);\n\t\treturn PageUtil.toPage(page.map(deployMapper::toDto));\n\t}\n\n\t@Override\n\tpublic List<DeployDto> queryAll(DeployQueryCriteria criteria) {\n\t\treturn deployMapper.toDto(deployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));\n\t}\n\n\t@Override\n\tpublic DeployDto findById(Long id) {\n\t\tDeploy deploy = deployRepository.findById(id).orElseGet(Deploy::new);\n\t\tValidationUtil.isNull(deploy.getId(), \"Deploy\", \"id\", id);\n\t\treturn deployMapper.toDto(deploy);\n\t}\n\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic void create(Deploy resources) {\n\t\tdeployRepository.save(resources);\n\t}\n\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic void update(Deploy resources) {\n\t\tDeploy deploy = deployRepository.findById(resources.getId()).orElseGet(Deploy::new);\n\t\tValidationUtil.isNull(deploy.getId(), \"Deploy\", \"id\", resources.getId());\n\t\tdeploy.copy(resources);\n\t\tdeployRepository.save(deploy);\n\t}\n\n\t@Override\n\t@Transactional(rollbackFor = Exception.class)\n\tpublic void delete(Set<Long> ids) {\n\t\tfor (Long id : ids) {\n\t\t\tdeployRepository.deleteById(id);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void deploy(String fileSavePath, Long id) {\n\t\tdeployApp(fileSavePath, id);\n\t}\n\n\t/**\n\t * @param fileSavePath 本机路径\n\t * @param id ID\n\t */\n\tprivate void deployApp(String fileSavePath, Long id) {\n\n\t\tDeployDto deploy = findById(id);\n\t\tif (deploy == null) {\n\t\t\tsendMsg(\"部署信息不存在\", MsgType.ERROR);\n\t\t\tthrow new BadRequestException(\"部署信息不存在\");\n\t\t}\n\t\tAppDto app = deploy.getApp();\n\t\tif (app == null) {\n\t\t\tsendMsg(\"包对应应用信息不存在\", MsgType.ERROR);\n\t\t\tthrow new BadRequestException(\"包对应应用信息不存在\");\n\t\t}\n\t\tint port = app.getPort();\n\t\t//这个是服务器部署路径\n\t\tString uploadPath = app.getUploadPath();\n\t\tStringBuilder sb = new StringBuilder();\n\t\tString msg;\n\t\tSet<ServerDeployDto> deploys = deploy.getDeploys();\n\t\tfor (ServerDeployDto deployDTO : deploys) {\n\t\t\tString ip = deployDTO.getIp();\n\t\t\tExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip);\n\t\t\t//判断是否第一次部署\n\t\t\tboolean flag = checkFile(executeShellUtil, app);\n\t\t\t//第一步要确认服务器上有这个目录\n\t\t\texecuteShellUtil.execute(\"mkdir -p \" + app.getUploadPath());\n\t\t\texecuteShellUtil.execute(\"mkdir -p \" + app.getBackupPath());\n\t\t\texecuteShellUtil.execute(\"mkdir -p \" + app.getDeployPath());\n\t\t\t//上传文件\n\t\t\tmsg = String.format(\"登陆到服务器:%s\", ip);\n\t\t\tScpClientUtil scpClientUtil = getScpClientUtil(ip);\n\t\t\tlog.info(msg);\n\t\t\tsendMsg(msg, MsgType.INFO);\n\t\t\tmsg = String.format(\"上传文件到服务器:%s<br>目录:%s下，请稍等...\", ip, uploadPath);\n\t\t\tsendMsg(msg, MsgType.INFO);\n\t\t\tscpClientUtil.putFile(fileSavePath, uploadPath);\n\t\t\tif (flag) {\n\t\t\t\tsendMsg(\"停止原来应用\", MsgType.INFO);\n\t\t\t\t//停止应用\n\t\t\t\tstopApp(port, executeShellUtil);\n\t\t\t\tsendMsg(\"备份原来应用\", MsgType.INFO);\n\t\t\t\t//备份应用\n\t\t\t\tbackupApp(executeShellUtil, ip, app.getDeployPath()+FILE_SEPARATOR, app.getName(), app.getBackupPath()+FILE_SEPARATOR, id);\n\t\t\t}\n\t\t\tsendMsg(\"部署应用\", MsgType.INFO);\n\t\t\t//部署文件,并启动应用\n\t\t\tString deployScript = app.getDeployScript();\n\t\t\texecuteShellUtil.execute(deployScript);\n\t\t\tsleep(3);\n\t\t\tsendMsg(\"应用部署中，请耐心等待部署结果，或者稍后手动查看部署状态\", MsgType.INFO);\n\t\t\tint i  = 0;\n\t\t\tboolean result = false;\n\t\t\t// 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败\n\t\t\twhile (i++ < count){\n\t\t\t\tresult = checkIsRunningStatus(port, executeShellUtil);\n\t\t\t\tif(result){\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// 休眠6秒\n\t\t\t\tsleep(6);\n\t\t\t}\n\t\t\tsb.append(\"服务器:\").append(deployDTO.getName()).append(\"<br>应用:\").append(app.getName());\n\t\t\tsendResultMsg(result, sb);\n\t\t\texecuteShellUtil.close();\n\t\t}\n\t}\n\n\tprivate void sleep(int second) {\n\t\ttry {\n\t\t\tThread.sleep(second * 1000);\n\t\t} catch (InterruptedException e) {\n\t\t\tlog.error(e.getMessage(),e);\n\t\t}\n\t}\n\n\tprivate void backupApp(ExecuteShellUtil executeShellUtil, String ip, String fileSavePath, String appName, String backupPath, Long id) {\n\t\tString deployDate = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tbackupPath += appName + FILE_SEPARATOR + deployDate + \"\\n\";\n\t\tsb.append(\"mkdir -p \").append(backupPath);\n\t\tsb.append(\"mv -f \").append(fileSavePath);\n\t\tsb.append(appName).append(\" \").append(backupPath);\n\t\tlog.info(\"备份应用脚本:\" + sb.toString());\n\t\texecuteShellUtil.execute(sb.toString());\n\t\t//还原信息入库\n\t\tDeployHistory deployHistory = new DeployHistory();\n\t\tdeployHistory.setAppName(appName);\n\t\tdeployHistory.setDeployUser(SecurityUtils.getCurrentUsername());\n\t\tdeployHistory.setIp(ip);\n\t\tdeployHistory.setDeployId(id);\n\t\tdeployHistoryService.create(deployHistory);\n\t}\n\n\t/**\n\t * 停App\n\t *\n\t * @param port 端口\n\t * @param executeShellUtil /\n\t */\n\tprivate void stopApp(int port, ExecuteShellUtil executeShellUtil) {\n\t\t//发送停止命令\n\t\texecuteShellUtil.execute(String.format(\"lsof -i :%d|grep -v \\\"PID\\\"|awk '{print \\\"kill -9\\\",$2}'|sh\", port));\n\n\t}\n\n\t/**\n\t * 指定端口程序是否在运行\n\t *\n\t * @param port 端口\n\t * @param executeShellUtil /\n\t * @return true 正在运行  false 已经停止\n\t */\n\tprivate boolean checkIsRunningStatus(int port, ExecuteShellUtil executeShellUtil) {\n\t\tString result = executeShellUtil.executeForResult(String.format(\"fuser -n tcp %d\", port));\n\t\treturn result.indexOf(\"/tcp:\")>0;\n\t}\n\n\tprivate void sendMsg(String msg, MsgType msgType) {\n\t\ttry {\n\t\t\tWebSocketServer.sendInfo(new SocketMsg(msg, msgType), \"deploy\");\n\t\t} catch (IOException e) {\n\t\t\tlog.error(e.getMessage(),e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic String serverStatus(Deploy resources) {\n\t\tSet<ServerDeploy> serverDeploys = resources.getDeploys();\n\t\tApp app = resources.getApp();\n\t\tfor (ServerDeploy serverDeploy : serverDeploys) {\n\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\tExecuteShellUtil executeShellUtil = getExecuteShellUtil(serverDeploy.getIp());\n\t\t\tsb.append(\"服务器:\").append(serverDeploy.getName()).append(\"<br>应用:\").append(app.getName());\n\t\t\tboolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);\n\t\t\tif (result) {\n\t\t\t\tsb.append(\"<br>正在运行\");\n\t\t\t\tsendMsg(sb.toString(), MsgType.INFO);\n\t\t\t} else {\n\t\t\t\tsb.append(\"<br>已停止!\");\n\t\t\t\tsendMsg(sb.toString(), MsgType.ERROR);\n\t\t\t}\n\t\t\tlog.info(sb.toString());\n\t\t\texecuteShellUtil.close();\n\t\t}\n\t\treturn \"执行完毕\";\n\t}\n\n\tprivate boolean checkFile(ExecuteShellUtil executeShellUtil, AppDto app) {\n\t\tString deployPath = app.getDeployPath();\n\t\tString appName = app.getName();\n\t\t// 使用安全的命令执行方式，避免直接拼接字符串，https://github.com/elunez/eladmin/issues/873\n\t\tString[] command = {\"find\", deployPath, \"-name\", appName};\n\t\tString result = executeShellUtil.executeForResult(Arrays.toString(command));\n\t\treturn result.contains(appName);\n\t}\n\n\t/**\n\t * 启动服务\n\t * @param resources /\n\t * @return /\n\t */\n\t@Override\n\tpublic String startServer(Deploy resources) {\n\t\tSet<ServerDeploy> deploys = resources.getDeploys();\n\t\tApp app = resources.getApp();\n\t\tfor (ServerDeploy deploy : deploys) {\n\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\tExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp());\n\t\t\t//为了防止重复启动，这里先停止应用\n\t\t\tstopApp(app.getPort(), executeShellUtil);\n\t\t\tsb.append(\"服务器:\").append(deploy.getName()).append(\"<br>应用:\").append(app.getName());\n\t\t\tsendMsg(\"下发启动命令\", MsgType.INFO);\n\t\t\texecuteShellUtil.execute(app.getStartScript());\n\t\t\tsleep(3);\n\t\t\tsendMsg(\"应用启动中，请耐心等待启动结果，或者稍后手动查看运行状态\", MsgType.INFO);\n\t\t\tint i  = 0;\n\t\t\tboolean result = false;\n\t\t\t// 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败\n\t\t\twhile (i++ < count){\n\t\t\t\tresult = checkIsRunningStatus(app.getPort(), executeShellUtil);\n\t\t\t\tif(result){\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// 休眠6秒\n\t\t\t\tsleep(6);\n\t\t\t}\n\t\t\tsendResultMsg(result, sb);\n\t\t\tlog.info(sb.toString());\n\t\t\texecuteShellUtil.close();\n\t\t}\n\t\treturn \"执行完毕\";\n\t}\n\n\t/**\n\t * 停止服务\n\t * @param resources /\n\t * @return /\n\t */\n\t@Override\n\tpublic String stopServer(Deploy resources) {\n\t\tSet<ServerDeploy> deploys = resources.getDeploys();\n\t\tApp app = resources.getApp();\n\t\tfor (ServerDeploy deploy : deploys) {\n\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\tExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp());\n\t\t\tsb.append(\"服务器:\").append(deploy.getName()).append(\"<br>应用:\").append(app.getName());\n\t\t\tsendMsg(\"下发停止命令\", MsgType.INFO);\n\t\t\t//停止应用\n\t\t\tstopApp(app.getPort(), executeShellUtil);\n\t\t\tsleep(1);\n\t\t\tboolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);\n\t\t\tif (result) {\n\t\t\t\tsb.append(\"<br>关闭失败!\");\n\t\t\t\tsendMsg(sb.toString(), MsgType.ERROR);\n\t\t\t} else {\n\t\t\t\tsb.append(\"<br>关闭成功!\");\n\t\t\t\tsendMsg(sb.toString(), MsgType.INFO);\n\t\t\t}\n\t\t\tlog.info(sb.toString());\n\t\t\texecuteShellUtil.close();\n\t\t}\n\t\treturn \"执行完毕\";\n\t}\n\n\t@Override\n\tpublic String serverReduction(DeployHistory resources) {\n\t\tLong deployId = resources.getDeployId();\n\t\tDeploy deployInfo = deployRepository.findById(deployId).orElseGet(Deploy::new);\n\t\tString deployDate = DateUtil.format(resources.getDeployDate(), DatePattern.PURE_DATETIME_PATTERN);\n\t\tApp app = deployInfo.getApp();\n\t\tif (app == null) {\n\t\t\tsendMsg(\"应用信息不存在：\" + resources.getAppName(), MsgType.ERROR);\n\t\t\tthrow new BadRequestException(\"应用信息不存在：\" + resources.getAppName());\n\t\t}\n\t\tString backupPath = app.getBackupPath()+FILE_SEPARATOR;\n\t\tbackupPath += resources.getAppName() + FILE_SEPARATOR + deployDate;\n\t\t//这个是服务器部署路径\n\t\tString deployPath = app.getDeployPath();\n\t\tString ip = resources.getIp();\n\t\tExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip);\n\t\tString msg;\n\n\t\tmsg = String.format(\"登陆到服务器:%s\", ip);\n\t\tlog.info(msg);\n\t\tsendMsg(msg, MsgType.INFO);\n\t\tsendMsg(\"停止原来应用\", MsgType.INFO);\n\t\t//停止应用\n\t\tstopApp(app.getPort(), executeShellUtil);\n\t\t//删除原来应用\n\t\tsendMsg(\"删除应用\", MsgType.INFO);\n\t\texecuteShellUtil.execute(\"rm -rf \" + deployPath + FILE_SEPARATOR + resources.getAppName());\n\t\t//还原应用\n\t\tsendMsg(\"还原应用\", MsgType.INFO);\n\t\texecuteShellUtil.execute(\"cp -r \" + backupPath + \"/. \" + deployPath);\n\t\tsendMsg(\"启动应用\", MsgType.INFO);\n\t\texecuteShellUtil.execute(app.getStartScript());\n\t\tsendMsg(\"应用启动中，请耐心等待启动结果，或者稍后手动查看启动状态\", MsgType.INFO);\n\t\tint i  = 0;\n\t\tboolean result = false;\n\t\t// 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败\n\t\twhile (i++ < count){\n\t\t\tresult = checkIsRunningStatus(app.getPort(), executeShellUtil);\n\t\t\tif(result){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// 休眠6秒\n\t\t\tsleep(6);\n\t\t}\n\t\tStringBuilder sb = new StringBuilder();\n\t\tsb.append(\"服务器:\").append(ip).append(\"<br>应用:\").append(resources.getAppName());\n\t\tsendResultMsg(result, sb);\n\t\texecuteShellUtil.close();\n\t\treturn \"\";\n\t}\n\n\tprivate ExecuteShellUtil getExecuteShellUtil(String ip) {\n\t\tServerDeployDto serverDeployDTO = serverDeployService.findByIp(ip);\n\t\tif (serverDeployDTO == null) {\n\t\t\tsendMsg(\"IP对应服务器信息不存在：\" + ip, MsgType.ERROR);\n\t\t\tthrow new BadRequestException(\"IP对应服务器信息不存在：\" + ip);\n\t\t}\n\t\treturn new ExecuteShellUtil(ip, serverDeployDTO.getAccount(), serverDeployDTO.getPassword(),serverDeployDTO.getPort());\n\t}\n\n\tprivate ScpClientUtil getScpClientUtil(String ip) {\n\t\tServerDeployDto serverDeployDTO = serverDeployService.findByIp(ip);\n\t\tif (serverDeployDTO == null) {\n\t\t\tsendMsg(\"IP对应服务器信息不存在：\" + ip, MsgType.ERROR);\n\t\t\tthrow new BadRequestException(\"IP对应服务器信息不存在：\" + ip);\n\t\t}\n\t\treturn ScpClientUtil.getInstance(ip, serverDeployDTO.getPort(), serverDeployDTO.getAccount(), serverDeployDTO.getPassword());\n\t}\n\n\tprivate void sendResultMsg(boolean result, StringBuilder sb) {\n\t\tif (result) {\n\t\t\tsb.append(\"<br>启动成功!\");\n\t\t\tsendMsg(sb.toString(), MsgType.INFO);\n\t\t} else {\n\t\t\tsb.append(\"<br>启动失败!\");\n\t\t\tsendMsg(sb.toString(), MsgType.ERROR);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void download(List<DeployDto> queryAll, HttpServletResponse response) throws IOException {\n\t\tList<Map<String, Object>> list = new ArrayList<>();\n\t\tfor (DeployDto deployDto : queryAll) {\n\t\t\tMap<String,Object> map = new LinkedHashMap<>();\n\t\t\tmap.put(\"应用名称\", deployDto.getApp().getName());\n\t\t\tmap.put(\"服务器\", deployDto.getServers());\n\t\t\tmap.put(\"部署日期\", deployDto.getCreateTime());\n\t\t\tlist.add(map);\n\t\t}\n\t\tFileUtil.downloadExcel(list, response);\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/ServerDeployServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.impl;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.maint.domain.ServerDeploy;\nimport me.zhengjie.modules.maint.repository.ServerDeployRepository;\nimport me.zhengjie.modules.maint.service.ServerDeployService;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployDto;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployQueryCriteria;\nimport me.zhengjie.modules.maint.service.mapstruct.ServerDeployMapper;\nimport me.zhengjie.modules.maint.util.ExecuteShellUtil;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Service\n@RequiredArgsConstructor\npublic class ServerDeployServiceImpl implements ServerDeployService {\n\n    private final ServerDeployRepository serverDeployRepository;\n    private final ServerDeployMapper serverDeployMapper;\n\n    @Override\n    public PageResult<ServerDeployDto> queryAll(ServerDeployQueryCriteria criteria, Pageable pageable){\n        Page<ServerDeploy> page = serverDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(serverDeployMapper::toDto));\n    }\n\n    @Override\n    public List<ServerDeployDto> queryAll(ServerDeployQueryCriteria criteria){\n        return serverDeployMapper.toDto(serverDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));\n    }\n\n    @Override\n    public ServerDeployDto findById(Long id) {\n        ServerDeploy server = serverDeployRepository.findById(id).orElseGet(ServerDeploy::new);\n        ValidationUtil.isNull(server.getId(),\"ServerDeploy\",\"id\",id);\n        return serverDeployMapper.toDto(server);\n    }\n\n    @Override\n    public ServerDeployDto findByIp(String ip) {\n        ServerDeploy deploy = serverDeployRepository.findByIp(ip);\n        return serverDeployMapper.toDto(deploy);\n    }\n\n    @Override\n    public Boolean testConnect(ServerDeploy resources) {\n        ExecuteShellUtil executeShellUtil = null;\n        try {\n            executeShellUtil = new ExecuteShellUtil(resources.getIp(), resources.getAccount(), resources.getPassword(),resources.getPort());\n            return executeShellUtil.execute(\"ls\")==0;\n        } catch (Exception e) {\n            return false;\n        }finally {\n            if (executeShellUtil != null) {\n                executeShellUtil.close();\n            }\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(ServerDeploy resources) {\n\t\tserverDeployRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(ServerDeploy resources) {\n        ServerDeploy serverDeploy = serverDeployRepository.findById(resources.getId()).orElseGet(ServerDeploy::new);\n        ValidationUtil.isNull( serverDeploy.getId(),\"ServerDeploy\",\"id\",resources.getId());\n        serverDeploy.copy(resources);\n        serverDeployRepository.save(serverDeploy);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        for (Long id : ids) {\n            serverDeployRepository.deleteById(id);\n        }\n    }\n\n    @Override\n    public void download(List<ServerDeployDto> queryAll, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (ServerDeployDto deployDto : queryAll) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"服务器名称\", deployDto.getName());\n            map.put(\"服务器IP\", deployDto.getIp());\n            map.put(\"端口\", deployDto.getPort());\n            map.put(\"账号\", deployDto.getAccount());\n            map.put(\"创建日期\", deployDto.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/AppMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.maint.domain.App;\nimport me.zhengjie.modules.maint.service.dto.AppDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Mapper(componentModel = \"spring\",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface AppMapper extends BaseMapper<AppDto, App> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DatabaseMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.maint.domain.Database;\nimport me.zhengjie.modules.maint.service.dto.DatabaseDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DatabaseMapper extends BaseMapper<DatabaseDto, Database> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployHistoryMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.maint.domain.DeployHistory;\nimport me.zhengjie.modules.maint.service.dto.DeployHistoryDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Mapper(componentModel = \"spring\",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DeployHistoryMapper extends BaseMapper<DeployHistoryDto, DeployHistory> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.maint.domain.Deploy;\nimport me.zhengjie.modules.maint.service.dto.DeployDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Mapper(componentModel = \"spring\",uses = {AppMapper.class, ServerDeployMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DeployMapper extends BaseMapper<DeployDto, Deploy> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/ServerDeployMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.maint.domain.ServerDeploy;\nimport me.zhengjie.modules.maint.service.dto.ServerDeployDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author zhanghouying\n* @date 2019-08-24\n*/\n@Mapper(componentModel = \"spring\",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface ServerDeployMapper extends BaseMapper<ServerDeployDto, ServerDeploy> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ExecuteShellUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.util;\n\nimport cn.hutool.core.io.IoUtil;\nimport com.jcraft.jsch.ChannelShell;\nimport com.jcraft.jsch.JSch;\nimport com.jcraft.jsch.Session;\nimport lombok.extern.slf4j.Slf4j;\nimport java.io.*;\nimport java.util.Vector;\n\n/**\n * 执行shell命令\n *\n * @author ZhangHouYing\n * @date 2019/8/10\n */\n@Slf4j\npublic class ExecuteShellUtil {\n\n\tprivate Vector<String> stdout;\n\n\tSession session;\n\n\tpublic ExecuteShellUtil(final String ipAddress, final String username, final String password,int port) {\n\t\ttry {\n\t\t\tJSch jsch = new JSch();\n\t\t\tsession = jsch.getSession(username, ipAddress, port);\n\t\t\tsession.setPassword(password);\n\t\t\tsession.setConfig(\"StrictHostKeyChecking\", \"no\");\n\t\t\tsession.connect(3000);\n\t\t} catch (Exception e) {\n\t\t\tlog.error(e.getMessage(),e);\n\t\t}\n\n\t}\n\n\tpublic int execute(final String command) {\n\t\tint returnCode = 0;\n\t\tChannelShell channel = null;\n\t\tPrintWriter printWriter = null;\n\t\tBufferedReader input = null;\n\t\tstdout = new Vector<String>();\n\t\ttry {\n\t\t\tchannel = (ChannelShell) session.openChannel(\"shell\");\n\t\t\tchannel.connect();\n\t\t\tinput = new BufferedReader(new InputStreamReader(channel.getInputStream()));\n\t\t\tprintWriter = new PrintWriter(channel.getOutputStream());\n\t\t\tprintWriter.println(command);\n\t\t\tprintWriter.println(\"exit\");\n\t\t\tprintWriter.flush();\n\t\t\tlog.info(\"The remote command is: \");\n\t\t\tString line;\n\t\t\twhile ((line = input.readLine()) != null) {\n\t\t\t\tstdout.add(line);\n\t\t\t\tSystem.out.println(line);\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tlog.error(e.getMessage(),e);\n\t\t\treturn -1;\n\t\t}finally {\n\t\t\tIoUtil.close(printWriter);\n\t\t\tIoUtil.close(input);\n\t\t\tif (channel != null) {\n\t\t\t\tchannel.disconnect();\n\t\t\t}\n\t\t}\n\t\treturn returnCode;\n\t}\n\n\tpublic void close(){\n\t\tif (session != null) {\n\t\t\tsession.disconnect();\n\t\t}\n\t}\n\n\tpublic String executeForResult(String command) {\n\t\texecute(command);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (String str : stdout) {\n\t\t\tsb.append(str);\n\t\t}\n\t\treturn sb.toString();\n\t}\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ScpClientUtil.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.util;\n\nimport ch.ethz.ssh2.Connection;\nimport ch.ethz.ssh2.SCPClient;\nimport com.google.common.collect.Maps;\nimport me.zhengjie.utils.StringUtils;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * 远程执行linux命令\n * @author ZhangHouYing\n * @date 2019-08-10 10:06\n */\npublic class ScpClientUtil {\n\n\tprivate final String ip;\n\tprivate final int port;\n\tprivate final String username;\n\tprivate final String password;\n\n\tstatic private final Map<String,ScpClientUtil> instance = Maps.newHashMap();\n\n\tstatic synchronized public ScpClientUtil getInstance(String ip, int port, String username, String password) {\n\t\tinstance.computeIfAbsent(ip, i -> new ScpClientUtil(i, port, username, password));\n\t\treturn instance.get(ip);\n\t}\n\n\tpublic ScpClientUtil(String ip, int port, String username, String password) {\n\t\tthis.ip = ip;\n\t\tthis.port = port;\n\t\tthis.username = username;\n\t\tthis.password = password;\n\t}\n\n\tpublic void getFile(String remoteFile, String localTargetDirectory) {\n\t\tConnection conn = new Connection(ip, port);\n\t\ttry {\n\t\t\tconn.connect();\n\t\t\tboolean isAuthenticated = conn.authenticateWithPassword(username, password);\n\t\t\tif (!isAuthenticated) {\n\t\t\t\tSystem.err.println(\"authentication failed\");\n\t\t\t}\n\t\t\tSCPClient client = new SCPClient(conn);\n\t\t\tclient.get(remoteFile, localTargetDirectory);\n\t\t} catch (IOException ex) {\n\t\t\tLogger.getLogger(SCPClient.class.getName()).log(Level.SEVERE, null, ex);\n\t\t}finally{\n\t\t\tconn.close();\n\t\t}\n\t}\n\n\tpublic void putFile(String localFile, String remoteTargetDirectory) {\n\t\tputFile(localFile, null, remoteTargetDirectory);\n\t}\n\n\tpublic void putFile(String localFile, String remoteFileName, String remoteTargetDirectory) {\n\t\tputFile(localFile, remoteFileName, remoteTargetDirectory,null);\n\t}\n\n\tpublic void putFile(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) {\n\t\tConnection conn = new Connection(ip, port);\n\t\ttry {\n\t\t\tconn.connect();\n\t\t\tboolean isAuthenticated = conn.authenticateWithPassword(username, password);\n\t\t\tif (!isAuthenticated) {\n\t\t\t\tSystem.err.println(\"authentication failed\");\n\t\t\t}\n\t\t\tSCPClient client = new SCPClient(conn);\n\t\t\tif (StringUtils.isBlank(mode)) {\n\t\t\t\tmode = \"0600\";\n\t\t\t}\n\t\t\tif (remoteFileName == null) {\n\t\t\t\tclient.put(localFile, remoteTargetDirectory);\n\t\t\t} else {\n\t\t\t\tclient.put(localFile, remoteFileName, remoteTargetDirectory, mode);\n\t\t\t}\n\t\t} catch (IOException ex) {\n\t\t\tLogger.getLogger(ScpClientUtil.class.getName()).log(Level.SEVERE, null, ex);\n\t\t}finally{\n\t\t\tconn.close();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/util/SqlUtils.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.util;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport com.alibaba.druid.util.StringUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.modules.maint.domain.enums.DataTypeEnum;\nimport me.zhengjie.utils.CloseUtil;\nimport javax.sql.DataSource;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author /\n */\n@Slf4j\npublic class SqlUtils {\n\n\t/**\n\t * 获取数据源\n\t *\n\t * @param jdbcUrl /\n\t * @param userName /\n\t * @param password /\n\t * @return DataSource\n\t */\n\tprivate static DataSource getDataSource(String jdbcUrl, String userName, String password) {\n\t\tDruidDataSource druidDataSource = new DruidDataSource();\n\t\tString className;\n\t\ttry {\n\t\t\tclassName = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName();\n\t\t} catch (SQLException e) {\n\t\t\tthrow new RuntimeException(\"Get class name error: =\" + jdbcUrl);\n\t\t}\n\t\tif (StringUtils.isEmpty(className)) {\n\t\t\tDataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl);\n\t\t\tif (null == dataTypeEnum) {\n\t\t\t\tthrow new RuntimeException(\"Not supported data type: jdbcUrl=\" + jdbcUrl);\n\t\t\t}\n\t\t\tdruidDataSource.setDriverClassName(dataTypeEnum.getDriver());\n\t\t} else {\n\t\t\tdruidDataSource.setDriverClassName(className);\n\t\t}\n\n\t\t// 去掉不安全的参数\n\t\tjdbcUrl = sanitizeJdbcUrl(jdbcUrl);\n\n\t\tdruidDataSource.setUrl(jdbcUrl);\n\t\tdruidDataSource.setUsername(userName);\n\t\tdruidDataSource.setPassword(password);\n\t\t// 配置获取连接等待超时的时间\n\t\tdruidDataSource.setMaxWait(3000);\n\t\t// 配置初始化大小、最小、最大\n\t\tdruidDataSource.setInitialSize(1);\n\t\tdruidDataSource.setMinIdle(1);\n\t\tdruidDataSource.setMaxActive(1);\n\n\t\t// 如果链接出现异常则直接判定为失败而不是一直重试\n\t\tdruidDataSource.setBreakAfterAcquireFailure(true);\n\t\ttry {\n\t\t\tdruidDataSource.init();\n\t\t} catch (SQLException e) {\n\t\t\tlog.error(\"Exception during pool initialization\", e);\n\t\t\tthrow new RuntimeException(e.getMessage());\n\t\t}\n\n\t\treturn druidDataSource;\n\t}\n\n\tprivate static Connection getConnection(String jdbcUrl, String userName, String password) {\n\t\tDataSource dataSource = getDataSource(jdbcUrl, userName, password);\n\t\tConnection connection = null;\n\t\ttry {\n\t\t\tconnection = dataSource.getConnection();\n\t\t} catch (Exception ignored) {}\n\t\ttry {\n\t\t\tint timeOut = 5;\n\t\t\tif (null == connection || connection.isClosed() || !connection.isValid(timeOut)) {\n\t\t\t\tlog.info(\"connection is closed or invalid, retry get connection!\");\n\t\t\t\tconnection = dataSource.getConnection();\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tlog.error(\"create connection error, jdbcUrl: {}\", jdbcUrl);\n\t\t\tthrow new RuntimeException(\"create connection error, jdbcUrl: \" + jdbcUrl);\n\t\t} finally {\n\t\t\tCloseUtil.close(connection);\n\t\t}\n\t\treturn connection;\n\t}\n\n\tprivate static void releaseConnection(Connection connection) {\n\t\tif (null != connection) {\n\t\t\ttry {\n\t\t\t\tconnection.close();\n\t\t\t} catch (Exception e) {\n\t\t\t\tlog.error(e.getMessage(),e);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static boolean testConnection(String jdbcUrl, String userName, String password) {\n\t\tConnection connection = null;\n\t\ttry {\n\t\t\tconnection = getConnection(jdbcUrl, userName, password);\n\t\t\tif (null != connection) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (Exception e) {\n            log.error(\"Get connection failed:{}\", e.getMessage());\n\t\t} finally {\n\t\t\treleaseConnection(connection);\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic static String executeFile(String jdbcUrl, String userName, String password, File sqlFile) {\n\t\tConnection connection = getConnection(jdbcUrl, userName, password);\n\t\ttry {\n\t\t\tbatchExecute(connection, readSqlList(sqlFile));\n\t\t} catch (Exception e) {\n\t\t\tlog.error(\"sql脚本执行发生异常:{}\",e.getMessage());\n\t\t\treturn e.getMessage();\n\t\t}finally {\n\t\t\treleaseConnection(connection);\n\t\t}\n\t\treturn \"success\";\n\t}\n\n\t/**\n\t * 批量执行sql\n\t * @param connection /\n\t * @param sqlList /\n\t */\n\tpublic static void batchExecute(Connection connection, List<String> sqlList) {\n\t\ttry (Statement st = connection.createStatement()) {\n\t\t\tfor (String sql : sqlList) {\n\t\t\t\t// 去除末尾的分号\n\t\t\t\tif (sql.endsWith(\";\")) {\n\t\t\t\t\tsql = sql.substring(0, sql.length() - 1);\n\t\t\t\t}\n\t\t\t\t// 检查 SQL 语句是否为空\n\t\t\t\tif (!sql.trim().isEmpty()) {\n\t\t\t\t\tst.addBatch(sql);\n\t\t\t\t}\n\t\t\t}\n\t\t\tst.executeBatch();\n\t\t} catch (SQLException e) {\n\t\t\tlog.error(\"SQL脚本批量执行发生异常: {}，错误代码: {}\", e.getMessage(), e.getErrorCode());\n\t\t}\n\t}\n\n\t/**\n\t * 将文件中的sql语句以；为单位读取到列表中\n\t * @param sqlFile /\n\t * @return /\n     */\n\tprivate static List<String> readSqlList(File sqlFile) {\n\t\tList<String> sqlList = new ArrayList<>();\n\t\tStringBuilder sb = new StringBuilder();\n\t\ttry (BufferedReader reader = Files.newBufferedReader(sqlFile.toPath(), StandardCharsets.UTF_8)) {\n\t\t\tString line;\n\t\t\twhile ((line = reader.readLine()) != null) {\n\t\t\t\tlog.info(\"line: {}\", line);\n\t\t\t\tsb.append(line.trim());\n\n\t\t\t\tif (line.trim().endsWith(\";\")) {\n\t\t\t\t\tsqlList.add(sb.toString());\n\t\t\t\t\t// 清空 StringBuilder\n\t\t\t\t\tsb.setLength(0);\n\t\t\t\t} else {\n\t\t\t\t\t// 在行之间加一个空格\n\t\t\t\t\tsb.append(\" \");\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (sb.length() > 0) {\n\t\t\t\tsqlList.add(sb.toString().trim());\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tlog.error(\"读取SQL文件时发生异常: {}\", e.getMessage());\n\t\t}\n\t\treturn sqlList;\n\t}\n\n\t/**\n\t * 去除不安全的参数\n\t * @param jdbcUrl /\n\t * @return /\n\t */\n\tprivate static String sanitizeJdbcUrl(String jdbcUrl) {\n\t\t// 定义不安全参数和其安全替代值\n\t\tString[][] unsafeParams = {\n\t\t\t\t// allowLoadLocalInfile：允许使用 LOAD DATA LOCAL INFILE，可能导致文件泄露\n\t\t\t\t{\"allowLoadLocalInfile\", \"false\"},\n\t\t\t\t// allowUrlInLocalInfile：允许在 LOAD DATA LOCAL INFILE 中使用 URL，可能导致未经授权的文件访问\n\t\t\t\t{\"allowUrlInLocalInfile\", \"false\"},\n\t\t\t\t// autoDeserialize：允许自动反序列化对象，可能导致反序列化漏洞\n\t\t\t\t{\"autoDeserialize\", \"false\"},\n\t\t\t\t// allowNanAndInf：允许使用 NaN 和 Infinity 作为数字值，可能导致不一致的数据处理\n\t\t\t\t{\"allowNanAndInf\", \"false\"},\n\t\t\t\t// allowMultiQueries：允许在一个语句中执行多个查询，可能导致 SQL 注入攻击\n\t\t\t\t{\"allowMultiQueries\", \"false\"},\n\t\t\t\t// allowPublicKeyRetrieval：允许从服务器检索公钥，可能导致中间人攻击\n\t\t\t\t{\"allowPublicKeyRetrieval\", \"false\"}\n\t\t};\n\n\t\t// 替换不安全的参数\n\t\tfor (String[] param : unsafeParams) {\n\t\t\tjdbcUrl = jdbcUrl.replaceAll(\"(?i)\" + param[0] + \"=true\", param[0] + \"=\" + param[1]);\n\t\t}\n\t\treturn jdbcUrl;\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/MsgType.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.websocket;\n\n/**\n * @author ZhangHouYing\n * @date 2019-08-10 9:56\n */\npublic enum MsgType {\n\t/** 连接 */\n\tCONNECT,\n\t/** 关闭 */\n\tCLOSE,\n\t/** 信息 */\n\tINFO,\n\t/** 错误 */\n\tERROR\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/SocketMsg.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.websocket;\n\nimport lombok.Data;\n\n/**\n * @author ZhangHouYing\n * @date 2019-08-10 9:55\n */\n@Data\npublic class SocketMsg {\n\tprivate String msg;\n\tprivate MsgType msgType;\n\n\tpublic SocketMsg(String msg, MsgType msgType) {\n\t\tthis.msg = msg;\n\t\tthis.msgType = msgType;\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/WebSocketServer.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.maint.websocket;\n\nimport com.alibaba.fastjson2.JSON;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\nimport javax.websocket.*;\nimport javax.websocket.server.PathParam;\nimport javax.websocket.server.ServerEndpoint;\nimport java.io.IOException;\nimport java.util.Objects;\nimport java.util.concurrent.CopyOnWriteArraySet;\n/**\n * @author ZhangHouYing\n * @date 2019-08-10 15:46\n */\n@ServerEndpoint(\"/webSocket/{sid}\")\n@Slf4j\n@Component\npublic class WebSocketServer {\n\n\t/**\n\t * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。\n\t */\n\tprivate static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();\n\n\t/**\n\t * 与某个客户端的连接会话，需要通过它来给客户端发送数据\n\t */\n\tprivate Session session;\n\n\t/**\n\t * 接收sid\n\t */\n\tprivate String sid=\"\";\n\t/**\n\t * 连接建立成功调用的方法\n\t * */\n\t@OnOpen\n\tpublic void onOpen(Session session,@PathParam(\"sid\") String sid) {\n\t\tthis.session = session;\n\t\t//如果存在就先删除一个，防止重复推送消息\n\t\twebSocketSet.removeIf(webSocket -> webSocket.sid.equals(sid));\n\t\twebSocketSet.add(this);\n\t\tthis.sid=sid;\n\t}\n\n\t/**\n\t * 连接关闭调用的方法\n\t */\n\t@OnClose\n\tpublic void onClose() {\n\t\twebSocketSet.remove(this);\n\t}\n\n\t/**\n\t * 收到客户端消息后调用的方法\n\t * @param message 客户端发送过来的消息*/\n\t@OnMessage\n\tpublic void onMessage(String message, Session session) {\n\t\tlog.info(\"收到来\"+sid+\"的信息:\"+message);\n\t\t//群发消息\n\t\tfor (WebSocketServer item : webSocketSet) {\n\t\t\ttry {\n\t\t\t\titem.sendMessage(message);\n\t\t\t} catch (IOException e) {\n\t\t\t\tlog.error(e.getMessage(),e);\n\t\t\t}\n\t\t}\n\t}\n\n\t@OnError\n\tpublic void onError(Session session, Throwable error) {\n\t\tlog.error(\"发生错误\", error);\n\t}\n\t/**\n\t * 实现服务器主动推送\n\t */\n\tprivate void sendMessage(String message) throws IOException {\n\t\tthis.session.getBasicRemote().sendText(message);\n\t}\n\n\n\t/**\n\t * 群发自定义消息\n\t * */\n\tpublic static void sendInfo(SocketMsg socketMsg,@PathParam(\"sid\") String sid) throws IOException {\n\t\tString message = JSON.toJSONString(socketMsg);\n\t\tlog.info(\"推送消息到\"+sid+\"，推送内容:\"+message);\n\t\tfor (WebSocketServer item : webSocketSet) {\n\t\t\ttry {\n\t\t\t\t//这里可以设定只推送给这个sid的，为null则全部推送\n\t\t\t\tif(sid==null) {\n\t\t\t\t\titem.sendMessage(message);\n\t\t\t\t}else if(item.sid.equals(sid)){\n\t\t\t\t\titem.sendMessage(message);\n\t\t\t\t}\n\t\t\t} catch (IOException ignored) { }\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) {\n\t\t\treturn true;\n\t\t}\n\t\tif (o == null || getClass() != o.getClass()) {\n\t\t\treturn false;\n\t\t}\n\t\tWebSocketServer that = (WebSocketServer) o;\n\t\treturn Objects.equals(session, that.session) &&\n\t\t\t\tObjects.equals(sid, that.sid);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn Objects.hash(session, sid);\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.config;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport me.zhengjie.modules.quartz.repository.QuartzJobRepository;\nimport me.zhengjie.modules.quartz.utils.QuartzManage;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.stereotype.Component;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\n@Component\n@RequiredArgsConstructor\npublic class JobRunner implements ApplicationRunner {\n    private static final Logger log = LoggerFactory.getLogger(JobRunner.class);\n    private final QuartzJobRepository quartzJobRepository;\n    private final QuartzManage quartzManage;\n\n    /**\n     * 项目启动时重新激活启用的定时任务\n     *\n     * @param applicationArguments /\n     */\n    @Override\n    public void run(ApplicationArguments applicationArguments) {\n        List<QuartzJob> quartzJobs = quartzJobRepository.findByIsPauseIsFalse();\n        quartzJobs.forEach(quartzManage::addJob);\n        log.info(\"Timing task injection complete\");\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/QuartzConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.config;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.spi.TriggerFiredBundle;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.config.AutowireCapableBeanFactory;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Scope;\nimport org.springframework.lang.NonNull;\nimport org.springframework.scheduling.quartz.AdaptableJobFactory;\nimport org.springframework.stereotype.Component;\n\n/**\n * 定时任务配置\n * @author /\n * @date 2019-01-07\n */\n@Slf4j\n@Configuration\n@Scope(\"singleton\")\npublic class QuartzConfig {\n\n\t/**\n\t * 解决Job中注入Spring Bean为null的问题\n\t */\n\t@Component(\"quartzJobFactory\")\n\tpublic static class QuartzJobFactory extends AdaptableJobFactory {\n\n\t\tprivate final AutowireCapableBeanFactory capableBeanFactory;\n\n\t\t@Autowired\n\t\tpublic QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {\n\t\t\tthis.capableBeanFactory = capableBeanFactory;\n\t\t}\n\n\t\t@NonNull\n\t\t@Override\n\t\tprotected Object createJobInstance(@NonNull TriggerFiredBundle bundle) throws Exception {\n\t\t\ttry {\n\t\t\t\t// 调用父类的方法，把Job注入到spring中\n\t\t\t\tObject jobInstance = super.createJobInstance(bundle);\n\t\t\t\tcapableBeanFactory.autowireBean(jobInstance);\n\t\t\t\tlog.debug(\"Job instance created and autowired: {}\", jobInstance.getClass().getName());\n\t\t\t\treturn jobInstance;\n\t\t\t} catch (Exception e) {\n\t\t\t\tlog.error(\"Error creating job instance for bundle: {}\", bundle, e);\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzJob.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\n@Getter\n@Setter\n@Entity\n@Table(name = \"sys_quartz_job\")\npublic class QuartzJob extends BaseEntity implements Serializable {\n\n    public static final String JOB_KEY = \"JOB_KEY\";\n\n    @Id\n    @Column(name = \"job_id\")\n    @NotNull(groups = {Update.class})\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @Transient\n    @ApiModelProperty(value = \"用于子任务唯一标识\", hidden = true)\n    private String uuid;\n\n    @ApiModelProperty(value = \"定时器名称\")\n    private String jobName;\n\n    @NotBlank\n    @ApiModelProperty(value = \"Bean名称\")\n    private String beanName;\n\n    @NotBlank\n    @ApiModelProperty(value = \"方法名称\")\n    private String methodName;\n\n    @ApiModelProperty(value = \"参数\")\n    private String params;\n\n    @NotBlank\n    @ApiModelProperty(value = \"cron表达式\")\n    private String cronExpression;\n\n    @ApiModelProperty(value = \"状态，暂时或启动\")\n    private Boolean isPause = false;\n\n    @ApiModelProperty(value = \"负责人\")\n    private String personInCharge;\n\n    @ApiModelProperty(value = \"报警邮箱\")\n    private String email;\n\n    @ApiModelProperty(value = \"子任务\")\n    private String subTask;\n\n    @ApiModelProperty(value = \"失败后暂停\")\n    private Boolean pauseAfterFailure;\n\n    @NotBlank\n    @ApiModelProperty(value = \"备注\")\n    private String description;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzLog.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport org.hibernate.annotations.CreationTimestamp;\nimport javax.persistence.*;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\n@Entity\n@Data\n@Table(name = \"sys_quartz_log\")\npublic class QuartzLog implements Serializable {\n\n    @Id\n    @Column(name = \"log_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @ApiModelProperty(value = \"任务名称\", hidden = true)\n    private String jobName;\n\n    @ApiModelProperty(value = \"bean名称\", hidden = true)\n    private String beanName;\n\n    @ApiModelProperty(value = \"方法名称\", hidden = true)\n    private String methodName;\n\n    @ApiModelProperty(value = \"参数\", hidden = true)\n    private String params;\n\n    @ApiModelProperty(value = \"cron表达式\", hidden = true)\n    private String cronExpression;\n\n    @ApiModelProperty(value = \"状态\", hidden = true)\n    private Boolean isSuccess;\n\n    @ApiModelProperty(value = \"异常详情\", hidden = true)\n    private String exceptionDetail;\n\n    @ApiModelProperty(value = \"执行耗时\", hidden = true)\n    private Long time;\n\n    @CreationTimestamp\n    @ApiModelProperty(value = \"创建时间\", hidden = true)\n    private Timestamp createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzJobRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.repository;\n\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\npublic interface QuartzJobRepository extends JpaRepository<QuartzJob,Long>, JpaSpecificationExecutor<QuartzJob> {\n\n    /**\n     * 查询启用的任务\n     * @return List\n     */\n    List<QuartzJob> findByIsPauseIsFalse();\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzLogRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.repository;\n\nimport me.zhengjie.modules.quartz.domain.QuartzLog;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\npublic interface QuartzLogRepository extends JpaRepository<QuartzLog,Long>, JpaSpecificationExecutor<QuartzLog> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/rest/QuartzJobController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport me.zhengjie.modules.quartz.domain.QuartzLog;\nimport me.zhengjie.modules.quartz.service.QuartzJobService;\nimport me.zhengjie.modules.quartz.service.dto.JobQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.SpringBeanHolder;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\n@Slf4j\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/jobs\")\n@Api(tags = \"系统:定时任务管理\")\npublic class QuartzJobController {\n\n    private static final String ENTITY_NAME = \"quartzJob\";\n    private final QuartzJobService quartzJobService;\n\n    @ApiOperation(\"查询定时任务\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('timing:list')\")\n    public ResponseEntity<PageResult<QuartzJob>> queryQuartzJob(JobQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(quartzJobService.queryAll(criteria,pageable), HttpStatus.OK);\n    }\n\n    @ApiOperation(\"导出任务数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('timing:list')\")\n    public void exportQuartzJob(HttpServletResponse response, JobQueryCriteria criteria) throws IOException {\n        quartzJobService.download(quartzJobService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(\"导出日志数据\")\n    @GetMapping(value = \"/logs/download\")\n    @PreAuthorize(\"@el.check('timing:list')\")\n    public void exportQuartzJobLog(HttpServletResponse response, JobQueryCriteria criteria) throws IOException {\n        quartzJobService.downloadLog(quartzJobService.queryAllLog(criteria), response);\n    }\n\n    @ApiOperation(\"查询任务执行日志\")\n    @GetMapping(value = \"/logs\")\n    @PreAuthorize(\"@el.check('timing:list')\")\n    public ResponseEntity<PageResult<QuartzLog>> queryQuartzJobLog(JobQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(quartzJobService.queryAllLog(criteria,pageable), HttpStatus.OK);\n    }\n\n    @Log(\"新增定时任务\")\n    @ApiOperation(\"新增定时任务\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('timing:add')\")\n    public ResponseEntity<Object> createQuartzJob(@Validated @RequestBody QuartzJob resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        // 验证Bean是不是合法的，合法的定时任务 Bean 需要用 @Service 定义\n        checkBean(resources.getBeanName());\n        quartzJobService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改定时任务\")\n    @ApiOperation(\"修改定时任务\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('timing:edit')\")\n    public ResponseEntity<Object> updateQuartzJob(@Validated(QuartzJob.Update.class) @RequestBody QuartzJob resources){\n        // 验证Bean是不是合法的，合法的定时任务 Bean 需要用 @Service 定义\n        checkBean(resources.getBeanName());\n        quartzJobService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"更改定时任务状态\")\n    @ApiOperation(\"更改定时任务状态\")\n    @PutMapping(value = \"/{id}\")\n    @PreAuthorize(\"@el.check('timing:edit')\")\n    public ResponseEntity<Object> updateQuartzJobStatus(@PathVariable Long id){\n        quartzJobService.updateIsPause(quartzJobService.findById(id));\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"执行定时任务\")\n    @ApiOperation(\"执行定时任务\")\n    @PutMapping(value = \"/exec/{id}\")\n    @PreAuthorize(\"@el.check('timing:edit')\")\n    public ResponseEntity<Object> executionQuartzJob(@PathVariable Long id){\n        quartzJobService.execution(quartzJobService.findById(id));\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除定时任务\")\n    @ApiOperation(\"删除定时任务\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('timing:del')\")\n    public ResponseEntity<Object> deleteQuartzJob(@RequestBody Set<Long> ids){\n        quartzJobService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    private void checkBean(String beanName){\n        // 避免调用攻击者可以从SpringContextHolder获得控制jdbcTemplate类\n        // 并使用getDeclaredMethod调用jdbcTemplate的queryForMap函数，执行任意sql命令。\n        if(!SpringBeanHolder.getAllServiceBeanName().contains(beanName)){\n            throw new BadRequestException(\"非法的 Bean，请重新输入！\");\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/QuartzJobService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.service;\n\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport me.zhengjie.modules.quartz.domain.QuartzLog;\nimport me.zhengjie.modules.quartz.service.dto.JobQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\npublic interface QuartzJobService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<QuartzJob> queryAll(JobQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部\n     * @param criteria 条件\n     * @return /\n     */\n    List<QuartzJob> queryAll(JobQueryCriteria criteria);\n\n    /**\n     * 分页查询日志\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<QuartzLog> queryAllLog(JobQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部\n     * @param criteria 条件\n     * @return /\n     */\n    List<QuartzLog> queryAllLog(JobQueryCriteria criteria);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(QuartzJob resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(QuartzJob resources);\n\n    /**\n     * 删除任务\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 根据ID查询\n     * @param id ID\n     * @return /\n     */\n    QuartzJob findById(Long id);\n\n    /**\n     * 更改定时任务状态\n     * @param quartzJob /\n     */\n    void updateIsPause(QuartzJob quartzJob);\n\n    /**\n     * 立即执行定时任务\n     * @param quartzJob /\n     */\n    void execution(QuartzJob quartzJob);\n\n    /**\n     * 导出定时任务\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<QuartzJob> queryAll, HttpServletResponse response) throws IOException;\n\n    /**\n     * 导出定时任务日志\n     * @param queryAllLog 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void downloadLog(List<QuartzLog> queryAllLog, HttpServletResponse response) throws IOException;\n\n    /**\n     * 执行子任务\n     * @param tasks /\n     * @throws InterruptedException /\n     */\n    void executionSubJob(String[] tasks) throws InterruptedException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/dto/JobQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2019-6-4 10:33:02\n */\n@Data\npublic class JobQueryCriteria {\n\n    @ApiModelProperty(value = \"任务名称\")\n    @Query(type = Query.Type.INNER_LIKE)\n    private String jobName;\n\n    @Query\n    @ApiModelProperty(value = \"是否成功\")\n    private Boolean isSuccess;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/impl/QuartzJobServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.service.impl;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport me.zhengjie.modules.quartz.domain.QuartzLog;\nimport me.zhengjie.modules.quartz.repository.QuartzJobRepository;\nimport me.zhengjie.modules.quartz.repository.QuartzLogRepository;\nimport me.zhengjie.modules.quartz.service.QuartzJobService;\nimport me.zhengjie.modules.quartz.service.dto.JobQueryCriteria;\nimport me.zhengjie.modules.quartz.utils.QuartzManage;\nimport me.zhengjie.utils.*;\nimport org.quartz.CronExpression;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\n@RequiredArgsConstructor\n@Service(value = \"quartzJobService\")\npublic class QuartzJobServiceImpl implements QuartzJobService {\n\n    private final QuartzJobRepository quartzJobRepository;\n    private final QuartzLogRepository quartzLogRepository;\n    private final QuartzManage quartzManage;\n    private final RedisUtils redisUtils;\n\n    @Override\n    public PageResult<QuartzJob> queryAll(JobQueryCriteria criteria, Pageable pageable){\n        return PageUtil.toPage(quartzJobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable));\n    }\n\n    @Override\n    public PageResult<QuartzLog> queryAllLog(JobQueryCriteria criteria, Pageable pageable){\n        return PageUtil.toPage(quartzLogRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable));\n    }\n\n    @Override\n    public List<QuartzJob> queryAll(JobQueryCriteria criteria) {\n        return quartzJobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));\n    }\n\n    @Override\n    public List<QuartzLog> queryAllLog(JobQueryCriteria criteria) {\n        return quartzLogRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));\n    }\n\n    @Override\n    public QuartzJob findById(Long id) {\n        QuartzJob quartzJob = quartzJobRepository.findById(id).orElseGet(QuartzJob::new);\n        ValidationUtil.isNull(quartzJob.getId(),\"QuartzJob\",\"id\",id);\n        return quartzJob;\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(QuartzJob resources) {\n        if (!CronExpression.isValidExpression(resources.getCronExpression())){\n            throw new BadRequestException(\"cron表达式格式错误\");\n        }\n        resources = quartzJobRepository.save(resources);\n        quartzManage.addJob(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(QuartzJob resources) {\n        if (!CronExpression.isValidExpression(resources.getCronExpression())){\n            throw new BadRequestException(\"cron表达式格式错误\");\n        }\n        if(StringUtils.isNotBlank(resources.getSubTask())){\n            List<String> tasks = Arrays.asList(resources.getSubTask().split(\"[,，]\"));\n            if (tasks.contains(resources.getId().toString())) {\n                throw new BadRequestException(\"子任务中不能添加当前任务ID\");\n            }\n        }\n        resources = quartzJobRepository.save(resources);\n        quartzManage.updateJobCron(resources);\n    }\n\n    @Override\n    public void updateIsPause(QuartzJob quartzJob) {\n        // 置换暂停状态\n        if (quartzJob.getIsPause()) {\n            quartzManage.resumeJob(quartzJob);\n            quartzJob.setIsPause(false);\n        } else {\n            quartzManage.pauseJob(quartzJob);\n            quartzJob.setIsPause(true);\n        }\n        quartzJobRepository.save(quartzJob);\n    }\n\n    @Override\n    public void execution(QuartzJob quartzJob) {\n        quartzManage.runJobNow(quartzJob);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        for (Long id : ids) {\n            QuartzJob quartzJob = findById(id);\n            quartzManage.deleteJob(quartzJob);\n            quartzJobRepository.delete(quartzJob);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void executionSubJob(String[] tasks) throws InterruptedException {\n        for (String id : tasks) {\n            if (StrUtil.isBlank(id)) {\n                // 如果是手动清除子任务id，会出现id为空字符串的问题\n                continue;\n            }\n            QuartzJob quartzJob = findById(Long.parseLong(id));\n            // 执行任务\n            String uuid = IdUtil.simpleUUID();\n            quartzJob.setUuid(uuid);\n            // 执行任务\n            execution(quartzJob);\n            // 获取执行状态，如果执行失败则停止后面的子任务执行\n            Boolean result = redisUtils.get(uuid, Boolean.class);\n            while (result == null) {\n                // 休眠5秒，再次获取子任务执行情况\n                Thread.sleep(5000);\n                result = redisUtils.get(uuid, Boolean.class);\n            }\n            if(!result){\n                redisUtils.del(uuid);\n                break;\n            }\n        }\n    }\n\n    @Override\n    public void download(List<QuartzJob> quartzJobs, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (QuartzJob quartzJob : quartzJobs) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"任务名称\", quartzJob.getJobName());\n            map.put(\"Bean名称\", quartzJob.getBeanName());\n            map.put(\"执行方法\", quartzJob.getMethodName());\n            map.put(\"参数\", quartzJob.getParams());\n            map.put(\"表达式\", quartzJob.getCronExpression());\n            map.put(\"状态\", quartzJob.getIsPause() ? \"暂停中\" : \"运行中\");\n            map.put(\"描述\", quartzJob.getDescription());\n            map.put(\"创建日期\", quartzJob.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    @Override\n    public void downloadLog(List<QuartzLog> queryAllLog, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (QuartzLog quartzLog : queryAllLog) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"任务名称\", quartzLog.getJobName());\n            map.put(\"Bean名称\", quartzLog.getBeanName());\n            map.put(\"执行方法\", quartzLog.getMethodName());\n            map.put(\"参数\", quartzLog.getParams());\n            map.put(\"表达式\", quartzLog.getCronExpression());\n            map.put(\"异常详情\", quartzLog.getExceptionDetail());\n            map.put(\"耗时/毫秒\", quartzLog.getTime());\n            map.put(\"状态\", quartzLog.getIsSuccess() ? \"成功\" : \"失败\");\n            map.put(\"创建日期\", quartzLog.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/TestTask.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.task;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\n/**\n * 测试用\n * @author Zheng Jie\n * @date 2019-01-08\n */\n@Slf4j\n@Service\npublic class TestTask {\n\n    public void run(){\n        log.info(\"run 执行成功\");\n    }\n\n    public void run1(String str){\n        log.info(\"run1 执行成功，参数为： {}\", str);\n    }\n\n    public void run2(){\n        log.info(\"run2 执行成功\");\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/ExecutionJob.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.utils;\n\nimport cn.hutool.extra.template.Template;\nimport cn.hutool.extra.template.TemplateConfig;\nimport cn.hutool.extra.template.TemplateEngine;\nimport cn.hutool.extra.template.TemplateUtil;\nimport me.zhengjie.domain.vo.EmailVo;\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport me.zhengjie.modules.quartz.domain.QuartzLog;\nimport me.zhengjie.modules.quartz.repository.QuartzLogRepository;\nimport me.zhengjie.modules.quartz.service.QuartzJobService;\nimport me.zhengjie.service.EmailService;\nimport me.zhengjie.utils.RedisUtils;\nimport me.zhengjie.utils.SpringBeanHolder;\nimport me.zhengjie.utils.StringUtils;\nimport me.zhengjie.utils.ThrowableUtil;\nimport org.quartz.JobExecutionContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\nimport org.springframework.scheduling.quartz.QuartzJobBean;\nimport java.util.*;\nimport java.util.concurrent.*;\n\n/**\n * 参考人人开源，<a href=\"https://gitee.com/renrenio/renren-security\">...</a>\n * @author /\n * @date 2019-01-07\n */\npublic class ExecutionJob extends QuartzJobBean {\n\n    private final Logger logger = LoggerFactory.getLogger(this.getClass());\n\n\n    // 此处仅供参考，可根据任务执行情况自定义线程池参数\n    private final ThreadPoolTaskExecutor executor = SpringBeanHolder.getBean(\"taskAsync\");\n\n\n    @Override\n    public void executeInternal(JobExecutionContext context) {\n        // 获取任务\n        QuartzJob quartzJob = (QuartzJob) context.getMergedJobDataMap().get(QuartzJob.JOB_KEY);\n        // 获取spring bean\n        QuartzLogRepository quartzLogRepository = SpringBeanHolder.getBean(QuartzLogRepository.class);\n        QuartzJobService quartzJobService = SpringBeanHolder.getBean(QuartzJobService.class);\n        RedisUtils redisUtils = SpringBeanHolder.getBean(RedisUtils.class);\n\n        String uuid = quartzJob.getUuid();\n\n        QuartzLog log = new QuartzLog();\n        log.setJobName(quartzJob.getJobName());\n        log.setBeanName(quartzJob.getBeanName());\n        log.setMethodName(quartzJob.getMethodName());\n        log.setParams(quartzJob.getParams());\n        long startTime = System.currentTimeMillis();\n        log.setCronExpression(quartzJob.getCronExpression());\n        try {\n            // 执行任务\n            QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams());\n            Future<?> future = executor.submit(task);\n            future.get();\n            long times = System.currentTimeMillis() - startTime;\n            log.setTime(times);\n            if(StringUtils.isNotBlank(uuid)) {\n                redisUtils.set(uuid, true);\n            }\n            // 任务状态\n            log.setIsSuccess(true);\n            logger.info(\"任务执行成功，任务名称：{}, 执行时间：{}毫秒\", quartzJob.getJobName(), times);\n            // 判断是否存在子任务\n            if(StringUtils.isNotBlank(quartzJob.getSubTask())){\n                String[] tasks = quartzJob.getSubTask().split(\"[,，]\");\n                // 执行子任务\n                quartzJobService.executionSubJob(tasks);\n            }\n        } catch (Exception e) {\n            if(StringUtils.isNotBlank(uuid)) {\n                redisUtils.set(uuid, false);\n            }\n            logger.error(\"任务执行失败，任务名称：{}\", quartzJob.getJobName());\n            long times = System.currentTimeMillis() - startTime;\n            log.setTime(times);\n            // 任务状态 0：成功 1：失败\n            log.setIsSuccess(false);\n            log.setExceptionDetail(ThrowableUtil.getStackTrace(e));\n            // 任务如果失败了则暂停\n            if(quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure()){\n                //更新状态\n                quartzJob.setIsPause(false);\n                quartzJobService.updateIsPause(quartzJob);\n            }\n            if(quartzJob.getEmail() != null){\n                EmailService emailService = SpringBeanHolder.getBean(EmailService.class);\n                // 邮箱报警\n                if(StringUtils.isNoneBlank(quartzJob.getEmail())){\n                    EmailVo emailVo = taskAlarm(quartzJob, ThrowableUtil.getStackTrace(e));\n                    emailService.send(emailVo, emailService.find());\n                }\n            }\n        } finally {\n            quartzLogRepository.save(log);\n        }\n    }\n\n    private EmailVo taskAlarm(QuartzJob quartzJob, String msg) {\n        EmailVo emailVo = new EmailVo();\n        emailVo.setSubject(\"定时任务【\"+ quartzJob.getJobName() +\"】执行失败，请尽快处理！\");\n        Map<String, Object> data = new HashMap<>(16);\n        data.put(\"task\", quartzJob);\n        data.put(\"msg\", msg);\n        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(\"template\", TemplateConfig.ResourceMode.CLASSPATH));\n        Template template = engine.getTemplate(\"taskAlarm.ftl\");\n        emailVo.setContent(template.render(data));\n        List<String> emails = Arrays.asList(quartzJob.getEmail().split(\"[,，]\"));\n        emailVo.setTos(emails);\n        return emailVo;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzManage.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.utils;\n\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.quartz.domain.QuartzJob;\nimport org.quartz.*;\nimport org.quartz.impl.triggers.CronTriggerImpl;\nimport org.springframework.stereotype.Component;\nimport javax.annotation.Resource;\nimport java.util.Date;\nimport static org.quartz.TriggerBuilder.newTrigger;\n\n/**\n * @author Zheng Jie\n * @date 2019-01-07\n */\n@Slf4j\n@Component\npublic class QuartzManage {\n\n    private static final String JOB_NAME = \"TASK_\";\n\n    @Resource\n    private Scheduler scheduler;\n\n    public void addJob(QuartzJob quartzJob){\n        try {\n            // 构建job信息\n            JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).\n                    withIdentity(JOB_NAME + quartzJob.getId()).build();\n\n            //通过触发器名和cron 表达式创建 Trigger\n            Trigger cronTrigger = newTrigger()\n                    .withIdentity(JOB_NAME + quartzJob.getId())\n                    .startNow()\n                    .withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))\n                    .build();\n\n            cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);\n\n            //重置启动时间\n            ((CronTriggerImpl)cronTrigger).setStartTime(new Date());\n\n            //执行定时任务，如果是持久化的，这里会报错，捕获输出\n            try {\n                scheduler.scheduleJob(jobDetail,cronTrigger);\n            } catch (ObjectAlreadyExistsException e) {\n                log.warn(\"定时任务已存在，跳过加载\");\n            }\n\n            // 暂停任务\n            if (quartzJob.getIsPause()) {\n                pauseJob(quartzJob);\n            }\n        } catch (Exception e){\n            log.error(\"创建定时任务失败\", e);\n            throw new BadRequestException(\"创建定时任务失败\");\n        }\n    }\n\n    /**\n     * 更新job cron表达式\n     * @param quartzJob /\n     */\n    public void updateJobCron(QuartzJob quartzJob){\n        try {\n            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());\n            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);\n            // 如果不存在则创建一个定时任务\n            if(trigger == null){\n                addJob(quartzJob);\n                trigger = (CronTrigger) scheduler.getTrigger(triggerKey);\n            }\n            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());\n            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();\n            //重置启动时间\n            ((CronTriggerImpl)trigger).setStartTime(new Date());\n            trigger.getJobDataMap().put(QuartzJob.JOB_KEY,quartzJob);\n\n            scheduler.rescheduleJob(triggerKey, trigger);\n            // 暂停任务\n            if (quartzJob.getIsPause()) {\n                pauseJob(quartzJob);\n            }\n        } catch (Exception e){\n            log.error(\"更新定时任务失败\", e);\n            throw new BadRequestException(\"更新定时任务失败\");\n        }\n\n    }\n\n    /**\n     * 删除一个job\n     * @param quartzJob /\n     */\n    public void deleteJob(QuartzJob quartzJob){\n        try {\n            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());\n            scheduler.pauseJob(jobKey);\n            scheduler.deleteJob(jobKey);\n        } catch (Exception e){\n            log.error(\"删除定时任务失败\", e);\n            throw new BadRequestException(\"删除定时任务失败\");\n        }\n    }\n\n    /**\n     * 恢复一个job\n     * @param quartzJob /\n     */\n    public void resumeJob(QuartzJob quartzJob){\n        try {\n            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());\n            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);\n            // 如果不存在则创建一个定时任务\n            if(trigger == null) {\n                addJob(quartzJob);\n            }\n            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());\n            scheduler.resumeJob(jobKey);\n        } catch (Exception e){\n            log.error(\"恢复定时任务失败\", e);\n            throw new BadRequestException(\"恢复定时任务失败\");\n        }\n    }\n\n    /**\n     * 立即执行job\n     * @param quartzJob /\n     */\n    public void runJobNow(QuartzJob quartzJob){\n        try {\n            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());\n            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);\n            // 如果不存在则创建一个定时任务\n            if(trigger == null) {\n                addJob(quartzJob);\n            }\n            JobDataMap dataMap = new JobDataMap();\n            dataMap.put(QuartzJob.JOB_KEY, quartzJob);\n            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());\n            scheduler.triggerJob(jobKey,dataMap);\n        } catch (Exception e){\n            log.error(\"定时任务执行失败\", e);\n            throw new BadRequestException(\"定时任务执行失败\");\n        }\n    }\n\n    /**\n     * 暂停一个job\n     * @param quartzJob /\n     */\n    public void pauseJob(QuartzJob quartzJob){\n        try {\n            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());\n            scheduler.pauseJob(jobKey);\n        } catch (Exception e){\n            log.error(\"定时任务暂停失败\", e);\n            throw new BadRequestException(\"定时任务暂停失败\");\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzRunnable.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.quartz.utils;\n\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.utils.SpringBeanHolder;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.util.ReflectionUtils;\nimport java.lang.reflect.Method;\nimport java.util.concurrent.Callable;\n\n/**\n * 执行定时任务\n * @author /\n */\n@Slf4j\npublic class QuartzRunnable implements Callable<Object> {\n\n\tprivate final Object target;\n\tprivate final Method method;\n\tprivate final String params;\n\n\tQuartzRunnable(String beanName, String methodName, String params)\n\t\t\tthrows NoSuchMethodException, SecurityException {\n\t\tthis.target = SpringBeanHolder.getBean(beanName);\n\t\tthis.params = params;\n\t\tif (StringUtils.isNotBlank(params)) {\n\t\t\tthis.method = target.getClass().getDeclaredMethod(methodName, String.class);\n\t\t} else {\n\t\t\tthis.method = target.getClass().getDeclaredMethod(methodName);\n\t\t}\n\t}\n\n\t@Override\n\t@SuppressWarnings({\"unchecked\",\"all\"})\n\tpublic Object call() throws Exception {\n\t\tReflectionUtils.makeAccessible(method);\n\t\tif (StringUtils.isNotBlank(params)) {\n\t\t\tmethod.invoke(target, params);\n\t\t} else {\n\t\t\tmethod.invoke(target);\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/config/CaptchaConfig.java",
    "content": "/*\n * Copyright 2019-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version loginCode.length.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-loginCode.length.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 me.zhengjie.modules.security.config;\n\nimport com.wf.captcha.*;\nimport com.wf.captcha.base.Captcha;\nimport lombok.Data;\nimport lombok.Getter;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.security.config.enums.LoginCodeEnum;\nimport me.zhengjie.utils.StringUtils;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.awt.*;\n\n/**\n * 登录验证码配置信息\n * @author liaojinlong\n * @date 2025-01-13\n */\n@Data\n@Configuration\n@ConfigurationProperties(prefix = \"login.code\")\npublic class CaptchaConfig {\n\n    /**\n     * 验证码配置\n     */\n    @Getter\n    private LoginCodeEnum codeType;\n\n    /**\n     * 验证码有效期 分钟\n     */\n    private Long expiration = 5L;\n\n    /**\n     * 验证码内容长度\n     */\n    private int length = 4;\n\n    /**\n     * 验证码宽度\n     */\n    private int width = 111;\n\n    /**\n     * 验证码高度\n     */\n    private int height = 36;\n\n    /**\n     * 验证码字体\n     */\n    private String fontName;\n\n    /**\n     * 字体大小\n     */\n    private int fontSize = 25;\n\n    /**\n     * 依据配置信息生产验证码\n     * @return /\n     */\n    public Captcha getCaptcha() {\n        Captcha captcha;\n        switch (codeType) {\n            case ARITHMETIC:\n                // 算术类型 https://gitee.com/whvse/EasyCaptcha\n                captcha = new FixedArithmeticCaptcha(width, height);\n                // 几位数运算，默认是两位\n                captcha.setLen(length);\n                break;\n            case CHINESE:\n                captcha = new ChineseCaptcha(width, height);\n                captcha.setLen(length);\n                break;\n            case CHINESE_GIF:\n                captcha = new ChineseGifCaptcha(width, height);\n                captcha.setLen(length);\n                break;\n            case GIF:\n                captcha = new GifCaptcha(width, height);\n                captcha.setLen(length);\n                break;\n            case SPEC:\n                captcha = new SpecCaptcha(width, height);\n                captcha.setLen(length);\n                break;\n            default:\n                throw new BadRequestException(\"验证码配置信息错误！正确配置查看 LoginCodeEnum \");\n        }\n        if(StringUtils.isNotBlank(fontName)){\n            captcha.setFont(new Font(fontName, Font.PLAIN, fontSize));\n        }\n        return captcha;\n    }\n\n    static class FixedArithmeticCaptcha extends ArithmeticCaptcha {\n        public FixedArithmeticCaptcha(int width, int height) {\n            super(width, height);\n        }\n\n        @Override\n        protected char[] alphas() {\n            // 生成随机数字和运算符\n            int n1 = num(1, 10), n2 = num(1, 10);\n            int opt = num(3);\n\n            // 计算结果\n            int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];\n            // 转换为字符运算符\n            char optChar = \"+-x\".charAt(opt);\n\n            this.setArithmeticString(String.format(\"%s%c%s=?\", n1, optChar, n2));\n            this.chars = String.valueOf(res);\n\n            return chars.toCharArray();\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/config/LoginProperties.java",
    "content": "/*\n * Copyright 2019-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version loginCode.length.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-loginCode.length.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 me.zhengjie.modules.security.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 配置文件读取\n *\n * @author liaojinlong\n * @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6\n */\n@Data\n@Configuration\n@ConfigurationProperties(prefix = \"login\")\npublic class LoginProperties {\n\n    /**\n     * 账号单用户 登录\n     */\n    private boolean singleLogin = false;\n\n    public static final String cacheKey = \"user-login-cache:\";\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * Jwt参数配置\n *\n * @author Zheng Jie\n * @date 2019年11月28日\n */\n@Data\n@Configuration\n@ConfigurationProperties(prefix = \"jwt\")\npublic class SecurityProperties {\n\n    /**\n     * Request Headers ： Authorization\n     */\n    private String header;\n\n    /**\n     * 令牌前缀，最后留个空格 Bearer\n     */\n    private String tokenStartWith;\n\n    /**\n     * 必须使用最少88位的Base64对该令牌进行编码\n     */\n    private String base64Secret;\n\n    /**\n     * 令牌过期时间 此处单位/毫秒\n     */\n    private Long tokenValidityInSeconds;\n\n    /**\n     * 在线用户 key，根据 key 查询 redis 中在线用户的数据\n     */\n    private String onlineKey;\n\n    /**\n     * 验证码 key\n     */\n    private String codeKey;\n\n    /**\n     * token 续期检查\n     */\n    private Long detect;\n\n    /**\n     * 续期时间\n     */\n    private Long renew;\n\n    public String getTokenStartWith() {\n        return tokenStartWith + \" \";\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/config/SpringSecurityConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.config;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.security.security.*;\nimport me.zhengjie.modules.security.service.OnlineUserService;\nimport me.zhengjie.utils.AnonTagUtils;\nimport me.zhengjie.utils.enums.RequestMethodEnum;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.core.GrantedAuthorityDefaults;\nimport org.springframework.security.config.http.SessionCreationPolicy;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.web.filter.CorsFilter;\nimport java.util.*;\n\n/**\n * @author Zheng Jie\n */\n@Configuration\n@RequiredArgsConstructor\n@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)\npublic class SpringSecurityConfig {\n\n    private final TokenProvider tokenProvider;\n    private final CorsFilter corsFilter;\n    private final JwtAuthenticationEntryPoint authenticationErrorHandler;\n    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;\n    private final ApplicationContext applicationContext;\n    private final SecurityProperties properties;\n    private final OnlineUserService onlineUserService;\n\n    @Bean\n    GrantedAuthorityDefaults grantedAuthorityDefaults() {\n        // 去除 ROLE_ 前缀\n        return new GrantedAuthorityDefaults(\"\");\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        // 密码加密方式\n        return new BCryptPasswordEncoder();\n    }\n\n    @Bean\n    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {\n        // 获取匿名标记\n        Map<String, Set<String>> anonymousUrls = AnonTagUtils.getAnonymousUrl(applicationContext);\n        return httpSecurity\n                // 禁用 CSRF\n                .csrf().disable()\n                .addFilter(corsFilter)\n                // 授权异常\n                .exceptionHandling()\n                .authenticationEntryPoint(authenticationErrorHandler)\n                .accessDeniedHandler(jwtAccessDeniedHandler)\n                // 防止iframe 造成跨域\n                .and()\n                .headers()\n                .frameOptions()\n                .disable()\n                // 不创建会话\n                .and()\n                .sessionManagement()\n                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)\n                .and()\n                .authorizeRequests()\n                // 静态资源等等\n                .antMatchers(\n                        HttpMethod.GET,\n                        \"/*.html\",\n                        \"/**/*.html\",\n                        \"/**/*.css\",\n                        \"/**/*.js\",\n                        \"/webSocket/**\"\n                ).permitAll()\n                // swagger 文档\n                .antMatchers(\"/swagger-ui.html\").permitAll()\n                .antMatchers(\"/swagger-resources/**\").permitAll()\n                .antMatchers(\"/webjars/**\").permitAll()\n                .antMatchers(\"/*/api-docs\").permitAll()\n                // 文件\n                .antMatchers(\"/avatar/**\").permitAll()\n                .antMatchers(\"/file/**\").permitAll()\n                // 阿里巴巴 druid\n                .antMatchers(\"/druid/**\").permitAll()\n                // 放行OPTIONS请求\n                .antMatchers(HttpMethod.OPTIONS, \"/**\").permitAll()\n                // 自定义匿名访问所有url放行：允许匿名和带Token访问，细腻化到每个 Request 类型\n                // GET\n                .antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()\n                // POST\n                .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()\n                // PUT\n                .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()\n                // PATCH\n                .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()\n                // DELETE\n                .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()\n                // 所有类型的接口都放行\n                .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()\n                // 所有请求都需要认证\n                .anyRequest().authenticated()\n                .and().apply(securityConfigurerAdapter())\n                .and().build();\n    }\n\n    private TokenConfigurer securityConfigurerAdapter() {\n        return new TokenConfigurer(tokenProvider, properties, onlineUserService);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/config/enums/LoginCodeEnum.java",
    "content": "/*\n * Copyright 2019-2020 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 *      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 me.zhengjie.modules.security.config.enums;\n\n/**\n * 验证码配置枚举\n *\n * @author liaojinlong\n * @date 2020/6/10 17:40\n */\n\npublic enum LoginCodeEnum {\n    /**\n     * 算数\n     */\n    ARITHMETIC,\n    /**\n     * 中文\n     */\n    CHINESE,\n    /**\n     * 中文闪图\n     */\n    CHINESE_GIF,\n    /**\n     * 闪图\n     */\n    GIF,\n    /**\n     * 静态\n     */\n    SPEC\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.rest;\n\nimport cn.hutool.core.util.IdUtil;\nimport com.wf.captcha.base.Captcha;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.annotation.rest.AnonymousDeleteMapping;\nimport me.zhengjie.annotation.rest.AnonymousGetMapping;\nimport me.zhengjie.annotation.rest.AnonymousPostMapping;\nimport me.zhengjie.config.properties.RsaProperties;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.security.config.CaptchaConfig;\nimport me.zhengjie.modules.security.config.enums.LoginCodeEnum;\nimport me.zhengjie.modules.security.config.LoginProperties;\nimport me.zhengjie.modules.security.config.SecurityProperties;\nimport me.zhengjie.modules.security.security.TokenProvider;\nimport me.zhengjie.modules.security.service.UserDetailsServiceImpl;\nimport me.zhengjie.modules.security.service.dto.AuthUserDto;\nimport me.zhengjie.modules.security.service.dto.JwtUserDto;\nimport me.zhengjie.modules.security.service.OnlineUserService;\nimport me.zhengjie.utils.RsaUtils;\nimport me.zhengjie.utils.RedisUtils;\nimport me.zhengjie.utils.SecurityUtils;\nimport me.zhengjie.utils.StringUtils;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n * 授权、根据token获取用户详细信息\n */\n@Slf4j\n@RestController\n@RequestMapping(\"/auth\")\n@RequiredArgsConstructor\n@Api(tags = \"系统：系统授权接口\")\npublic class AuthController {\n    private final SecurityProperties properties;\n    private final RedisUtils redisUtils;\n    private final OnlineUserService onlineUserService;\n    private final TokenProvider tokenProvider;\n    private final LoginProperties loginProperties;\n    private final CaptchaConfig captchaConfig;\n    private final PasswordEncoder passwordEncoder;\n    private final UserDetailsServiceImpl userDetailsService;\n\n    @Log(\"用户登录\")\n    @ApiOperation(\"登录授权\")\n    @AnonymousPostMapping(value = \"/login\")\n    public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {\n        // 密码解密\n        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());\n        // 查询验证码\n        String code = redisUtils.get(authUser.getUuid(), String.class);\n        // 清除验证码\n        redisUtils.del(authUser.getUuid());\n        if (StringUtils.isBlank(code)) {\n            throw new BadRequestException(\"验证码不存在或已过期\");\n        }\n        if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {\n            throw new BadRequestException(\"验证码错误\");\n        }\n        // 获取用户信息\n        JwtUserDto jwtUser = userDetailsService.loadUserByUsername(authUser.getUsername());\n        // 验证用户密码\n        if (!passwordEncoder.matches(password, jwtUser.getPassword())) {\n            throw new BadRequestException(\"登录密码错误\");\n        }\n        Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());\n        SecurityContextHolder.getContext().setAuthentication(authentication);\n        // 生成令牌\n        String token = tokenProvider.createToken(jwtUser);\n        // 返回 token 与 用户信息\n        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{\n            put(\"token\", properties.getTokenStartWith() + token);\n            put(\"user\", jwtUser);\n        }};\n        if (loginProperties.isSingleLogin()) {\n            // 踢掉之前已经登录的token\n            onlineUserService.kickOutForUsername(authUser.getUsername());\n        }\n        // 保存在线信息\n        onlineUserService.save(jwtUser, token, request);\n        // 返回登录信息\n        return ResponseEntity.ok(authInfo);\n    }\n\n    @ApiOperation(\"获取用户信息\")\n    @GetMapping(value = \"/info\")\n    public ResponseEntity<UserDetails> getUserInfo() {\n        JwtUserDto jwtUser = (JwtUserDto) SecurityUtils.getCurrentUser();\n        return ResponseEntity.ok(jwtUser);\n    }\n\n    @ApiOperation(\"获取验证码\")\n    @AnonymousGetMapping(value = \"/code\")\n    public ResponseEntity<Object> getCode() {\n        // 获取运算的结果\n        Captcha captcha = captchaConfig.getCaptcha();\n        String uuid = properties.getCodeKey() + IdUtil.simpleUUID();\n        //当验证码类型为 arithmetic时且长度 >= 2 时，captcha.text()的结果有几率为浮点型\n        String captchaValue = captcha.text();\n        if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(\".\")) {\n            captchaValue = captchaValue.split(\"\\\\.\")[0];\n        }\n        // 保存\n        redisUtils.set(uuid, captchaValue, captchaConfig.getExpiration(), TimeUnit.MINUTES);\n        // 验证码信息\n        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{\n            put(\"img\", captcha.toBase64());\n            put(\"uuid\", uuid);\n        }};\n        return ResponseEntity.ok(imgResult);\n    }\n\n    @ApiOperation(\"退出登录\")\n    @AnonymousDeleteMapping(value = \"/logout\")\n    public ResponseEntity<Object> logout(HttpServletRequest request) {\n        onlineUserService.logout(tokenProvider.getToken(request));\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.security.service.OnlineUserService;\nimport me.zhengjie.modules.security.service.dto.OnlineUserDto;\nimport me.zhengjie.utils.EncryptUtils;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/auth/online\")\n@Api(tags = \"系统：在线用户管理\")\npublic class OnlineController {\n\n    private final OnlineUserService onlineUserService;\n\n    @ApiOperation(\"查询在线用户\")\n    @GetMapping\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<PageResult<OnlineUserDto>> queryOnlineUser(String username, Pageable pageable){\n        return new ResponseEntity<>(onlineUserService.getAll(username, pageable),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"导出数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check()\")\n    public void exportOnlineUser(HttpServletResponse response, String username) throws IOException {\n        onlineUserService.download(onlineUserService.getAll(username), response);\n    }\n\n    @ApiOperation(\"踢出用户\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check()\")\n    public ResponseEntity<Object> deleteOnlineUser(@RequestBody Set<String> keys) throws Exception {\n        for (String token : keys) {\n            // 解密Key\n            token = EncryptUtils.desDecrypt(token);\n            onlineUserService.logout(token);\n        }\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.security;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport me.zhengjie.exception.handler.ApiError;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.security.access.AccessDeniedException;\nimport org.springframework.security.web.access.AccessDeniedHandler;\nimport org.springframework.stereotype.Component;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @author Zheng Jie\n */\n@Component\npublic class JwtAccessDeniedHandler implements AccessDeniedHandler {\n\n   @Override\n   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {\n      //当用户在没有授权的情况下访问受保护的REST资源时，将调用此方法发送403 Forbidden响应\n      response.setStatus(HttpStatus.FORBIDDEN.value());\n      response.setContentType(\"application/json;charset=UTF-8\");\n      ObjectMapper objectMapper = new ObjectMapper();\n      String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.FORBIDDEN.value(), \"禁止访问，您没有权限访问此资源\"));\n      response.getWriter().write(jsonResponse);\n   }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.security;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.exception.handler.ApiError;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.web.AuthenticationEntryPoint;\nimport org.springframework.stereotype.Component;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @author Zheng Jie\n */\n@Slf4j\n@Component\npublic class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {\n\n    @Override\n    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {\n        // 当用户尝试访问安全的REST资源而不提供任何凭据时，将调用此方法发送401 响应\n        int code = HttpStatus.UNAUTHORIZED.value();\n        response.setStatus(code);\n        response.setContentType(\"application/json;charset=UTF-8\");\n        ObjectMapper objectMapper = new ObjectMapper();\n        String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.UNAUTHORIZED.value(), \"登录状态已过期，请重新登录\"));\n        response.getWriter().write(jsonResponse);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.security;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.security.config.SecurityProperties;\nimport me.zhengjie.modules.security.service.OnlineUserService;\nimport me.zhengjie.modules.security.service.UserCacheManager;\nimport org.springframework.security.config.annotation.SecurityConfigurerAdapter;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.DefaultSecurityFilterChain;\nimport org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;\n\n/**\n * @author /\n */\n@RequiredArgsConstructor\npublic class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {\n\n    private final TokenProvider tokenProvider;\n    private final SecurityProperties properties;\n    private final OnlineUserService onlineUserService;\n\n    @Override\n    public void configure(HttpSecurity http) {\n        TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService);\n        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.security;\n\nimport cn.hutool.core.util.StrUtil;\nimport me.zhengjie.modules.security.config.SecurityProperties;\nimport me.zhengjie.modules.security.service.dto.OnlineUserDto;\nimport me.zhengjie.modules.security.service.OnlineUserService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.filter.GenericFilterBean;\nimport javax.servlet.FilterChain;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.IOException;\n\n/**\n * @author /\n */\npublic class TokenFilter extends GenericFilterBean {\n    private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);\n\n\n    private final TokenProvider tokenProvider;\n    private final SecurityProperties properties;\n    private final OnlineUserService onlineUserService;\n\n    /**\n     * @param tokenProvider     Token\n     * @param properties        JWT\n     * @param onlineUserService 用户在线\n     */\n    public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService) {\n        this.properties = properties;\n        this.onlineUserService = onlineUserService;\n        this.tokenProvider = tokenProvider;\n    }\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)\n            throws IOException, ServletException {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;\n        String token = resolveToken(httpServletRequest);\n        // 对于 Token 为空的不需要去查 Redis\n        if(StrUtil.isNotBlank(token)){\n            // 获取用户Token的Key\n            String loginKey = tokenProvider.loginKey(token);\n            OnlineUserDto onlineUserDto = onlineUserService.getOne(loginKey);\n            // 判断用户在线信息是否为空\n            if (onlineUserDto != null) {\n                // Token 续期判断\n                tokenProvider.checkRenewal(token);\n                // 获取认证信息，设置上下文\n                Authentication authentication = tokenProvider.getAuthentication(token);\n                SecurityContextHolder.getContext().setAuthentication(authentication);\n            }\n        }\n        filterChain.doFilter(servletRequest, servletResponse);\n    }\n\n    /**\n     * 初步检测Token\n     *\n     * @param request /\n     * @return /\n     */\n    private String resolveToken(HttpServletRequest request) {\n        String bearerToken = request.getHeader(properties.getHeader());\n        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {\n            // 去掉令牌前缀\n            return bearerToken.replace(properties.getTokenStartWith(), \"\");\n        } else {\n            log.debug(\"非法Token：{}\", bearerToken);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.security;\n\nimport cn.hutool.core.date.DateField;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.IdUtil;\nimport io.jsonwebtoken.*;\nimport io.jsonwebtoken.io.Decoders;\nimport io.jsonwebtoken.security.Keys;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.modules.security.config.SecurityProperties;\nimport me.zhengjie.modules.security.service.dto.JwtUserDto;\nimport me.zhengjie.utils.RedisUtils;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.stereotype.Component;\nimport javax.servlet.http.HttpServletRequest;\nimport java.security.Key;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author /\n */\n@Slf4j\n@Component\npublic class TokenProvider implements InitializingBean {\n\n    private Key signingKey;\n    private JwtParser jwtParser;\n    private final RedisUtils redisUtils;\n    private final SecurityProperties properties;\n    public static final String AUTHORITIES_UUID_KEY = \"uid\";\n    public static final String AUTHORITIES_UID_KEY = \"userId\";\n\n    public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {\n        this.properties = properties;\n        this.redisUtils = redisUtils;\n    }\n\n    @Override\n    public void afterPropertiesSet() {\n        // 解码Base64密钥并创建签名密钥\n        byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());\n        this.signingKey = Keys.hmacShaKeyFor(keyBytes);\n        // 初始化 JwtParser\n        jwtParser = Jwts.parserBuilder()\n                .setSigningKey(signingKey) // 使用预生成的签名密钥\n                .build();\n    }\n\n    /**\n     * 创建Token 设置永不过期，\n     * Token 的时间有效性转到Redis 维护\n     *\n     * @param user /\n     * @return /\n     */\n    public String createToken(JwtUserDto user) {\n        // 设置参数\n        Map<String, Object> claims = new HashMap<>(6);\n        // 设置用户ID\n        claims.put(AUTHORITIES_UID_KEY, user.getUser().getId());\n        // 设置UUID，确保每次Token不一样\n        claims.put(AUTHORITIES_UUID_KEY, IdUtil.simpleUUID());\n        // 直接调用 Jwts.builder() 创建新实例\n        return Jwts.builder()\n                // 设置自定义 Claims\n                .setClaims(claims)\n                // 设置主题\n                .setSubject(user.getUsername())\n                // 使用预生成的签名密钥和算法签名\n                .signWith(signingKey, SignatureAlgorithm.HS512)\n                .compact();\n    }\n\n    /**\n     * 依据Token 获取鉴权信息\n     *\n     * @param token /\n     * @return /\n     */\n    Authentication getAuthentication(String token) {\n        Claims claims = getClaims(token);\n        User principal = new User(claims.getSubject(), \"******\", new ArrayList<>());\n        return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());\n    }\n\n    public Claims getClaims(String token) {\n        return jwtParser\n                .parseClaimsJws(token)\n                .getBody();\n    }\n\n    /**\n     * @param token 需要检查的token\n     */\n    public void checkRenewal(String token) {\n        // 判断是否续期token,计算token的过期时间\n        String loginKey = loginKey(token);\n        long time = redisUtils.getExpire(loginKey) * 1000;\n        Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);\n        // 判断当前时间与过期时间的时间差\n        long differ = expireDate.getTime() - System.currentTimeMillis();\n        // 如果在续期检查的范围内，则续期\n        if (differ <= properties.getDetect()) {\n            long renew = time + properties.getRenew();\n            redisUtils.expire(loginKey, renew, TimeUnit.MILLISECONDS);\n        }\n    }\n\n    public String getToken(HttpServletRequest request) {\n        final String requestHeader = request.getHeader(properties.getHeader());\n        if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {\n            return requestHeader.substring(7);\n        }\n        return null;\n    }\n\n    /**\n     * 获取登录用户RedisKey\n     * @param token /\n     * @return key\n     */\n    public String loginKey(String token) {\n        Claims claims = getClaims(token);\n        return properties.getOnlineKey() + claims.getSubject() + \":\" + getId(token);\n    }\n\n    /**\n     * 获取登录用户TokenKey\n     * @param token /\n     * @return /\n     */\n    public String getId(String token) {\n        Claims claims = getClaims(token);\n        return claims.get(AUTHORITIES_UUID_KEY).toString();\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service;\n\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.modules.security.security.TokenProvider;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.security.config.SecurityProperties;\nimport me.zhengjie.modules.security.service.dto.JwtUserDto;\nimport me.zhengjie.modules.security.service.dto.OnlineUserDto;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author Zheng Jie\n * @date 2019年10月26日21:56:27\n */\n@Service\n@Slf4j\n@AllArgsConstructor\npublic class OnlineUserService {\n\n    private final SecurityProperties properties;\n    private final TokenProvider tokenProvider;\n    private final RedisUtils redisUtils;\n\n    /**\n     * 保存在线用户信息\n     * @param jwtUserDto /\n     * @param token /\n     * @param request /\n     */\n    public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){\n        String dept = jwtUserDto.getUser().getDept().getName();\n        String ip = StringUtils.getIp(request);\n        String id = tokenProvider.getId(token);\n        String browser = StringUtils.getBrowser(request);\n        String address = StringUtils.getCityInfo(ip);\n        OnlineUserDto onlineUserDto = null;\n        try {\n            onlineUserDto = new OnlineUserDto(id, jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());\n        } catch (Exception e) {\n            log.error(e.getMessage(),e);\n        }\n        String loginKey = tokenProvider.loginKey(token);\n        redisUtils.set(loginKey, onlineUserDto, properties.getTokenValidityInSeconds(), TimeUnit.MILLISECONDS);\n    }\n\n    /**\n     * 查询全部数据\n     * @param username /\n     * @param pageable /\n     * @return /\n     */\n    public PageResult<OnlineUserDto> getAll(String username, Pageable pageable){\n        List<OnlineUserDto> onlineUserDtos = getAll(username);\n        return PageUtil.toPage(\n                PageUtil.paging(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos),\n                onlineUserDtos.size()\n        );\n    }\n\n    /**\n     * 查询全部数据，不分页\n     * @param username /\n     * @return /\n     */\n    public List<OnlineUserDto> getAll(String username){\n        String loginKey = properties.getOnlineKey() +\n                (StringUtils.isBlank(username) ? \"\" : \"*\" + username);\n        List<String> keys = redisUtils.scan(loginKey + \"*\");\n        Collections.reverse(keys);\n        List<OnlineUserDto> onlineUserDtos = new ArrayList<>();\n        for (String key : keys) {\n            onlineUserDtos.add(redisUtils.get(key, OnlineUserDto.class));\n        }\n        onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));\n        return onlineUserDtos;\n    }\n\n    /**\n     * 退出登录\n     * @param token /\n     */\n    public void logout(String token) {\n        String loginKey = tokenProvider.loginKey(token);\n        redisUtils.del(loginKey);\n    }\n\n    /**\n     * 导出\n     * @param all /\n     * @param response /\n     * @throws IOException /\n     */\n    public void download(List<OnlineUserDto> all, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (OnlineUserDto user : all) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"用户名\", user.getUserName());\n            map.put(\"部门\", user.getDept());\n            map.put(\"登录IP\", user.getIp());\n            map.put(\"登录地点\", user.getAddress());\n            map.put(\"浏览器\", user.getBrowser());\n            map.put(\"登录日期\", user.getLoginTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    /**\n     * 查询用户\n     * @param key /\n     * @return /\n     */\n    public OnlineUserDto getOne(String key) {\n        return redisUtils.get(key, OnlineUserDto.class);\n    }\n\n    /**\n     * 根据用户名强退用户\n     * @param username /\n     */\n    public void kickOutForUsername(String username) {\n        String loginKey = properties.getOnlineKey() + username + \"*\";\n        redisUtils.scanDel(loginKey);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service;\n\nimport cn.hutool.core.util.RandomUtil;\nimport me.zhengjie.modules.security.config.LoginProperties;\nimport me.zhengjie.modules.security.service.dto.JwtUserDto;\nimport me.zhengjie.utils.RedisUtils;\nimport me.zhengjie.utils.StringUtils;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\nimport javax.annotation.Resource;\n\n/**\n * @author Zheng Jie\n * @description 用户缓存管理\n * @date 2022-05-26\n **/\n@Component\npublic class UserCacheManager {\n\n    @Resource\n    private RedisUtils redisUtils;\n    @Value(\"${login.user-cache.idle-time}\")\n    private long idleTime;\n\n    /**\n     * 返回用户缓存\n     * @param userName 用户名\n     * @return JwtUserDto\n     */\n    public JwtUserDto getUserCache(String userName) {\n        // 转小写\n        userName = StringUtils.lowerCase(userName);\n        if (StringUtils.isNotEmpty(userName)) {\n            // 获取数据\n            return redisUtils.get(LoginProperties.cacheKey + userName, JwtUserDto.class);\n        }\n        return null;\n    }\n\n    /**\n     *  添加缓存到Redis\n     * @param userName 用户名\n     */\n    @Async\n    public void addUserCache(String userName, JwtUserDto user) {\n        // 转小写\n        userName = StringUtils.lowerCase(userName);\n        if (StringUtils.isNotEmpty(userName)) {\n            // 添加数据, 避免数据同时过期\n            long time = idleTime + RandomUtil.randomInt(900, 1800);\n            redisUtils.set(LoginProperties.cacheKey + userName, user, time);\n        }\n    }\n\n    /**\n     * 清理用户缓存信息\n     * 用户信息变更时\n     * @param userName 用户名\n     */\n    @Async\n    public void cleanUserCache(String userName) {\n        // 转小写\n        userName = StringUtils.lowerCase(userName);\n        if (StringUtils.isNotEmpty(userName)) {\n            // 清除数据\n            redisUtils.del(LoginProperties.cacheKey + userName);\n        }\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.security.service.dto.AuthorityDto;\nimport me.zhengjie.modules.security.service.dto.JwtUserDto;\nimport me.zhengjie.modules.system.service.DataService;\nimport me.zhengjie.modules.system.service.RoleService;\nimport me.zhengjie.modules.system.service.UserService;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.stereotype.Service;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-22\n */\n@Slf4j\n@RequiredArgsConstructor\n@Service(\"userDetailsService\")\npublic class UserDetailsServiceImpl implements UserDetailsService {\n    private final UserService userService;\n    private final RoleService roleService;\n    private final DataService dataService;\n    private final UserCacheManager userCacheManager;\n\n    @Override\n    public JwtUserDto loadUserByUsername(String username) {\n        JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);\n        if(jwtUserDto == null){\n            UserDto user = userService.getLoginData(username);\n            if (user == null) {\n                throw new BadRequestException(\"用户不存在\");\n            } else {\n                if (!user.getEnabled()) {\n                    throw new BadRequestException(\"账号未激活！\");\n                }\n                // 获取用户的权限\n                List<AuthorityDto> authorities = roleService.buildPermissions(user);\n                // 初始化JwtUserDto\n                jwtUserDto = new JwtUserDto(user, dataService.getDeptIds(user), authorities);\n                // 添加缓存数据\n                userCacheManager.addUserCache(username, jwtUserDto);\n            }\n        }\n        return jwtUserDto;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthUserDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-30\n */\n@Getter\n@Setter\npublic class AuthUserDto {\n\n    @NotBlank\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @NotBlank\n    @ApiModelProperty(value = \"密码\")\n    private String password;\n\n    @ApiModelProperty(value = \"验证码\")\n    private String code;\n\n    @ApiModelProperty(value = \"验证码的key\")\n    private String uuid = \"\";\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthorityDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.springframework.security.core.GrantedAuthority;\n\n/**\n * 避免序列化问题\n * @author Zheng Jie\n * @date 2018-11-30\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AuthorityDto implements GrantedAuthority {\n\n    @ApiModelProperty(value = \"角色名\")\n    private String authority;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/JwtUserDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service.dto;\n\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Getter\n@AllArgsConstructor\npublic class JwtUserDto implements UserDetails {\n\n    @ApiModelProperty(value = \"用户\")\n    private final UserDto user;\n\n    @ApiModelProperty(value = \"数据权限\")\n    private final List<Long> dataScopes;\n\n    @ApiModelProperty(value = \"角色权限\")\n    private final List<AuthorityDto> authorities;\n\n    public Set<String> getRoles() {\n        return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet());\n    }\n\n    @Override\n    @JSONField(serialize = false)\n    public String getPassword() {\n        return user.getPassword();\n    }\n\n    @Override\n    @JSONField(serialize = false)\n    public String getUsername() {\n        return user.getUsername();\n    }\n\n    @JSONField(serialize = false)\n    @Override\n    public boolean isAccountNonExpired() {\n        return true;\n    }\n\n    @JSONField(serialize = false)\n    @Override\n    public boolean isAccountNonLocked() {\n        return true;\n    }\n\n    @JSONField(serialize = false)\n    @Override\n    public boolean isCredentialsNonExpired() {\n        return true;\n    }\n\n    @Override\n    @JSONField(serialize = false)\n    public boolean isEnabled() {\n        return user.getEnabled();\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/OnlineUserDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.security.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport java.util.Date;\n\n/**\n * 在线用户\n * @author Zheng Jie\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class OnlineUserDto {\n\n    @ApiModelProperty(value = \"Token编号\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String userName;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickName;\n\n    @ApiModelProperty(value = \"岗位\")\n    private String dept;\n\n    @ApiModelProperty(value = \"浏览器\")\n    private String browser;\n\n    @ApiModelProperty(value = \"IP\")\n    private String ip;\n\n    @ApiModelProperty(value = \"地址\")\n    private String address;\n\n    @ApiModelProperty(value = \"token\")\n    private String key;\n\n    @ApiModelProperty(value = \"登录时间\")\n    private Date loginTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dept.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"sys_dept\")\npublic class Dept extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"dept_id\")\n    @NotNull(groups = Update.class)\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @JSONField(serialize = false)\n    @ManyToMany(mappedBy = \"depts\")\n    @ApiModelProperty(value = \"角色\")\n    private Set<Role> roles;\n\n    @ApiModelProperty(value = \"排序\")\n    private Integer deptSort;\n\n    @NotBlank\n    @ApiModelProperty(value = \"部门名称\")\n    private String name;\n\n    @NotNull\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @ApiModelProperty(value = \"上级部门\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"子节点数目\", hidden = true)\n    private Integer subCount = 0;\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Dept dept = (Dept) o;\n        return Objects.equals(id, dept.id) &&\n                Objects.equals(name, dept.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, name);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dict.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"sys_dict\")\npublic class Dict extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"dict_id\")\n    @NotNull(groups = Update.class)\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @OneToMany(mappedBy = \"dict\",cascade={CascadeType.PERSIST,CascadeType.REMOVE})\n    private List<DictDetail> dictDetails;\n\n    @NotBlank\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/DictDetail.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"sys_dict_detail\")\npublic class DictDetail extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"detail_id\")\n    @NotNull(groups = Update.class)\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @JoinColumn(name = \"dict_id\")\n    @ManyToOne(fetch=FetchType.LAZY)\n    @ApiModelProperty(value = \"字典\", hidden = true)\n    private Dict dict;\n\n    @ApiModelProperty(value = \"字典标签\")\n    private String label;\n\n    @ApiModelProperty(value = \"字典值\")\n    private String value;\n\n    @ApiModelProperty(value = \"排序\")\n    private Integer dictSort = 999;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Job.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\n@Entity\n@Getter\n@Setter\n@Table(name=\"sys_job\")\npublic class Job extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"job_id\")\n    @NotNull(groups = Update.class)\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @NotBlank\n    @ApiModelProperty(value = \"岗位名称\")\n    private String name;\n\n    @NotNull\n    @ApiModelProperty(value = \"岗位排序\")\n    private Long jobSort;\n\n    @NotNull\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Job job = (Job) o;\n        return Objects.equals(id, job.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Menu.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-17\n */\n@Entity\n@Getter\n@Setter\n@Table(name = \"sys_menu\")\npublic class Menu extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"menu_id\")\n    @NotNull(groups = {Update.class})\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @JSONField(serialize = false)\n    @ManyToMany(mappedBy = \"menus\")\n    @ApiModelProperty(value = \"菜单角色\")\n    private Set<Role> roles;\n\n    @ApiModelProperty(value = \"菜单标题\")\n    private String title;\n\n    @Column(name = \"name\")\n    @ApiModelProperty(value = \"菜单组件名称\")\n    private String componentName;\n\n    @ApiModelProperty(value = \"排序\")\n    private Integer menuSort = 999;\n\n    @ApiModelProperty(value = \"组件路径\")\n    private String component;\n\n    @ApiModelProperty(value = \"路由地址\")\n    private String path;\n\n    @ApiModelProperty(value = \"菜单类型，目录、菜单、按钮\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"权限标识\")\n    private String permission;\n\n    @ApiModelProperty(value = \"菜单图标\")\n    private String icon;\n\n    @Column(columnDefinition = \"bit(1) default 0\")\n    @ApiModelProperty(value = \"缓存\")\n    private Boolean cache;\n\n    @Column(columnDefinition = \"bit(1) default 0\")\n    @ApiModelProperty(value = \"是否隐藏\")\n    private Boolean hidden;\n\n    @ApiModelProperty(value = \"上级菜单\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"子节点数目\", hidden = true)\n    private Integer subCount = 0;\n\n    @ApiModelProperty(value = \"外链菜单\")\n    private Boolean iFrame;\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Menu menu = (Menu) o;\n        return Objects.equals(id, menu.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Role.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport me.zhengjie.utils.enums.DataScopeEnum;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * 角色\n * @author Zheng Jie\n * @date 2018-11-22\n */\n@Getter\n@Setter\n@Entity\n@Table(name = \"sys_role\")\npublic class Role extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"role_id\")\n    @NotNull(groups = {Update.class})\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    private Long id;\n\n    @JSONField(serialize = false)\n    @ManyToMany(mappedBy = \"roles\")\n    @ApiModelProperty(value = \"用户\", hidden = true)\n    private Set<User> users;\n\n    @ManyToMany(fetch = FetchType.EAGER)\n    @JoinTable(name = \"sys_roles_menus\",\n            joinColumns = {@JoinColumn(name = \"role_id\",referencedColumnName = \"role_id\")},\n            inverseJoinColumns = {@JoinColumn(name = \"menu_id\",referencedColumnName = \"menu_id\")})\n    @ApiModelProperty(value = \"菜单\", hidden = true)\n    private Set<Menu> menus;\n\n    @ManyToMany\n    @JoinTable(name = \"sys_roles_depts\",\n            joinColumns = {@JoinColumn(name = \"role_id\",referencedColumnName = \"role_id\")},\n            inverseJoinColumns = {@JoinColumn(name = \"dept_id\",referencedColumnName = \"dept_id\")})\n    @ApiModelProperty(value = \"部门\", hidden = true)\n    private Set<Dept> depts;\n\n    @NotBlank\n    @ApiModelProperty(value = \"名称\", hidden = true)\n    private String name;\n\n    @ApiModelProperty(value = \"数据权限，全部 、 本级 、 自定义\")\n    private String dataScope = DataScopeEnum.THIS_LEVEL.getValue();\n\n    @Column(name = \"level\")\n    @ApiModelProperty(value = \"级别，数值越小，级别越大\")\n    private Integer level = 3;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Role role = (Role) o;\n        return Objects.equals(id, role.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/User.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.Date;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-22\n */\n@Entity\n@Getter\n@Setter\n@Table(name=\"sys_user\")\npublic class User extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"user_id\")\n    @NotNull(groups = Update.class)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    private Long id;\n\n    @ManyToMany(fetch = FetchType.EAGER)\n    @ApiModelProperty(value = \"用户角色\")\n    @JoinTable(name = \"sys_users_roles\",\n            joinColumns = {@JoinColumn(name = \"user_id\",referencedColumnName = \"user_id\")},\n            inverseJoinColumns = {@JoinColumn(name = \"role_id\",referencedColumnName = \"role_id\")})\n    private Set<Role> roles;\n\n    @ManyToMany(fetch = FetchType.EAGER)\n    @ApiModelProperty(value = \"用户岗位\")\n    @JoinTable(name = \"sys_users_jobs\",\n            joinColumns = {@JoinColumn(name = \"user_id\",referencedColumnName = \"user_id\")},\n            inverseJoinColumns = {@JoinColumn(name = \"job_id\",referencedColumnName = \"job_id\")})\n    private Set<Job> jobs;\n\n    @OneToOne\n    @JoinColumn(name = \"dept_id\")\n    @ApiModelProperty(value = \"用户部门\")\n    private Dept dept;\n\n    @NotBlank\n    @Column(unique = true)\n    @ApiModelProperty(value = \"用户名称\")\n    private String username;\n\n    @NotBlank\n    @ApiModelProperty(value = \"用户昵称\")\n    private String nickName;\n\n    @Email\n    @NotBlank\n    @ApiModelProperty(value = \"邮箱\")\n    private String email;\n\n    @NotBlank\n    @ApiModelProperty(value = \"电话号码\")\n    private String phone;\n\n    @ApiModelProperty(value = \"用户性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像真实名称\",hidden = true)\n    private String avatarName;\n\n    @ApiModelProperty(value = \"头像存储的路径\", hidden = true)\n    private String avatarPath;\n\n    @ApiModelProperty(value = \"密码\")\n    private String password;\n\n    @NotNull\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @ApiModelProperty(value = \"是否为admin账号\", hidden = true)\n    private Boolean isAdmin = false;\n\n    @Column(name = \"pwd_reset_time\")\n    @ApiModelProperty(value = \"最后修改密码的时间\", hidden = true)\n    private Date pwdResetTime;\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        User user = (User) o;\n        return Objects.equals(id, user.id) &&\n                Objects.equals(username, user.username);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, username);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuMetaVo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport java.io.Serializable;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-20\n */\n@Data\n@AllArgsConstructor\npublic class MenuMetaVo implements Serializable {\n\n    @ApiModelProperty(value = \"菜单标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"菜单图标\")\n    private String icon;\n\n    @ApiModelProperty(value = \"缓存\")\n    private Boolean noCache;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuVo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 构建前端路由时用到\n * @author Zheng Jie\n * @date 2018-12-20\n */\n@Data\npublic class MenuVo implements Serializable {\n\n    @ApiModelProperty(value = \"菜单名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"路径\")\n    private String path;\n\n    @ApiModelProperty(value = \"隐藏状态\")\n    private Boolean hidden;\n\n    @ApiModelProperty(value = \"重定向\")\n    private String redirect;\n\n    @ApiModelProperty(value = \"组件\")\n    private String component;\n\n    @ApiModelProperty(value = \"总是显示\")\n    private Boolean alwaysShow;\n\n    @ApiModelProperty(value = \"元数据\")\n    private MenuMetaVo meta;\n\n    @ApiModelProperty(value = \"子路由\")\n    private List<MenuVo> children;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/UserPassVo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.domain.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * 修改密码的 Vo 类\n * @author Zheng Jie\n * @date 2019年7月11日13:59:49\n */\n@Data\npublic class UserPassVo {\n\n    @ApiModelProperty(value = \"旧密码\")\n    private String oldPass;\n\n    @ApiModelProperty(value = \"新密码\")\n    private String newPass;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DeptRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.Dept;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.data.jpa.repository.Query;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\npublic interface DeptRepository extends JpaRepository<Dept, Long>, JpaSpecificationExecutor<Dept> {\n\n    /**\n     * 根据 PID 查询\n     * @param id pid\n     * @return /\n     */\n    List<Dept> findByPid(Long id);\n\n    /**\n     * 获取顶级部门\n     * @return /\n     */\n    List<Dept> findByPidIsNull();\n\n    /**\n     * 根据角色ID 查询\n     * @param roleId 角色ID\n     * @return /\n     */\n    @Query(value = \"select d.* from sys_dept d, sys_roles_depts r where \" +\n            \"d.dept_id = r.dept_id and r.role_id = ?1\", nativeQuery = true)\n    Set<Dept> findByRoleId(Long roleId);\n\n    /**\n     * 判断是否存在子节点\n     * @param pid /\n     * @return /\n     */\n    int countByPid(Long pid);\n\n    /**\n     * 根据ID更新sub_count\n     * @param count /\n     * @param id /\n     */\n    @Modifying\n    @Query(value = \" update sys_dept set sub_count = ?1 where dept_id = ?2 \",nativeQuery = true)\n    void updateSubCntById(Integer count, Long id);\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictDetailRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.DictDetail;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\npublic interface DictDetailRepository extends JpaRepository<DictDetail, Long>, JpaSpecificationExecutor<DictDetail> {\n\n    /**\n     * 根据字典名称查询\n     * @param name /\n     * @return /\n     */\n    List<DictDetail> findByDictName(String name);\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.Dict;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\npublic interface DictRepository extends JpaRepository<Dict, Long>, JpaSpecificationExecutor<Dict> {\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void deleteByIdIn(Set<Long> ids);\n\n    /**\n     * 查询\n     * @param ids /\n     * @return /\n     */\n    List<Dict> findByIdIn(Set<Long> ids);\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/JobRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.Job;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\npublic interface JobRepository extends JpaRepository<Job, Long>, JpaSpecificationExecutor<Job> {\n\n    /**\n     * 根据名称查询\n     * @param name 名称\n     * @return /\n     */\n    Job findByName(String name);\n\n    /**\n     * 根据Id删除\n     * @param ids /\n     */\n    void deleteAllByIdIn(Set<Long> ids);\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.Menu;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.data.jpa.repository.Query;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-17\n */\npublic interface MenuRepository extends JpaRepository<Menu, Long>, JpaSpecificationExecutor<Menu> {\n\n    /**\n     * 根据菜单标题查询\n     * @param title 菜单标题\n     * @return /\n     */\n    Menu findByTitle(String title);\n\n    /**\n     * 根据组件名称查询\n     * @param name 组件名称\n     * @return /\n     */\n    Menu findByComponentName(String name);\n\n    /**\n     * 根据菜单的 PID 查询\n     * @param pid /\n     * @return /\n     */\n    List<Menu> findByPidOrderByMenuSort(long pid);\n\n    /**\n     * 查询顶级菜单\n     * @return /\n     */\n    List<Menu> findByPidIsNullOrderByMenuSort();\n\n    /**\n     * 根据角色ID与菜单类型查询菜单\n     * @param roleIds roleIDs\n     * @param type 类型\n     * @return /\n     */\n    @Query(value = \"SELECT m.* FROM sys_menu m, sys_roles_menus r WHERE \" +\n            \"m.menu_id = r.menu_id AND r.role_id IN ?1 AND type != ?2 order by m.menu_sort asc\",nativeQuery = true)\n    LinkedHashSet<Menu> findByRoleIdsAndTypeNot(Set<Long> roleIds, int type);\n\n    /**\n     * 获取节点数量\n     * @param id /\n     * @return /\n     */\n    int countByPid(Long id);\n\n    /**\n     * 更新节点数目\n     * @param count /\n     * @param menuId /\n     */\n    @Modifying\n    @Query(value = \" update sys_menu set sub_count = ?1 where menu_id = ?2 \",nativeQuery = true)\n    void updateSubCntById(int count, Long menuId);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/RoleRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.Role;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.data.jpa.repository.Query;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-03\n */\npublic interface RoleRepository extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> {\n\n    /**\n     * 根据名称查询\n     * @param name /\n     * @return /\n     */\n    Role findByName(String name);\n\n    /**\n     * 删除多个角色\n     * @param ids /\n     */\n    void deleteAllByIdIn(Set<Long> ids);\n\n    /**\n     * 根据用户ID查询\n     * @param id 用户ID\n     * @return /\n     */\n    @Query(value = \"SELECT r.* FROM sys_role r, sys_users_roles u WHERE \" +\n            \"r.role_id = u.role_id AND u.user_id = ?1\",nativeQuery = true)\n    Set<Role> findByUserId(Long id);\n\n    /**\n     * 解绑角色菜单\n     * @param id 菜单ID\n     */\n    @Modifying\n    @Query(value = \"delete from sys_roles_menus where menu_id = ?1\",nativeQuery = true)\n    void untiedMenu(Long id);\n\n    /**\n     * 根据部门查询\n     * @param deptIds /\n     * @return /\n     */\n    @Query(value = \"select count(1) from sys_role r, sys_roles_depts d where \" +\n            \"r.role_id = d.role_id and d.dept_id in ?1\",nativeQuery = true)\n    int countByDepts(Set<Long> deptIds);\n\n    /**\n     * 根据菜单Id查询\n     * @param menuIds /\n     * @return /\n     */\n    @Query(value = \"SELECT r.* FROM sys_role r, sys_roles_menus m WHERE \" +\n            \"r.role_id = m.role_id AND m.menu_id in ?1\",nativeQuery = true)\n    List<Role> findInMenuId(List<Long> menuIds);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/repository/UserRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.repository;\n\nimport me.zhengjie.modules.system.domain.User;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.data.jpa.repository.Query;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-22\n */\npublic interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {\n\n    /**\n     * 根据用户名查询\n     * @param username 用户名\n     * @return /\n     */\n    User findByUsername(String username);\n\n    /**\n     * 根据邮箱查询\n     * @param email 邮箱\n     * @return /\n     */\n    User findByEmail(String email);\n\n    /**\n     * 根据手机号查询\n     * @param phone 手机号\n     * @return /\n     */\n    User findByPhone(String phone);\n\n    /**\n     * 修改密码\n     * @param username 用户名\n     * @param pass 密码\n     * @param lastPasswordResetTime /\n     */\n    @Modifying\n    @Query(value = \"update sys_user set password = ?2 , pwd_reset_time = ?3 where username = ?1\",nativeQuery = true)\n    void updatePass(String username, String pass, Date lastPasswordResetTime);\n\n    /**\n     * 修改邮箱\n     * @param username 用户名\n     * @param email 邮箱\n     */\n    @Modifying\n    @Query(value = \"update sys_user set email = ?2 where username = ?1\",nativeQuery = true)\n    void updateEmail(String username, String email);\n\n    /**\n     * 根据角色查询用户\n     * @param roleId /\n     * @return /\n     */\n    @Query(value = \"SELECT u.* FROM sys_user u, sys_users_roles r WHERE\" +\n            \" u.user_id = r.user_id AND r.role_id = ?1\", nativeQuery = true)\n    List<User> findByRoleId(Long roleId);\n\n    /**\n     * 根据角色中的部门查询\n     * @param deptId /\n     * @return /\n     */\n    @Query(value = \"SELECT u.* FROM sys_user u, sys_users_roles r, sys_roles_depts d WHERE \" +\n            \"u.user_id = r.user_id AND r.role_id = d.role_id AND d.dept_id = ?1 group by u.user_id\", nativeQuery = true)\n    List<User> findByRoleDeptId(Long deptId);\n\n    /**\n     * 根据菜单查询\n     * @param id 菜单ID\n     * @return /\n     */\n    @Query(value = \"SELECT u.* FROM sys_user u, sys_users_roles ur, sys_roles_menus rm WHERE\\n\" +\n            \"u.user_id = ur.user_id AND ur.role_id = rm.role_id AND rm.menu_id = ?1 group by u.user_id\", nativeQuery = true)\n    List<User> findByMenuId(Long id);\n\n    /**\n     * 根据Id删除\n     * @param ids /\n     */\n    void deleteAllByIdIn(Set<Long> ids);\n\n    /**\n     * 根据岗位查询\n     * @param ids /\n     * @return /\n     */\n    @Query(value = \"SELECT count(1) FROM sys_user u, sys_users_jobs j WHERE u.user_id = j.user_id AND j.job_id IN ?1\", nativeQuery = true)\n    int countByJobs(Set<Long> ids);\n\n    /**\n     * 根据部门查询\n     * @param deptIds /\n     * @return /\n     */\n    @Query(value = \"SELECT count(1) FROM sys_user u WHERE u.dept_id IN ?1\", nativeQuery = true)\n    int countByDepts(Set<Long> deptIds);\n\n    /**\n     * 根据角色查询\n     * @param ids /\n     * @return /\n     */\n    @Query(value = \"SELECT count(1) FROM sys_user u, sys_users_roles r WHERE \" +\n            \"u.user_id = r.user_id AND r.role_id in ?1\", nativeQuery = true)\n    int countByRoles(Set<Long> ids);\n\n    /**\n     * 重置密码\n     * @param ids 、\n     * @param pwd 、\n     */\n    @Modifying\n    @Query(value = \"update sys_user set password = ?2 where user_id in ?1\",nativeQuery = true)\n    void resetPwd(Set<Long> ids, String pwd);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DeptController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.service.DeptService;\nimport me.zhengjie.modules.system.service.dto.DeptDto;\nimport me.zhengjie.modules.system.service.dto.DeptQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.PageUtil;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统：部门管理\")\n@RequestMapping(\"/api/dept\")\npublic class DeptController {\n\n    private final DeptService deptService;\n    private static final String ENTITY_NAME = \"dept\";\n\n    @ApiOperation(\"导出部门数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('dept:list')\")\n    public void exportDept(HttpServletResponse response, DeptQueryCriteria criteria) throws Exception {\n        deptService.download(deptService.queryAll(criteria, false), response);\n    }\n\n    @ApiOperation(\"查询部门\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('user:list','dept:list')\")\n    public ResponseEntity<PageResult<DeptDto>> queryDept(DeptQueryCriteria criteria) throws Exception {\n        List<DeptDto> depts = deptService.queryAll(criteria, true);\n        return new ResponseEntity<>(PageUtil.toPage(depts, depts.size()),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询部门:根据ID获取同级与上级数据\")\n    @PostMapping(\"/superior\")\n    @PreAuthorize(\"@el.check('user:list','dept:list')\")\n    public ResponseEntity<Object> getDeptSuperior(@RequestBody List<Long> ids,\n                                                  @RequestParam(defaultValue = \"false\") Boolean exclude) {\n        Set<DeptDto> deptSet  = new LinkedHashSet<>();\n        for (Long id : ids) {\n            DeptDto deptDto = deptService.findById(id);\n            List<DeptDto> depts = deptService.getSuperior(deptDto, new ArrayList<>());\n            if(exclude){\n                for (DeptDto dept : depts) {\n                    if(dept.getId().equals(deptDto.getPid())) {\n                        dept.setSubCount(dept.getSubCount() - 1);\n                    }\n                }\n                // 编辑部门时不显示自己以及自己下级的数据，避免出现PID数据环形问题\n                depts = depts.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toList());\n            }\n            deptSet.addAll(depts);\n        }\n        return new ResponseEntity<>(deptService.buildTree(new ArrayList<>(deptSet)),HttpStatus.OK);\n    }\n\n    @Log(\"新增部门\")\n    @ApiOperation(\"新增部门\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('dept:add')\")\n    public ResponseEntity<Object> createDept(@Validated @RequestBody Dept resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        deptService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改部门\")\n    @ApiOperation(\"修改部门\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('dept:edit')\")\n    public ResponseEntity<Object> updateDept(@Validated(Dept.Update.class) @RequestBody Dept resources){\n        deptService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除部门\")\n    @ApiOperation(\"删除部门\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('dept:del')\")\n    public ResponseEntity<Object> deleteDept(@RequestBody Set<Long> ids){\n        Set<DeptDto> deptDtos = new HashSet<>();\n        for (Long id : ids) {\n            List<Dept> deptList = deptService.findByPid(id);\n            deptDtos.add(deptService.findById(id));\n            if(CollectionUtil.isNotEmpty(deptList)){\n                deptDtos = deptService.getDeleteDepts(deptList, deptDtos);\n            }\n        }\n        // 验证是否被角色或用户关联\n        deptService.verification(deptDtos);\n        deptService.delete(deptDtos);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.Dict;\nimport me.zhengjie.modules.system.service.DictService;\nimport me.zhengjie.modules.system.service.dto.DictDto;\nimport me.zhengjie.modules.system.service.dto.DictQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.PageUtil;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统：字典管理\")\n@RequestMapping(\"/api/dict\")\npublic class DictController {\n\n    private final DictService dictService;\n    private static final String ENTITY_NAME = \"dict\";\n\n    @ApiOperation(\"导出字典数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('dict:list')\")\n    public void exportDict(HttpServletResponse response, DictQueryCriteria criteria) throws IOException {\n        dictService.download(dictService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(\"查询字典\")\n    @GetMapping(value = \"/all\")\n    @PreAuthorize(\"@el.check('dict:list')\")\n    public ResponseEntity<List<DictDto>> queryAllDict(){\n        return new ResponseEntity<>(dictService.queryAll(new DictQueryCriteria()),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询字典\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('dict:list')\")\n    public ResponseEntity<PageResult<DictDto>> queryDict(DictQueryCriteria resources, Pageable pageable){\n        return new ResponseEntity<>(dictService.queryAll(resources,pageable),HttpStatus.OK);\n    }\n\n    @Log(\"新增字典\")\n    @ApiOperation(\"新增字典\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('dict:add')\")\n    public ResponseEntity<Object> createDict(@Validated @RequestBody Dict resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        dictService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改字典\")\n    @ApiOperation(\"修改字典\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('dict:edit')\")\n    public ResponseEntity<Object> updateDict(@Validated(Dict.Update.class) @RequestBody Dict resources){\n        dictService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除字典\")\n    @ApiOperation(\"删除字典\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('dict:del')\")\n    public ResponseEntity<Object> deleteDict(@RequestBody Set<Long> ids){\n        dictService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictDetailController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.DictDetail;\nimport me.zhengjie.modules.system.service.DictDetailService;\nimport me.zhengjie.modules.system.service.dto.DictDetailDto;\nimport me.zhengjie.modules.system.service.dto.DictDetailQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统：字典详情管理\")\n@RequestMapping(\"/api/dictDetail\")\npublic class DictDetailController {\n\n    private final DictDetailService dictDetailService;\n    private static final String ENTITY_NAME = \"dictDetail\";\n\n    @ApiOperation(\"查询字典详情\")\n    @GetMapping\n    public ResponseEntity<PageResult<DictDetailDto>> queryDictDetail(DictDetailQueryCriteria criteria,\n                                                                     @PageableDefault(sort = {\"dictSort\"}, direction = Sort.Direction.ASC) Pageable pageable){\n        return new ResponseEntity<>(dictDetailService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询多个字典详情\")\n    @GetMapping(value = \"/map\")\n    public ResponseEntity<Object> getDictDetailMaps(@RequestParam String dictName){\n        String[] names = dictName.split(\"[,，]\");\n        Map<String, List<DictDetailDto>> dictMap = new HashMap<>(16);\n        for (String name : names) {\n            dictMap.put(name, dictDetailService.getDictByName(name));\n        }\n        return new ResponseEntity<>(dictMap, HttpStatus.OK);\n    }\n\n    @Log(\"新增字典详情\")\n    @ApiOperation(\"新增字典详情\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('dict:add')\")\n    public ResponseEntity<Object> createDictDetail(@Validated @RequestBody DictDetail resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        dictDetailService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改字典详情\")\n    @ApiOperation(\"修改字典详情\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('dict:edit')\")\n    public ResponseEntity<Object> updateDictDetail(@Validated(DictDetail.Update.class) @RequestBody DictDetail resources){\n        dictDetailService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除字典详情\")\n    @ApiOperation(\"删除字典详情\")\n    @DeleteMapping(value = \"/{id}\")\n    @PreAuthorize(\"@el.check('dict:del')\")\n    public ResponseEntity<Object> deleteDictDetail(@PathVariable Long id){\n        dictDetailService.delete(id);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/JobController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.Job;\nimport me.zhengjie.modules.system.service.JobService;\nimport me.zhengjie.modules.system.service.dto.JobDto;\nimport me.zhengjie.modules.system.service.dto.JobQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统：岗位管理\")\n@RequestMapping(\"/api/job\")\npublic class JobController {\n\n    private final JobService jobService;\n    private static final String ENTITY_NAME = \"job\";\n\n    @ApiOperation(\"导出岗位数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('job:list')\")\n    public void exportJob(HttpServletResponse response, JobQueryCriteria criteria) throws IOException {\n        jobService.download(jobService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(\"查询岗位\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('job:list','user:list')\")\n    public ResponseEntity<PageResult<JobDto>> queryJob(JobQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(jobService.queryAll(criteria, pageable),HttpStatus.OK);\n    }\n\n    @Log(\"新增岗位\")\n    @ApiOperation(\"新增岗位\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('job:add')\")\n    public ResponseEntity<Object> createJob(@Validated @RequestBody Job resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        jobService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改岗位\")\n    @ApiOperation(\"修改岗位\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('job:edit')\")\n    public ResponseEntity<Object> updateJob(@Validated(Job.Update.class) @RequestBody Job resources){\n        jobService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除岗位\")\n    @ApiOperation(\"删除岗位\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('job:del')\")\n    public ResponseEntity<Object> deleteJob(@RequestBody Set<Long> ids){\n        // 验证是否被用户关联\n        jobService.verification(ids);\n        jobService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/LimitController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport me.zhengjie.annotation.Limit;\nimport me.zhengjie.annotation.rest.AnonymousGetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author /\n * 接口限流测试类\n */\n@RestController\n@RequestMapping(\"/api/limit\")\n@Api(tags = \"系统：限流测试管理\")\npublic class LimitController {\n\n    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();\n\n    /**\n     * 测试限流注解，下面配置说明该接口 60秒内最多只能访问 10次，保存到redis的键名为 limit_test，\n     */\n    @AnonymousGetMapping\n    @ApiOperation(\"测试\")\n    @Limit(key = \"test\", period = 60, count = 10, name = \"testLimit\", prefix = \"limit\")\n    public int testLimit() {\n        return ATOMIC_INTEGER.incrementAndGet();\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MenuController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.modules.system.domain.Menu;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.vo.MenuVo;\nimport me.zhengjie.modules.system.service.MenuService;\nimport me.zhengjie.modules.system.service.dto.DeptDto;\nimport me.zhengjie.modules.system.service.dto.MenuDto;\nimport me.zhengjie.modules.system.service.dto.MenuQueryCriteria;\nimport me.zhengjie.modules.system.service.mapstruct.MenuMapper;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.PageUtil;\nimport me.zhengjie.utils.SecurityUtils;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-03\n */\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统：菜单管理\")\n@RequestMapping(\"/api/menus\")\npublic class MenuController {\n\n    private final MenuService menuService;\n    private final MenuMapper menuMapper;\n    private static final String ENTITY_NAME = \"menu\";\n\n    @ApiOperation(\"导出菜单数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('menu:list')\")\n    public void exportMenu(HttpServletResponse response, MenuQueryCriteria criteria) throws Exception {\n        menuService.download(menuService.queryAll(criteria, false), response);\n    }\n\n    @GetMapping(value = \"/build\")\n    @ApiOperation(\"获取前端所需菜单\")\n    public ResponseEntity<List<MenuVo>> buildMenus(){\n        List<MenuDto> menuDtoList = menuService.findByUser(SecurityUtils.getCurrentUserId());\n        List<MenuDto> menus = menuService.buildTree(menuDtoList);\n        return new ResponseEntity<>(menuService.buildMenus(menus),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"返回全部的菜单\")\n    @GetMapping(value = \"/lazy\")\n    @PreAuthorize(\"@el.check('menu:list','roles:list')\")\n    public ResponseEntity<List<MenuDto>> queryAllMenu(@RequestParam Long pid){\n        return new ResponseEntity<>(menuService.getMenus(pid),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"根据菜单ID返回所有子节点ID，包含自身ID\")\n    @GetMapping(value = \"/child\")\n    @PreAuthorize(\"@el.check('menu:list','roles:list')\")\n    public ResponseEntity<Object> childMenu(@RequestParam Long id){\n        Set<Menu> menuSet = new HashSet<>();\n        List<MenuDto> menuList = menuService.getMenus(id);\n        menuSet.add(menuService.findOne(id));\n        menuSet = menuService.getChildMenus(menuMapper.toEntity(menuList), menuSet);\n        Set<Long> ids = menuSet.stream().map(Menu::getId).collect(Collectors.toSet());\n        return new ResponseEntity<>(ids,HttpStatus.OK);\n    }\n\n    @GetMapping\n    @ApiOperation(\"查询菜单\")\n    @PreAuthorize(\"@el.check('menu:list')\")\n    public ResponseEntity<PageResult<MenuDto>> queryMenu(MenuQueryCriteria criteria) throws Exception {\n        List<MenuDto> menuDtoList = menuService.queryAll(criteria, true);\n        return new ResponseEntity<>(PageUtil.toPage(menuDtoList, menuDtoList.size()),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询菜单:根据ID获取同级与上级数据\")\n    @PostMapping(\"/superior\")\n    @PreAuthorize(\"@el.check('menu:list')\")\n    public ResponseEntity<List<MenuDto>> getMenuSuperior(@RequestBody List<Long> ids) {\n        Set<MenuDto> menuDtos = new LinkedHashSet<>();\n        if(CollectionUtil.isNotEmpty(ids)){\n            for (Long id : ids) {\n                MenuDto menuDto = menuService.findById(id);\n                List<MenuDto> menuDtoList = menuService.getSuperior(menuDto, new ArrayList<>());\n                for (MenuDto menu : menuDtoList) {\n                    if(menu.getId().equals(menuDto.getPid())) {\n                        menu.setSubCount(menu.getSubCount() - 1);\n                    }\n                }\n                menuDtos.addAll(menuDtoList);\n            }\n            // 编辑菜单时不显示自己以及自己下级的数据，避免出现PID数据环形问题\n            menuDtos = menuDtos.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toSet());\n            return new ResponseEntity<>(menuService.buildTree(new ArrayList<>(menuDtos)),HttpStatus.OK);\n        }\n        return new ResponseEntity<>(menuService.getMenus(null),HttpStatus.OK);\n    }\n\n    @Log(\"新增菜单\")\n    @ApiOperation(\"新增菜单\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('menu:add')\")\n    public ResponseEntity<Object> createMenu(@Validated @RequestBody Menu resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        menuService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改菜单\")\n    @ApiOperation(\"修改菜单\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('menu:edit')\")\n    public ResponseEntity<Object> updateMenu(@Validated(Menu.Update.class) @RequestBody Menu resources){\n        menuService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除菜单\")\n    @ApiOperation(\"删除菜单\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('menu:del')\")\n    public ResponseEntity<Object> deleteMenu(@RequestBody Set<Long> ids){\n        Set<Menu> menuSet = new HashSet<>();\n        for (Long id : ids) {\n            List<MenuDto> menuList = menuService.getMenus(id);\n            menuSet.add(menuService.findOne(id));\n            menuSet = menuService.getChildMenus(menuMapper.toEntity(menuList), menuSet);\n        }\n        menuService.delete(menuSet);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MonitorController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.system.service.MonitorService;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @author Zheng Jie\n * @date 2020-05-02\n */\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统-服务监控管理\")\n@RequestMapping(\"/api/monitor\")\npublic class MonitorController {\n\n    private final MonitorService serverService;\n\n    @GetMapping\n    @ApiOperation(\"查询服务监控\")\n    @PreAuthorize(\"@el.check('monitor:list')\")\n    public ResponseEntity<Object> queryMonitor(){\n        return new ResponseEntity<>(serverService.getServers(),HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport cn.hutool.core.lang.Dict;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.modules.system.domain.Role;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.service.RoleService;\nimport me.zhengjie.modules.system.service.dto.RoleDto;\nimport me.zhengjie.modules.system.service.dto.RoleQueryCriteria;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.utils.SecurityUtils;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-03\n */\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"系统：角色管理\")\n@RequestMapping(\"/api/roles\")\npublic class RoleController {\n\n    private final RoleService roleService;\n\n    private static final String ENTITY_NAME = \"role\";\n\n    @ApiOperation(\"获取单个role\")\n    @GetMapping(value = \"/{id}\")\n    @PreAuthorize(\"@el.check('roles:list')\")\n    public ResponseEntity<RoleDto> findRoleById(@PathVariable Long id){\n        return new ResponseEntity<>(roleService.findById(id), HttpStatus.OK);\n    }\n\n    @ApiOperation(\"导出角色数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('role:list')\")\n    public void exportRole(HttpServletResponse response, RoleQueryCriteria criteria) throws IOException {\n        roleService.download(roleService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(\"返回全部的角色\")\n    @GetMapping(value = \"/all\")\n    @PreAuthorize(\"@el.check('roles:list','user:add','user:edit')\")\n    public ResponseEntity<List<RoleDto>> queryAllRole(){\n        return new ResponseEntity<>(roleService.queryAll(),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"查询角色\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('roles:list')\")\n    public ResponseEntity<PageResult<RoleDto>> queryRole(RoleQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(roleService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"获取用户级别\")\n    @GetMapping(value = \"/level\")\n    public ResponseEntity<Object> getRoleLevel(){\n        return new ResponseEntity<>(Dict.create().set(\"level\", getLevels(null)),HttpStatus.OK);\n    }\n\n    @Log(\"新增角色\")\n    @ApiOperation(\"新增角色\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('roles:add')\")\n    public ResponseEntity<Object> createRole(@Validated @RequestBody Role resources){\n        if (resources.getId() != null) {\n            throw new BadRequestException(\"A new \"+ ENTITY_NAME +\" cannot already have an ID\");\n        }\n        getLevels(resources.getLevel());\n        roleService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改角色\")\n    @ApiOperation(\"修改角色\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('roles:edit')\")\n    public ResponseEntity<Object> updateRole(@Validated(Role.Update.class) @RequestBody Role resources){\n        getLevels(resources.getLevel());\n        roleService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"修改角色菜单\")\n    @ApiOperation(\"修改角色菜单\")\n    @PutMapping(value = \"/menu\")\n    @PreAuthorize(\"@el.check('roles:edit')\")\n    public ResponseEntity<Object> updateRoleMenu(@RequestBody Role resources){\n        RoleDto role = roleService.findById(resources.getId());\n        getLevels(role.getLevel());\n        roleService.updateMenu(resources,role);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除角色\")\n    @ApiOperation(\"删除角色\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('roles:del')\")\n    public ResponseEntity<Object> deleteRole(@RequestBody Set<Long> ids){\n        for (Long id : ids) {\n            RoleDto role = roleService.findById(id);\n            getLevels(role.getLevel());\n        }\n        // 验证是否被用户关联\n        roleService.verification(ids);\n        roleService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    /**\n     * 获取用户的角色级别\n     * @return /\n     */\n    private int getLevels(Integer level){\n        List<Integer> levels = roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList());\n        int min = Collections.min(levels);\n        if(level != null){\n            if(level < min){\n                throw new BadRequestException(\"权限不足，你的角色级别：\" + min + \"，低于操作的角色级别：\" + level);\n            }\n        }\n        return min;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.config.properties.RsaProperties;\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.service.DataService;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.vo.UserPassVo;\nimport me.zhengjie.modules.system.service.DeptService;\nimport me.zhengjie.modules.system.service.RoleService;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport me.zhengjie.modules.system.service.dto.UserQueryCriteria;\nimport me.zhengjie.modules.system.service.VerifyService;\nimport me.zhengjie.utils.*;\nimport me.zhengjie.modules.system.service.UserService;\nimport me.zhengjie.utils.enums.CodeEnum;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.ObjectUtils;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Api(tags = \"系统：用户管理\")\n@RestController\n@RequestMapping(\"/api/users\")\n@RequiredArgsConstructor\npublic class UserController {\n\n    private final PasswordEncoder passwordEncoder;\n    private final UserService userService;\n    private final DataService dataService;\n    private final DeptService deptService;\n    private final RoleService roleService;\n    private final VerifyService verificationCodeService;\n\n    @ApiOperation(\"导出用户数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('user:list')\")\n    public void exportUser(HttpServletResponse response, UserQueryCriteria criteria) throws IOException {\n        userService.download(userService.queryAll(criteria), response);\n    }\n\n    @ApiOperation(\"查询用户\")\n    @GetMapping\n    @PreAuthorize(\"@el.check('user:list')\")\n    public ResponseEntity<PageResult<UserDto>> queryUser(UserQueryCriteria criteria, Pageable pageable){\n        if (!ObjectUtils.isEmpty(criteria.getDeptId())) {\n            criteria.getDeptIds().add(criteria.getDeptId());\n            // 先查找是否存在子节点\n            List<Dept> data = deptService.findByPid(criteria.getDeptId());\n            // 然后把子节点的ID都加入到集合中\n            criteria.getDeptIds().addAll(deptService.getDeptChildren(data));\n        }\n        // 数据权限\n        List<Long> dataScopes = dataService.getDeptIds(userService.findByName(SecurityUtils.getCurrentUsername()));\n        // criteria.getDeptIds() 不为空并且数据权限不为空则取交集\n        if (!CollectionUtils.isEmpty(criteria.getDeptIds()) && !CollectionUtils.isEmpty(dataScopes)){\n            // 取交集\n            criteria.getDeptIds().retainAll(dataScopes);\n            if(!CollectionUtil.isEmpty(criteria.getDeptIds())){\n                return new ResponseEntity<>(userService.queryAll(criteria,pageable),HttpStatus.OK);\n            }\n        } else {\n            // 否则取并集\n            criteria.getDeptIds().addAll(dataScopes);\n            return new ResponseEntity<>(userService.queryAll(criteria,pageable),HttpStatus.OK);\n        }\n        return new ResponseEntity<>(PageUtil.noData(),HttpStatus.OK);\n    }\n\n    @Log(\"新增用户\")\n    @ApiOperation(\"新增用户\")\n    @PostMapping\n    @PreAuthorize(\"@el.check('user:add')\")\n    public ResponseEntity<Object> createUser(@Validated @RequestBody User resources){\n        checkLevel(resources);\n        // 默认密码 123456\n        resources.setPassword(passwordEncoder.encode(\"123456\"));\n        userService.create(resources);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @Log(\"修改用户\")\n    @ApiOperation(\"修改用户\")\n    @PutMapping\n    @PreAuthorize(\"@el.check('user:edit')\")\n    public ResponseEntity<Object> updateUser(@Validated(User.Update.class) @RequestBody User resources) throws Exception {\n        checkLevel(resources);\n        userService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"修改用户：个人中心\")\n    @ApiOperation(\"修改用户：个人中心\")\n    @PutMapping(value = \"center\")\n    public ResponseEntity<Object> centerUser(@Validated(User.Update.class) @RequestBody User resources){\n        if(!resources.getId().equals(SecurityUtils.getCurrentUserId())){\n            throw new BadRequestException(\"不能修改他人资料\");\n        }\n        userService.updateCenter(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除用户\")\n    @ApiOperation(\"删除用户\")\n    @DeleteMapping\n    @PreAuthorize(\"@el.check('user:del')\")\n    public ResponseEntity<Object> deleteUser(@RequestBody Set<Long> ids){\n        for (Long id : ids) {\n            Integer currentLevel =  Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));\n            Integer optLevel =  Collections.min(roleService.findByUsersId(id).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));\n            if (currentLevel > optLevel) {\n                throw new BadRequestException(\"角色权限不足，不能删除：\" + userService.findById(id).getUsername());\n            }\n        }\n        userService.delete(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @ApiOperation(\"修改密码\")\n    @PostMapping(value = \"/updatePass\")\n    public ResponseEntity<Object> updateUserPass(@RequestBody UserPassVo passVo) throws Exception {\n        String oldPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getOldPass());\n        String newPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getNewPass());\n        UserDto user = userService.findByName(SecurityUtils.getCurrentUsername());\n        if(!passwordEncoder.matches(oldPass, user.getPassword())){\n            throw new BadRequestException(\"修改失败，旧密码错误\");\n        }\n        if(passwordEncoder.matches(newPass, user.getPassword())){\n            throw new BadRequestException(\"新密码不能与旧密码相同\");\n        }\n        userService.updatePass(user.getUsername(),passwordEncoder.encode(newPass));\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @ApiOperation(\"重置密码\")\n    @PutMapping(value = \"/resetPwd\")\n    public ResponseEntity<Object> resetPwd(@RequestBody Set<Long> ids) {\n        String pwd = passwordEncoder.encode(\"123456\");\n        userService.resetPwd(ids, pwd);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @ApiOperation(\"修改头像\")\n    @PostMapping(value = \"/updateAvatar\")\n    public ResponseEntity<Object> updateUserAvatar(@RequestParam MultipartFile avatar){\n        return new ResponseEntity<>(userService.updateAvatar(avatar), HttpStatus.OK);\n    }\n\n    @Log(\"修改邮箱\")\n    @ApiOperation(\"修改邮箱\")\n    @PostMapping(value = \"/updateEmail/{code}\")\n    public ResponseEntity<Object> updateUserEmail(@PathVariable String code,@RequestBody User user) throws Exception {\n        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,user.getPassword());\n        UserDto userDto = userService.findByName(SecurityUtils.getCurrentUsername());\n        if(!passwordEncoder.matches(password, userDto.getPassword())){\n            throw new BadRequestException(\"密码错误\");\n        }\n        verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + user.getEmail(), code);\n        userService.updateEmail(userDto.getUsername(),user.getEmail());\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    /**\n     * 如果当前用户的角色级别低于创建用户的角色级别，则抛出权限不足的错误\n     * @param resources /\n     */\n    private void checkLevel(User resources) {\n        Integer currentLevel =  Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));\n        Integer optLevel = roleService.findByRoles(resources.getRoles());\n        if (currentLevel > optLevel) {\n            throw new BadRequestException(\"角色权限不足\");\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/rest/VerifyController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.vo.EmailVo;\nimport me.zhengjie.service.EmailService;\nimport me.zhengjie.modules.system.service.VerifyService;\nimport me.zhengjie.utils.enums.CodeBiEnum;\nimport me.zhengjie.utils.enums.CodeEnum;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\nimport java.util.Objects;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-26\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/code\")\n@Api(tags = \"系统：验证码管理\")\npublic class VerifyController {\n\n    private final VerifyService verificationCodeService;\n    private final EmailService emailService;\n\n    @PostMapping(value = \"/resetEmail\")\n    @ApiOperation(\"重置邮箱，发送验证码\")\n    public ResponseEntity<Object> resetEmail(@RequestParam String email){\n        EmailVo emailVo = verificationCodeService.sendEmail(email, CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey());\n        emailService.send(emailVo,emailService.find());\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @PostMapping(value = \"/email/resetPass\")\n    @ApiOperation(\"重置密码，发送验证码\")\n    public ResponseEntity<Object> resetPass(@RequestParam String email){\n        EmailVo emailVo = verificationCodeService.sendEmail(email, CodeEnum.EMAIL_RESET_PWD_CODE.getKey());\n        emailService.send(emailVo,emailService.find());\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @GetMapping(value = \"/validated\")\n    @ApiOperation(\"验证码验证\")\n    public ResponseEntity<Object> validated(@RequestParam String email, @RequestParam String code, @RequestParam Integer codeBi){\n        CodeBiEnum biEnum = CodeBiEnum.find(codeBi);\n        switch (Objects.requireNonNull(biEnum)){\n            case ONE:\n                verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + email ,code);\n                break;\n            case TWO:\n                verificationCodeService.validated(CodeEnum.EMAIL_RESET_PWD_CODE.getKey() + email ,code);\n                break;\n            default:\n                break;\n        }\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/DataService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport java.util.List;\n\n/**\n * 数据权限服务类\n * @author Zheng Jie\n * @date 2020-05-07\n */\npublic interface DataService {\n\n    /**\n     * 获取数据权限\n     * @param user /\n     * @return /\n     */\n    List<Long> getDeptIds(UserDto user);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/DeptService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.service.dto.DeptDto;\nimport me.zhengjie.modules.system.service.dto.DeptQueryCriteria;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\npublic interface DeptService {\n\n    /**\n     * 查询所有数据\n     * @param criteria 条件\n     * @param isQuery /\n     * @throws Exception /\n     * @return /\n     */\n    List<DeptDto> queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception;\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    DeptDto findById(Long id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(Dept resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Dept resources);\n\n    /**\n     * 删除\n     * @param deptDtos /\n     *\n     */\n    void delete(Set<DeptDto> deptDtos);\n\n    /**\n     * 根据PID查询\n     * @param pid /\n     * @return /\n     */\n    List<Dept> findByPid(long pid);\n\n    /**\n     * 根据角色ID查询\n     * @param id /\n     * @return /\n     */\n    Set<Dept> findByRoleId(Long id);\n\n    /**\n     * 导出数据\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<DeptDto> queryAll, HttpServletResponse response) throws IOException;\n\n    /**\n     * 获取待删除的部门\n     * @param deptList /\n     * @param deptDtos /\n     * @return /\n     */\n    Set<DeptDto> getDeleteDepts(List<Dept> deptList, Set<DeptDto> deptDtos);\n\n    /**\n     * 根据ID获取同级与上级数据\n     * @param deptDto /\n     * @param depts /\n     * @return /\n     */\n    List<DeptDto> getSuperior(DeptDto deptDto, List<Dept> depts);\n\n    /**\n     * 构建树形数据\n     * @param deptDtos /\n     * @return /\n     */\n    Object buildTree(List<DeptDto> deptDtos);\n\n    /**\n     * 获取\n     * @param deptList /\n     * @return /\n     */\n    List<Long> getDeptChildren(List<Dept> deptList);\n\n    /**\n     * 验证是否被角色或用户关联\n     * @param deptDtos /\n     */\n    void verification(Set<DeptDto> deptDtos);\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictDetailService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.system.domain.DictDetail;\nimport me.zhengjie.modules.system.service.dto.DictDetailDto;\nimport me.zhengjie.modules.system.service.dto.DictDetailQueryCriteria;\nimport org.springframework.data.domain.Pageable;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\npublic interface DictDetailService {\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(DictDetail resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(DictDetail resources);\n\n    /**\n     * 删除\n     * @param id /\n     */\n    void delete(Long id);\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<DictDetailDto> queryAll(DictDetailQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 根据字典名称获取字典详情\n     * @param name 字典名称\n     * @return /\n     */\n    List<DictDetailDto> getDictByName(String name);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.system.domain.Dict;\nimport me.zhengjie.modules.system.service.dto.DictDto;\nimport me.zhengjie.modules.system.service.dto.DictQueryCriteria;\nimport org.springframework.data.domain.Pageable;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\npublic interface DictService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<DictDto> queryAll(DictQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param dict /\n     * @return /\n     */\n    List<DictDto> queryAll(DictQueryCriteria dict);\n\n    /**\n     * 创建\n     * @param resources /\n     * @return /\n     */\n    void create(Dict resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Dict resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 导出数据\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<DictDto> queryAll, HttpServletResponse response) throws IOException;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/JobService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.system.domain.Job;\nimport me.zhengjie.modules.system.service.dto.JobDto;\nimport me.zhengjie.modules.system.service.dto.JobQueryCriteria;\nimport org.springframework.data.domain.Pageable;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\npublic interface JobService {\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    JobDto findById(Long id);\n\n    /**\n     * 创建\n     * @param resources /\n     * @return /\n     */\n    void create(Job resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Job resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<JobDto> queryAll(JobQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param criteria /\n     * @return /\n     */\n    List<JobDto> queryAll(JobQueryCriteria criteria);\n\n    /**\n     * 导出数据\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<JobDto> queryAll, HttpServletResponse response) throws IOException;\n\n    /**\n     * 验证是否被用户关联\n     * @param ids /\n     */\n    void verification(Set<Long> ids);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/MenuService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.modules.system.domain.Menu;\nimport me.zhengjie.modules.system.domain.vo.MenuVo;\nimport me.zhengjie.modules.system.service.dto.MenuDto;\nimport me.zhengjie.modules.system.service.dto.MenuQueryCriteria;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-17\n */\npublic interface MenuService {\n\n    /**\n     * 查询全部数据\n     * @param criteria 条件\n     * @param isQuery /\n     * @throws Exception /\n     * @return /\n     */\n    List<MenuDto> queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception;\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    MenuDto findById(long id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(Menu resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Menu resources);\n\n    /**\n     * 获取所有子节点，包含自身ID\n     * @param menuList /\n     * @param menuSet /\n     * @return /\n     */\n    Set<Menu> getChildMenus(List<Menu> menuList, Set<Menu> menuSet);\n\n    /**\n     * 构建菜单树\n     * @param menuDtos 原始数据\n     * @return /\n     */\n    List<MenuDto> buildTree(List<MenuDto> menuDtos);\n\n    /**\n     * 构建菜单树\n     * @param menuDtos /\n     * @return /\n     */\n    List<MenuVo> buildMenus(List<MenuDto> menuDtos);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    Menu findOne(Long id);\n\n    /**\n     * 删除\n     * @param menuSet /\n     */\n    void delete(Set<Menu> menuSet);\n\n    /**\n     * 导出\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<MenuDto> queryAll, HttpServletResponse response) throws IOException;\n\n    /**\n     * 懒加载菜单数据\n     * @param pid /\n     * @return /\n     */\n    List<MenuDto> getMenus(Long pid);\n\n    /**\n     * 根据ID获取同级与上级数据\n     * @param menuDto /\n     * @param objects /\n     * @return /\n     */\n    List<MenuDto> getSuperior(MenuDto menuDto, List<Menu> objects);\n\n    /**\n     * 根据当前用户获取菜单\n     * @param currentUserId /\n     * @return /\n     */\n    List<MenuDto> findByUser(Long currentUserId);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/MonitorService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport java.util.Map;\n\n/**\n * @author Zheng Jie\n * @date 2020-05-02\n */\npublic interface MonitorService {\n\n    /**\n    * 查询数据分页\n    * @return Map<String,Object>\n    */\n    Map<String,Object> getServers();\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.modules.security.service.dto.AuthorityDto;\nimport me.zhengjie.modules.system.domain.Role;\nimport me.zhengjie.modules.system.service.dto.RoleDto;\nimport me.zhengjie.modules.system.service.dto.RoleQueryCriteria;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-03\n */\npublic interface RoleService {\n\n    /**\n     * 查询全部数据\n     * @return /\n     */\n    List<RoleDto> queryAll();\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    RoleDto findById(long id);\n\n    /**\n     * 创建\n     * @param resources /\n     */\n    void create(Role resources);\n\n    /**\n     * 编辑\n     * @param resources /\n     */\n    void update(Role resources);\n\n    /**\n     * 删除\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 根据用户ID查询\n     * @param userId 用户ID\n     * @return /\n     */\n    List<RoleSmallDto> findByUsersId(Long userId);\n\n    /**\n     * 根据角色查询角色级别\n     * @param roles /\n     * @return /\n     */\n    Integer findByRoles(Set<Role> roles);\n\n    /**\n     * 修改绑定的菜单\n     * @param resources /\n     * @param roleDTO /\n     */\n    void updateMenu(Role resources, RoleDto roleDTO);\n\n    /**\n     * 解绑菜单\n     * @param id /\n     */\n    void untiedMenu(Long id);\n\n    /**\n     * 待条件分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<RoleDto> queryAll(RoleQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部\n     * @param criteria 条件\n     * @return /\n     */\n    List<RoleDto> queryAll(RoleQueryCriteria criteria);\n\n    /**\n     * 导出数据\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<RoleDto> queryAll, HttpServletResponse response) throws IOException;\n\n    /**\n     * 获取用户权限信息\n     * @param user 用户信息\n     * @return 权限信息\n     */\n    List<AuthorityDto> buildPermissions(UserDto user);\n\n    /**\n     * 验证是否被用户关联\n     * @param ids /\n     */\n    void verification(Set<Long> ids);\n\n    /**\n     * 根据菜单Id查询\n     * @param menuIds /\n     * @return /\n     */\n    List<Role> findInMenuId(List<Long> menuIds);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/UserService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport me.zhengjie.modules.system.service.dto.UserQueryCriteria;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\npublic interface UserService {\n\n    /**\n     * 根据ID查询\n     * @param id ID\n     * @return /\n     */\n    UserDto findById(long id);\n\n    /**\n     * 新增用户\n     * @param resources /\n     */\n    void create(User resources);\n\n    /**\n     * 编辑用户\n     * @param resources /\n     * @throws Exception /\n     */\n    void update(User resources) throws Exception;\n\n    /**\n     * 删除用户\n     * @param ids /\n     */\n    void delete(Set<Long> ids);\n\n    /**\n     * 根据用户名查询\n     * @param userName /\n     * @return /\n     */\n    UserDto findByName(String userName);\n\n    /**\n     * 根据用户名查询\n     * @param userName /\n     * @return /\n     */\n    UserDto getLoginData(String userName);\n\n    /**\n     * 修改密码\n     * @param username 用户名\n     * @param encryptPassword 密码\n     */\n    void updatePass(String username, String encryptPassword);\n\n    /**\n     * 修改头像\n     * @param file 文件\n     * @return /\n     */\n    Map<String, String> updateAvatar(MultipartFile file);\n\n    /**\n     * 修改邮箱\n     * @param username 用户名\n     * @param email 邮箱\n     */\n    void updateEmail(String username, String email);\n\n    /**\n     * 查询全部\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<UserDto> queryAll(UserQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部不分页\n     * @param criteria 条件\n     * @return /\n     */\n    List<UserDto> queryAll(UserQueryCriteria criteria);\n\n    /**\n     * 导出数据\n     * @param queryAll 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<UserDto> queryAll, HttpServletResponse response) throws IOException;\n\n    /**\n     * 用户自助修改资料\n     * @param resources /\n     */\n    void updateCenter(User resources);\n\n    /**\n     * 重置密码\n     * @param ids 用户id\n     * @param pwd 密码\n     */\n    void resetPwd(Set<Long> ids, String pwd);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/VerifyService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service;\n\nimport me.zhengjie.domain.vo.EmailVo;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-26\n */\npublic interface VerifyService {\n\n    /**\n     * 发送验证码\n     * @param email /\n     * @param key /\n     * @return /\n     */\n    EmailVo sendEmail(String email, String key);\n\n\n    /**\n     * 验证\n     * @param code /\n     * @param key /\n     */\n    void validated(String key, String code);\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@Getter\n@Setter\npublic class DeptDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @ApiModelProperty(value = \"排序\")\n    private Integer deptSort;\n\n    @ApiModelProperty(value = \"子部门\")\n    private List<DeptDto> children;\n\n    @ApiModelProperty(value = \"上级部门\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"子部门数量\", hidden = true)\n    private Integer subCount;\n\n    @ApiModelProperty(value = \"是否有子节点\")\n    public Boolean getHasChildren() {\n        return subCount > 0;\n    }\n\n    @ApiModelProperty(value = \"是否为叶子\")\n    public Boolean getLeaf() {\n        return subCount <= 0;\n    }\n\n    @ApiModelProperty(value = \"部门全名\")\n    public String getLabel() {\n        return name;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        DeptDto deptDto = (DeptDto) o;\n        return Objects.equals(id, deptDto.id) &&\n                Objects.equals(name, deptDto.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, name);\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.DataPermission;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@Data\n@DataPermission(fieldName = \"id\")\npublic class DeptQueryCriteria{\n\n    @ApiModelProperty(value = \"名称\")\n    @Query(type = Query.Type.INNER_LIKE)\n    private String name;\n\n    @Query\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @Query\n    @ApiModelProperty(value = \"上级部门\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"PID空查询\", hidden = true)\n    @Query(type = Query.Type.IS_NULL, propName = \"pid\")\n    private Boolean pidIsNull;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-6-10 16:32:18\n*/\n@Data\npublic class DeptSmallDto implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Getter\n@Setter\npublic class DictDetailDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"字典ID\")\n    private DictSmallDto dict;\n\n    @ApiModelProperty(value = \"字典标签\")\n    private String label;\n\n    @ApiModelProperty(value = \"字典值\")\n    private String value;\n\n    @ApiModelProperty(value = \"排序\")\n    private Integer dictSort;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Data\npublic class DictDetailQueryCriteria {\n\n    @ApiModelProperty(value = \"字典标签\")\n    @Query(type = Query.Type.INNER_LIKE)\n    private String label;\n\n    @ApiModelProperty(value = \"字典名称\")\n    @Query(propName = \"name\",joinName = \"dict\")\n    private String dictName;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Getter\n@Setter\npublic class DictDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"字典详情\")\n    private List<DictDetailDto> dictDetails;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\n\n/**\n * @author Zheng Jie\n * 公共查询类\n */\n@Data\npublic class DictQueryCriteria {\n\n    @ApiModelProperty(value = \"模糊查询\")\n    @Query(blurry = \"name,description\")\n    private String blurry;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictSmallDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Getter\n@Setter\npublic class DictSmallDto implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.NoArgsConstructor;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\n\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\n@Getter\n@Setter\n@NoArgsConstructor\npublic class JobDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"岗位排序\")\n    private Integer jobSort;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    public JobDto(String name, Boolean enabled) {\n        this.name = name;\n        this.enabled = enabled;\n    }\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-6-4 14:49:34\n*/\n@Data\n@NoArgsConstructor\npublic class JobQueryCriteria {\n\n    @ApiModelProperty(value = \"岗位名称\")\n    @Query(type = Query.Type.INNER_LIKE)\n    private String name;\n\n    @Query\n    @ApiModelProperty(value = \"岗位状态\")\n    private Boolean enabled;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-6-10 16:32:18\n*/\n@Data\n@NoArgsConstructor\npublic class JobSmallDto implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-17\n */\n@Getter\n@Setter\npublic class MenuDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"子节点\")\n    private List<MenuDto> children;\n\n    @ApiModelProperty(value = \"类型\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"权限\")\n    private String permission;\n\n    @ApiModelProperty(value = \"菜单标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"排序\")\n    private Integer menuSort;\n\n    @ApiModelProperty(value = \"路径\")\n    private String path;\n\n    @ApiModelProperty(value = \"组件\")\n    private String component;\n\n    @ApiModelProperty(value = \"PID\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"子节点数目\")\n    private Integer subCount;\n\n    @ApiModelProperty(value = \"是否为Iframe\")\n    @JsonProperty(\"iFrame\")\n    private Boolean iFrame;\n\n    @ApiModelProperty(value = \"是否缓存\")\n    private Boolean cache;\n\n    @ApiModelProperty(value = \"是否隐藏\")\n    private Boolean hidden;\n\n    @ApiModelProperty(value = \"组件名称\")\n    private String componentName;\n\n    @ApiModelProperty(value = \"图标\")\n    private String icon;\n\n    @ApiModelProperty(value = \"是否存在子节点\")\n    public Boolean getHasChildren() {\n        return subCount > 0;\n    }\n\n    @ApiModelProperty(value = \"是否叶子节点\")\n    public Boolean getLeaf() {\n        return subCount <= 0;\n    }\n\n    @ApiModelProperty(value = \"标题\")\n    public String getLabel() {\n        return title;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        MenuDto menuDto = (MenuDto) o;\n        return Objects.equals(id, menuDto.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * 公共查询类\n */\n@Data\npublic class MenuQueryCriteria {\n\n    @ApiModelProperty(value = \"模糊查询\")\n    @Query(blurry = \"title,component,permission\")\n    private String blurry;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n\n    @ApiModelProperty(value = \"PID空查询\", hidden = true)\n    @Query(type = Query.Type.IS_NULL, propName = \"pid\")\n    private Boolean pidIsNull;\n\n    @Query\n    @ApiModelProperty(value = \"PID\")\n    private Long pid;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Getter\n@Setter\npublic class RoleDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"菜单\")\n    private Set<MenuDto> menus;\n\n    @ApiModelProperty(value = \"部门\")\n    private Set<DeptDto> depts;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"数据权限\")\n    private String dataScope;\n\n    @ApiModelProperty(value = \"级别\")\n    private Integer level;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        RoleDto roleDto = (RoleDto) o;\n        return Objects.equals(id, roleDto.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\n\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n * @author Zheng Jie\n * 公共查询类\n */\n@Data\npublic class RoleQueryCriteria {\n\n    @ApiModelProperty(value = \"模糊查询\")\n    @Query(blurry = \"name,description\")\n    private String blurry;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.io.Serializable;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Data\npublic class RoleSmallDto implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"级别\")\n    private Integer level;\n\n    @ApiModelProperty(value = \"数据权限\")\n    private String dataScope;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\nimport java.util.Date;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Getter\n@Setter\npublic class UserDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"角色\")\n    private Set<RoleSmallDto> roles;\n\n    @ApiModelProperty(value = \"岗位\")\n    private Set<JobSmallDto> jobs;\n\n    @ApiModelProperty(value = \"部门\")\n    private DeptSmallDto dept;\n\n    @ApiModelProperty(value = \"部门ID\")\n    private Long deptId;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickName;\n\n    @ApiModelProperty(value = \"邮箱\")\n    private String email;\n\n    @ApiModelProperty(value = \"电话\")\n    private String phone;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatarName;\n\n    @ApiModelProperty(value = \"头像路径\")\n    private String avatarPath;\n\n    @ApiModelProperty(value = \"密码\")\n    private String password;\n\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @ApiModelProperty(value = \"管理员\")\n    @JSONField(serialize = false)\n    private Boolean isAdmin = false;\n\n    @ApiModelProperty(value = \"密码重置时间\")\n    private Date pwdResetTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.io.Serializable;\nimport java.sql.Timestamp;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Data\npublic class UserQueryCriteria implements Serializable {\n\n    @Query\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"部门ID集合\")\n    @Query(propName = \"id\", type = Query.Type.IN, joinName = \"dept\")\n    private Set<Long> deptIds = new HashSet<>();\n\n    @ApiModelProperty(value = \"模糊查询\")\n    @Query(blurry = \"email,username,nickName\")\n    private String blurry;\n\n    @Query\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean enabled;\n\n    @ApiModelProperty(value = \"部门ID\")\n    private Long deptId;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DataServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.service.DataService;\nimport me.zhengjie.modules.system.service.DeptService;\nimport me.zhengjie.modules.system.service.RoleService;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport me.zhengjie.utils.CacheKey;\nimport me.zhengjie.utils.RedisUtils;\nimport me.zhengjie.utils.enums.DataScopeEnum;\nimport org.springframework.stereotype.Service;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author Zheng Jie\n * @description 数据权限服务实现\n * @date 2020-05-07\n **/\n@Service\n@RequiredArgsConstructor\npublic class DataServiceImpl implements DataService {\n\n    private final RedisUtils redisUtils;\n    private final RoleService roleService;\n    private final DeptService deptService;\n\n    /**\n     * 用户角色和用户部门改变时需清理缓存\n     * @param user /\n     * @return /\n     */\n    @Override\n    public List<Long> getDeptIds(UserDto user) {\n        String key = CacheKey.DATA_USER + user.getId();\n        List<Long> ids = redisUtils.getList(key, Long.class);\n        if (CollUtil.isEmpty(ids)) {\n            // 用于存储部门id\n            Set<Long> deptIds = new HashSet<>();\n            // 查询用户角色\n            List<RoleSmallDto> roleSet = roleService.findByUsersId(user.getId());\n            // 获取对应的部门ID\n            for (RoleSmallDto role : roleSet) {\n                DataScopeEnum dataScopeEnum = DataScopeEnum.find(role.getDataScope());\n                switch (Objects.requireNonNull(dataScopeEnum)) {\n                    case THIS_LEVEL:\n                        deptIds.add(user.getDept().getId());\n                        break;\n                    case CUSTOMIZE:\n                        deptIds.addAll(getCustomize(deptIds, role));\n                        break;\n                    default:\n                        return new ArrayList<>();\n                }\n            }\n            ids = new ArrayList<>(deptIds);\n            redisUtils.set(key, ids, 1, TimeUnit.DAYS);\n        }\n        return new ArrayList<>(ids);\n    }\n\n    /**\n     * 获取自定义的数据权限\n     * @param deptIds 部门ID\n     * @param role 角色\n     * @return 数据权限ID\n     */\n    public Set<Long> getCustomize(Set<Long> deptIds, RoleSmallDto role){\n        Set<Dept> depts = deptService.findByRoleId(role.getId());\n        for (Dept dept : depts) {\n            deptIds.add(dept.getId());\n            List<Dept> deptChildren = deptService.findByPid(dept.getId());\n            if (CollUtil.isNotEmpty(deptChildren)) {\n                deptIds.addAll(deptService.getDeptChildren(deptChildren));\n            }\n        }\n        return deptIds;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DeptServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.modules.system.repository.RoleRepository;\nimport me.zhengjie.modules.system.repository.UserRepository;\nimport me.zhengjie.modules.system.service.dto.DeptDto;\nimport me.zhengjie.modules.system.service.dto.DeptQueryCriteria;\nimport me.zhengjie.utils.*;\nimport me.zhengjie.modules.system.repository.DeptRepository;\nimport me.zhengjie.modules.system.service.DeptService;\nimport me.zhengjie.modules.system.service.mapstruct.DeptMapper;\nimport me.zhengjie.utils.enums.DataScopeEnum;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@Service\n@RequiredArgsConstructor\npublic class DeptServiceImpl implements DeptService {\n\n    private final DeptRepository deptRepository;\n    private final DeptMapper deptMapper;\n    private final UserRepository userRepository;\n    private final RedisUtils redisUtils;\n    private final RoleRepository roleRepository;\n\n    @Override\n    public List<DeptDto> queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception {\n        Sort sort = Sort.by(Sort.Direction.ASC, \"deptSort\");\n        String dataScopeType = SecurityUtils.getDataScopeType();\n        if (isQuery) {\n            if(dataScopeType.equals(DataScopeEnum.ALL.getValue())){\n                criteria.setPidIsNull(true);\n            }\n            List<Field> fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>());\n            List<String> fieldNames = new ArrayList<String>(){{ add(\"pidIsNull\");add(\"enabled\");}};\n            for (Field field : fields) {\n                //设置对象的访问权限，保证对private的属性的访问\n                field.setAccessible(true);\n                Object val = field.get(criteria);\n                if(fieldNames.contains(field.getName())){\n                    continue;\n                }\n                if (ObjectUtil.isNotNull(val)) {\n                    criteria.setPidIsNull(null);\n                    break;\n                }\n            }\n        }\n        List<DeptDto> list = deptMapper.toDto(deptRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort));\n        // 如果为空，就代表为自定义权限或者本级权限，就需要去重，不理解可以注释掉，看查询结果\n        if(StringUtils.isBlank(dataScopeType)){\n            return deduplication(list);\n        }\n        return list;\n    }\n\n    @Override\n    public DeptDto findById(Long id) {\n        String key = CacheKey.DEPT_ID + id;\n        Dept dept = redisUtils.get(key, Dept.class);\n        if(dept == null){\n            dept = deptRepository.findById(id).orElseGet(Dept::new);\n            ValidationUtil.isNull(dept.getId(),\"Dept\",\"id\",id);\n            redisUtils.set(key, dept, 1, TimeUnit.DAYS);\n        }\n        return deptMapper.toDto(dept);\n    }\n\n    @Override\n    public List<Dept> findByPid(long pid) {\n        return deptRepository.findByPid(pid);\n    }\n\n    @Override\n    public Set<Dept> findByRoleId(Long id) {\n        return deptRepository.findByRoleId(id);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(Dept resources) {\n        deptRepository.save(resources);\n        // 计算子节点数目\n        resources.setSubCount(0);\n        // 清理缓存\n        updateSubCnt(resources.getPid());\n        // 清理自定义角色权限的datascope缓存\n        delCaches(resources.getPid());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(Dept resources) {\n        // 旧的部门\n        Long oldPid = findById(resources.getId()).getPid();\n        Long newPid = resources.getPid();\n        if(resources.getPid() != null && resources.getId().equals(resources.getPid())) {\n            throw new BadRequestException(\"上级不能为自己\");\n        }\n        Dept dept = deptRepository.findById(resources.getId()).orElseGet(Dept::new);\n        ValidationUtil.isNull( dept.getId(),\"Dept\",\"id\",resources.getId());\n        resources.setId(dept.getId());\n        deptRepository.save(resources);\n        // 更新父节点中子节点数目\n        updateSubCnt(oldPid);\n        updateSubCnt(newPid);\n        // 清理缓存\n        delCaches(resources.getId());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<DeptDto> deptDtos) {\n        for (DeptDto deptDto : deptDtos) {\n            // 清理缓存\n            delCaches(deptDto.getId());\n            deptRepository.deleteById(deptDto.getId());\n            updateSubCnt(deptDto.getPid());\n        }\n    }\n\n    @Override\n    public void download(List<DeptDto> deptDtos, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (DeptDto deptDTO : deptDtos) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"部门名称\", deptDTO.getName());\n            map.put(\"部门状态\", deptDTO.getEnabled() ? \"启用\" : \"停用\");\n            map.put(\"创建日期\", deptDTO.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    @Override\n    public Set<DeptDto> getDeleteDepts(List<Dept> menuList, Set<DeptDto> deptDtos) {\n        for (Dept dept : menuList) {\n            deptDtos.add(deptMapper.toDto(dept));\n            List<Dept> depts = deptRepository.findByPid(dept.getId());\n            if(CollUtil.isNotEmpty(depts)){\n                getDeleteDepts(depts, deptDtos);\n            }\n        }\n        return deptDtos;\n    }\n\n    @Override\n    public List<Long> getDeptChildren(List<Dept> deptList) {\n        List<Long> list = new ArrayList<>();\n        deptList.forEach(dept -> {\n                    if (dept!=null && dept.getEnabled()) {\n                        List<Dept> depts = deptRepository.findByPid(dept.getId());\n                        if (CollUtil.isNotEmpty(depts)) {\n                            list.addAll(getDeptChildren(depts));\n                        }\n                        list.add(dept.getId());\n                    }\n                }\n        );\n        return list;\n    }\n\n    @Override\n    public List<DeptDto> getSuperior(DeptDto deptDto, List<Dept> depts) {\n        if(deptDto.getPid() == null){\n            depts.addAll(deptRepository.findByPidIsNull());\n            return deptMapper.toDto(depts);\n        }\n        depts.addAll(deptRepository.findByPid(deptDto.getPid()));\n        return getSuperior(findById(deptDto.getPid()), depts);\n    }\n\n    @Override\n    public Object buildTree(List<DeptDto> deptDtos) {\n        Set<DeptDto> trees = new LinkedHashSet<>();\n        Set<DeptDto> depts= new LinkedHashSet<>();\n        List<String> deptNames = deptDtos.stream().map(DeptDto::getName).collect(Collectors.toList());\n        boolean isChild;\n        for (DeptDto deptDTO : deptDtos) {\n            isChild = false;\n            if (deptDTO.getPid() == null) {\n                trees.add(deptDTO);\n            }\n            for (DeptDto it : deptDtos) {\n                if (it.getPid() != null && deptDTO.getId().equals(it.getPid())) {\n                    isChild = true;\n                    if (deptDTO.getChildren() == null) {\n                        deptDTO.setChildren(new ArrayList<>());\n                    }\n                    deptDTO.getChildren().add(it);\n                }\n            }\n            if(isChild) {\n                depts.add(deptDTO);\n            } else if(deptDTO.getPid() != null &&  !deptNames.contains(findById(deptDTO.getPid()).getName())) {\n                depts.add(deptDTO);\n            }\n        }\n\n        if (CollectionUtil.isEmpty(trees)) {\n            trees = depts;\n        }\n        Map<String,Object> map = new HashMap<>(2);\n        map.put(\"totalElements\",deptDtos.size());\n        map.put(\"content\",CollectionUtil.isEmpty(trees)? deptDtos :trees);\n        return map;\n    }\n\n    @Override\n    public void verification(Set<DeptDto> deptDtos) {\n        Set<Long> deptIds = deptDtos.stream().map(DeptDto::getId).collect(Collectors.toSet());\n        if(userRepository.countByDepts(deptIds) > 0){\n            throw new BadRequestException(\"所选部门存在用户关联，请解除后再试！\");\n        }\n        if(roleRepository.countByDepts(deptIds) > 0){\n            throw new BadRequestException(\"所选部门存在角色关联，请解除后再试！\");\n        }\n    }\n\n    private void updateSubCnt(Long deptId){\n        if(deptId != null){\n            int count = deptRepository.countByPid(deptId);\n            deptRepository.updateSubCntById(count, deptId);\n        }\n    }\n\n    private List<DeptDto> deduplication(List<DeptDto> list) {\n        List<DeptDto> deptDtos = new ArrayList<>();\n        for (DeptDto deptDto : list) {\n            boolean flag = true;\n            for (DeptDto dto : list) {\n                if (dto.getId().equals(deptDto.getPid())) {\n                    flag = false;\n                    break;\n                }\n            }\n            if (flag){\n                deptDtos.add(deptDto);\n            }\n        }\n        return deptDtos;\n    }\n\n    /**\n     * 清理缓存\n     * @param id /\n     */\n    public void delCaches(Long id){\n        List<User> users = userRepository.findByRoleDeptId(id);\n        // 删除数据权限\n        redisUtils.delByKeys(CacheKey.DATA_USER, users.stream().map(User::getId).collect(Collectors.toSet()));\n        redisUtils.del(CacheKey.DEPT_ID + id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictDetailServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.system.domain.Dict;\nimport me.zhengjie.modules.system.domain.DictDetail;\nimport me.zhengjie.modules.system.repository.DictRepository;\nimport me.zhengjie.modules.system.service.dto.DictDetailQueryCriteria;\nimport me.zhengjie.utils.*;\nimport me.zhengjie.modules.system.repository.DictDetailRepository;\nimport me.zhengjie.modules.system.service.DictDetailService;\nimport me.zhengjie.modules.system.service.dto.DictDetailDto;\nimport me.zhengjie.modules.system.service.mapstruct.DictDetailMapper;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Service\n@RequiredArgsConstructor\npublic class DictDetailServiceImpl implements DictDetailService {\n\n    private final DictRepository dictRepository;\n    private final DictDetailRepository dictDetailRepository;\n    private final DictDetailMapper dictDetailMapper;\n    private final RedisUtils redisUtils;\n\n    @Override\n    public PageResult<DictDetailDto> queryAll(DictDetailQueryCriteria criteria, Pageable pageable) {\n        Page<DictDetail> page = dictDetailRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(dictDetailMapper::toDto));\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(DictDetail resources) {\n        dictDetailRepository.save(resources);\n        // 清理缓存\n        delCaches(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(DictDetail resources) {\n        DictDetail dictDetail = dictDetailRepository.findById(resources.getId()).orElseGet(DictDetail::new);\n        ValidationUtil.isNull( dictDetail.getId(),\"DictDetail\",\"id\",resources.getId());\n        resources.setId(dictDetail.getId());\n        dictDetailRepository.save(resources);\n        // 清理缓存\n        delCaches(resources);\n    }\n\n    @Override\n    public List<DictDetailDto> getDictByName(String name) {\n        String key = CacheKey.DICT_NAME + name;\n        List<DictDetail> dictDetails = redisUtils.getList(key, DictDetail.class);\n        if(CollUtil.isEmpty(dictDetails)){\n            dictDetails = dictDetailRepository.findByDictName(name);\n            redisUtils.set(key, dictDetails, 1 , TimeUnit.DAYS);\n        }\n        return dictDetailMapper.toDto(dictDetails);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Long id) {\n        DictDetail dictDetail = dictDetailRepository.findById(id).orElseGet(DictDetail::new);\n        // 清理缓存\n        delCaches(dictDetail);\n        dictDetailRepository.deleteById(id);\n    }\n\n    public void delCaches(DictDetail dictDetail){\n        Dict dict = dictRepository.findById(dictDetail.getDict().getId()).orElseGet(Dict::new);\n        redisUtils.del(CacheKey.DICT_NAME + dict.getName());\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.modules.system.domain.Dict;\nimport me.zhengjie.modules.system.service.dto.DictDetailDto;\nimport me.zhengjie.modules.system.service.dto.DictQueryCriteria;\nimport me.zhengjie.utils.*;\nimport me.zhengjie.modules.system.repository.DictRepository;\nimport me.zhengjie.modules.system.service.DictService;\nimport me.zhengjie.modules.system.service.dto.DictDto;\nimport me.zhengjie.modules.system.service.mapstruct.DictMapper;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Service\n@RequiredArgsConstructor\npublic class DictServiceImpl implements DictService {\n\n    private final DictRepository dictRepository;\n    private final DictMapper dictMapper;\n    private final RedisUtils redisUtils;\n\n    @Override\n    public PageResult<DictDto> queryAll(DictQueryCriteria dict, Pageable pageable){\n        Page<Dict> page = dictRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, dict, cb), pageable);\n        return PageUtil.toPage(page.map(dictMapper::toDto));\n    }\n\n    @Override\n    public List<DictDto> queryAll(DictQueryCriteria dict) {\n        List<Dict> list = dictRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, dict, cb));\n        return dictMapper.toDto(list);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(Dict resources) {\n        dictRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(Dict resources) {\n        // 清理缓存\n        delCaches(resources);\n        Dict dict = dictRepository.findById(resources.getId()).orElseGet(Dict::new);\n        ValidationUtil.isNull( dict.getId(),\"Dict\",\"id\",resources.getId());\n        dict.setName(resources.getName());\n        dict.setDescription(resources.getDescription());\n        dictRepository.save(dict);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        // 清理缓存\n        List<Dict> dicts = dictRepository.findByIdIn(ids);\n        for (Dict dict : dicts) {\n            delCaches(dict);\n        }\n        dictRepository.deleteByIdIn(ids);\n    }\n\n    @Override\n    public void download(List<DictDto> dictDtos, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (DictDto dictDTO : dictDtos) {\n            if(CollectionUtil.isNotEmpty(dictDTO.getDictDetails())){\n                for (DictDetailDto dictDetail : dictDTO.getDictDetails()) {\n                    Map<String,Object> map = new LinkedHashMap<>();\n                    map.put(\"字典名称\", dictDTO.getName());\n                    map.put(\"字典描述\", dictDTO.getDescription());\n                    map.put(\"字典标签\", dictDetail.getLabel());\n                    map.put(\"字典值\", dictDetail.getValue());\n                    map.put(\"创建日期\", dictDetail.getCreateTime());\n                    list.add(map);\n                }\n            } else {\n                Map<String,Object> map = new LinkedHashMap<>();\n                map.put(\"字典名称\", dictDTO.getName());\n                map.put(\"字典描述\", dictDTO.getDescription());\n                map.put(\"字典标签\", null);\n                map.put(\"字典值\", null);\n                map.put(\"创建日期\", dictDTO.getCreateTime());\n                list.add(map);\n            }\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    public void delCaches(Dict dict){\n        redisUtils.del(CacheKey.DICT_NAME + dict.getName());\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/JobServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.exception.EntityExistException;\nimport me.zhengjie.modules.system.domain.Job;\nimport me.zhengjie.modules.system.repository.UserRepository;\nimport me.zhengjie.modules.system.service.dto.JobQueryCriteria;\nimport me.zhengjie.utils.*;\nimport me.zhengjie.modules.system.repository.JobRepository;\nimport me.zhengjie.modules.system.service.JobService;\nimport me.zhengjie.modules.system.service.dto.JobDto;\nimport me.zhengjie.modules.system.service.mapstruct.JobMapper;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\n@Service\n@RequiredArgsConstructor\npublic class JobServiceImpl implements JobService {\n\n    private final JobRepository jobRepository;\n    private final JobMapper jobMapper;\n    private final RedisUtils redisUtils;\n    private final UserRepository userRepository;\n\n    @Override\n    public PageResult<JobDto> queryAll(JobQueryCriteria criteria, Pageable pageable) {\n        Page<Job> page = jobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(jobMapper::toDto).getContent(),page.getTotalElements());\n    }\n\n    @Override\n    public List<JobDto> queryAll(JobQueryCriteria criteria) {\n        List<Job> list = jobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));\n        return jobMapper.toDto(list);\n    }\n\n    @Override\n    public JobDto findById(Long id) {\n        String key = CacheKey.JOB_ID + id;\n        Job job = redisUtils.get(key, Job.class);\n        if(job == null){\n            job = jobRepository.findById(id).orElseGet(Job::new);\n            ValidationUtil.isNull(job.getId(),\"Job\",\"id\",id);\n            redisUtils.set(key, job, 1, TimeUnit.DAYS);\n        }\n        return jobMapper.toDto(job);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(Job resources) {\n        Job job = jobRepository.findByName(resources.getName());\n        if(job != null){\n            throw new EntityExistException(Job.class,\"name\",resources.getName());\n        }\n        jobRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(Job resources) {\n        Job job = jobRepository.findById(resources.getId()).orElseGet(Job::new);\n        Job old = jobRepository.findByName(resources.getName());\n        if(old != null && !old.getId().equals(resources.getId())){\n            throw new EntityExistException(Job.class,\"name\",resources.getName());\n        }\n        ValidationUtil.isNull( job.getId(),\"Job\",\"id\",resources.getId());\n        resources.setId(job.getId());\n        jobRepository.save(resources);\n        // 删除缓存\n        delCaches(resources.getId());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        jobRepository.deleteAllByIdIn(ids);\n        // 删除缓存\n        redisUtils.delByKeys(CacheKey.JOB_ID, ids);\n    }\n\n    @Override\n    public void download(List<JobDto> jobDtos, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (JobDto jobDTO : jobDtos) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"岗位名称\", jobDTO.getName());\n            map.put(\"岗位状态\", jobDTO.getEnabled() ? \"启用\" : \"停用\");\n            map.put(\"创建日期\", jobDTO.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    @Override\n    public void verification(Set<Long> ids) {\n        if(userRepository.countByJobs(ids) > 0){\n            throw new BadRequestException(\"所选的岗位中存在用户关联，请解除关联再试！\");\n        }\n    }\n\n    /**\n     * 删除缓存\n     * @param id /\n     */\n    public void delCaches(Long id){\n        redisUtils.del(CacheKey.JOB_ID + id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.modules.system.domain.Menu;\nimport me.zhengjie.modules.system.domain.Role;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.modules.system.domain.vo.MenuMetaVo;\nimport me.zhengjie.modules.system.domain.vo.MenuVo;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.exception.EntityExistException;\nimport me.zhengjie.modules.system.repository.MenuRepository;\nimport me.zhengjie.modules.system.repository.UserRepository;\nimport me.zhengjie.modules.system.service.MenuService;\nimport me.zhengjie.modules.system.service.RoleService;\nimport me.zhengjie.modules.system.service.dto.MenuDto;\nimport me.zhengjie.modules.system.service.dto.MenuQueryCriteria;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport me.zhengjie.modules.system.service.mapstruct.MenuMapper;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n */\n@Service\n@RequiredArgsConstructor\npublic class MenuServiceImpl implements MenuService {\n\n    private final MenuRepository menuRepository;\n    private final UserRepository userRepository;\n    private final MenuMapper menuMapper;\n    private final RoleService roleService;\n    private final RedisUtils redisUtils;\n\n    private static final String HTTP_PRE = \"http://\";\n    private static final String HTTPS_PRE = \"https://\";\n    private static final String YES_STR = \"是\";\n    private static final String NO_STR = \"否\";\n    private static final String BAD_REQUEST = \"外链必须以http://或者https://开头\";\n    \n    @Override\n    public List<MenuDto> queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception {\n        Sort sort = Sort.by(Sort.Direction.ASC, \"menuSort\");\n        if(Boolean.TRUE.equals(isQuery)){\n            criteria.setPidIsNull(true);\n            List<Field> fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>());\n            for (Field field : fields) {\n                //设置对象的访问权限，保证对private的属性的访问\n                field.setAccessible(true);\n                Object val = field.get(criteria);\n                if(\"pidIsNull\".equals(field.getName())){\n                    continue;\n                }\n                if (ObjectUtil.isNotNull(val)) {\n                    criteria.setPidIsNull(null);\n                    break;\n                }\n            }\n        }\n        return menuMapper.toDto(menuRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort));\n    }\n\n    @Override\n    public MenuDto findById(long id) {\n        String key = CacheKey.MENU_ID + id;\n        Menu menu = redisUtils.get(key, Menu.class);\n        if(menu == null){\n            menu = menuRepository.findById(id).orElseGet(Menu::new);\n            ValidationUtil.isNull(menu.getId(),\"Menu\",\"id\",id);\n            redisUtils.set(key, menu, 1, TimeUnit.DAYS);\n        }\n        return menuMapper.toDto(menu);\n    }\n\n    /**\n     * 用户角色改变时需清理缓存\n     * @param currentUserId /\n     * @return /\n     */\n    @Override\n    public List<MenuDto> findByUser(Long currentUserId) {\n        String key = CacheKey.MENU_USER + currentUserId;\n        List<Menu> menus = redisUtils.getList(key, Menu.class);\n        if (CollUtil.isEmpty(menus)){\n            List<RoleSmallDto> roles = roleService.findByUsersId(currentUserId);\n            Set<Long> roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet());\n            LinkedHashSet<Menu> data = menuRepository.findByRoleIdsAndTypeNot(roleIds, 2);\n            menus = new ArrayList<>(data);\n            redisUtils.set(key, menus, 1, TimeUnit.DAYS);\n        }\n        return menus.stream().map(menuMapper::toDto).collect(Collectors.toList());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(Menu resources) {\n        if(menuRepository.findByTitle(resources.getTitle()) != null){\n            throw new EntityExistException(Menu.class,\"title\",resources.getTitle());\n        }\n        if(StringUtils.isNotBlank(resources.getComponentName())){\n            if(menuRepository.findByComponentName(resources.getComponentName()) != null){\n                throw new EntityExistException(Menu.class,\"componentName\",resources.getComponentName());\n            }\n        }\n        if (Long.valueOf(0L).equals(resources.getPid())) {\n            resources.setPid(null);\n        }\n        if(resources.getIFrame()){\n            if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) {\n                throw new BadRequestException(BAD_REQUEST);\n            }\n        }\n        menuRepository.save(resources);\n        // 计算子节点数目\n        resources.setSubCount(0);\n        // 更新父节点菜单数目\n        updateSubCnt(resources.getPid());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(Menu resources) {\n        if(resources.getId().equals(resources.getPid())) {\n            throw new BadRequestException(\"上级不能为自己\");\n        }\n        Menu menu = menuRepository.findById(resources.getId()).orElseGet(Menu::new);\n        ValidationUtil.isNull(menu.getId(),\"Permission\",\"id\",resources.getId());\n\n        if(resources.getIFrame()){\n            if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) {\n                throw new BadRequestException(BAD_REQUEST);\n            }\n        }\n        Menu menu1 = menuRepository.findByTitle(resources.getTitle());\n\n        if(menu1 != null && !menu1.getId().equals(menu.getId())){\n            throw new EntityExistException(Menu.class,\"title\",resources.getTitle());\n        }\n\n        if(resources.getPid().equals(0L)){\n            resources.setPid(null);\n        }\n\n        // 记录的父节点ID\n        Long oldPid = menu.getPid();\n        Long newPid = resources.getPid();\n\n        if(StringUtils.isNotBlank(resources.getComponentName())){\n            menu1 = menuRepository.findByComponentName(resources.getComponentName());\n            if(menu1 != null && !menu1.getId().equals(menu.getId())){\n                throw new EntityExistException(Menu.class,\"componentName\",resources.getComponentName());\n            }\n        }\n        menu.setTitle(resources.getTitle());\n        menu.setComponent(resources.getComponent());\n        menu.setPath(resources.getPath());\n        menu.setIcon(resources.getIcon());\n        menu.setIFrame(resources.getIFrame());\n        menu.setPid(resources.getPid());\n        menu.setMenuSort(resources.getMenuSort());\n        menu.setCache(resources.getCache());\n        menu.setHidden(resources.getHidden());\n        menu.setComponentName(resources.getComponentName());\n        menu.setPermission(resources.getPermission());\n        menu.setType(resources.getType());\n        menuRepository.save(menu);\n        // 计算父级菜单节点数目\n        updateSubCnt(oldPid);\n        updateSubCnt(newPid);\n        // 清理缓存\n        delCaches(resources.getId());\n    }\n\n    @Override\n    public Set<Menu> getChildMenus(List<Menu> menuList, Set<Menu> menuSet) {\n        for (Menu menu : menuList) {\n            menuSet.add(menu);\n            List<Menu> menus = menuRepository.findByPidOrderByMenuSort(menu.getId());\n            if(CollUtil.isNotEmpty(menus)){\n                getChildMenus(menus, menuSet);\n            }\n        }\n        return menuSet;\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Menu> menuSet) {\n        for (Menu menu : menuSet) {\n            // 清理缓存\n            delCaches(menu.getId());\n            roleService.untiedMenu(menu.getId());\n            menuRepository.deleteById(menu.getId());\n            updateSubCnt(menu.getPid());\n        }\n    }\n\n    @Override\n    public List<MenuDto> getMenus(Long pid) {\n        List<Menu> menus;\n        if(pid != null && !pid.equals(0L)){\n            menus = menuRepository.findByPidOrderByMenuSort(pid);\n        } else {\n            menus = menuRepository.findByPidIsNullOrderByMenuSort();\n        }\n        return menuMapper.toDto(menus);\n    }\n\n    @Override\n    public List<MenuDto> getSuperior(MenuDto menuDto, List<Menu> menus) {\n        if(menuDto.getPid() == null){\n            menus.addAll(menuRepository.findByPidIsNullOrderByMenuSort());\n            return menuMapper.toDto(menus);\n        }\n        menus.addAll(menuRepository.findByPidOrderByMenuSort(menuDto.getPid()));\n        return getSuperior(findById(menuDto.getPid()), menus);\n    }\n\n    @Override\n    public List<MenuDto> buildTree(List<MenuDto> menuDtos) {\n        List<MenuDto> trees = new ArrayList<>();\n        Set<Long> ids = new HashSet<>();\n        for (MenuDto menuDTO : menuDtos) {\n            if (menuDTO.getPid() == null) {\n                trees.add(menuDTO);\n            }\n            for (MenuDto it : menuDtos) {\n                if (menuDTO.getId().equals(it.getPid())) {\n                    if (menuDTO.getChildren() == null) {\n                        menuDTO.setChildren(new ArrayList<>());\n                    }\n                    menuDTO.getChildren().add(it);\n                    ids.add(it.getId());\n                }\n            }\n        }\n        if(trees.isEmpty()){\n            trees = menuDtos.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList());\n        }\n        return trees;\n    }\n\n    @Override\n    public List<MenuVo> buildMenus(List<MenuDto> menuDtos) {\n        List<MenuVo> list = new LinkedList<>();\n        menuDtos.forEach(menuDTO -> {\n                    if (menuDTO!=null){\n                        List<MenuDto> menuDtoList = menuDTO.getChildren();\n                        MenuVo menuVo = new MenuVo();\n                        menuVo.setName(ObjectUtil.isNotEmpty(menuDTO.getComponentName())  ? menuDTO.getComponentName() : menuDTO.getTitle());\n                        // 一级目录需要加斜杠，不然会报警告\n                        menuVo.setPath(menuDTO.getPid() == null ? \"/\" + menuDTO.getPath() :menuDTO.getPath());\n                        menuVo.setHidden(menuDTO.getHidden());\n                        // 如果不是外链\n                        if(!menuDTO.getIFrame()){\n                            if(menuDTO.getPid() == null){\n                                menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?\"Layout\":menuDTO.getComponent());\n                                // 如果不是一级菜单，并且菜单类型为目录，则代表是多级菜单\n                            }else if(menuDTO.getType() == 0){\n                                menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?\"ParentView\":menuDTO.getComponent());\n                            }else if(StringUtils.isNoneBlank(menuDTO.getComponent())){\n                                menuVo.setComponent(menuDTO.getComponent());\n                            }\n                        }\n                        menuVo.setMeta(new MenuMetaVo(menuDTO.getTitle(),menuDTO.getIcon(),!menuDTO.getCache()));\n                        if(CollectionUtil.isNotEmpty(menuDtoList)){\n                            menuVo.setAlwaysShow(true);\n                            menuVo.setRedirect(\"noredirect\");\n                            menuVo.setChildren(buildMenus(menuDtoList));\n                            // 处理是一级菜单并且没有子菜单的情况\n                        } else if(menuDTO.getPid() == null){\n                            MenuVo menuVo1 = getMenuVo(menuDTO, menuVo);\n                            menuVo.setName(null);\n                            menuVo.setMeta(null);\n                            menuVo.setComponent(\"Layout\");\n                            List<MenuVo> list1 = new ArrayList<>();\n                            list1.add(menuVo1);\n                            menuVo.setChildren(list1);\n                        }\n                        list.add(menuVo);\n                    }\n                }\n        );\n        return list;\n    }\n\n    @Override\n    public Menu findOne(Long id) {\n        Menu menu = menuRepository.findById(id).orElseGet(Menu::new);\n        ValidationUtil.isNull(menu.getId(),\"Menu\",\"id\",id);\n        return menu;\n    }\n\n    @Override\n    public void download(List<MenuDto> menuDtos, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (MenuDto menuDTO : menuDtos) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"菜单标题\", menuDTO.getTitle());\n            map.put(\"菜单类型\", menuDTO.getType() == null ? \"目录\" : menuDTO.getType() == 1 ? \"菜单\" : \"按钮\");\n            map.put(\"权限标识\", menuDTO.getPermission());\n            map.put(\"外链菜单\", menuDTO.getIFrame() ? YES_STR : NO_STR);\n            map.put(\"菜单可见\", menuDTO.getHidden() ? NO_STR : YES_STR);\n            map.put(\"是否缓存\", menuDTO.getCache() ? YES_STR : NO_STR);\n            map.put(\"创建日期\", menuDTO.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    private void updateSubCnt(Long menuId){\n        if(menuId != null){\n            int count = menuRepository.countByPid(menuId);\n            menuRepository.updateSubCntById(count, menuId);\n        }\n    }\n\n    /**\n     * 清理缓存\n     * @param id 菜单ID\n     */\n    public void delCaches(Long id){\n        List<User> users = userRepository.findByMenuId(id);\n        redisUtils.del(CacheKey.MENU_ID + id);\n        redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(User::getId).collect(Collectors.toSet()));\n        // 清除 Role 缓存\n        List<Role> roles = roleService.findInMenuId(new ArrayList<Long>(){{\n            add(id);\n        }});\n        redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(Role::getId).collect(Collectors.toSet()));\n    }\n\n    /**\n     * 构建前端路由\n     * @param menuDTO /\n     * @param menuVo /\n     * @return /\n     */\n    private static MenuVo getMenuVo(MenuDto menuDTO, MenuVo menuVo) {\n        MenuVo menuVo1 = new MenuVo();\n        menuVo1.setMeta(menuVo.getMeta());\n        // 非外链\n        if(!menuDTO.getIFrame()){\n            menuVo1.setPath(\"index\");\n            menuVo1.setName(menuVo.getName());\n            menuVo1.setComponent(menuVo.getComponent());\n        } else {\n            menuVo1.setPath(menuDTO.getPath());\n        }\n        return menuVo1;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MonitorServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.date.BetweenFormatter.Level;\nimport cn.hutool.core.date.DateUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.modules.system.service.MonitorService;\nimport me.zhengjie.utils.ElConstant;\nimport me.zhengjie.utils.FileUtil;\nimport me.zhengjie.utils.StringUtils;\nimport org.springframework.stereotype.Service;\nimport oshi.SystemInfo;\nimport oshi.hardware.*;\nimport oshi.software.os.FileSystem;\nimport oshi.software.os.OSFileStore;\nimport oshi.software.os.OperatingSystem;\nimport oshi.util.FormatUtil;\nimport oshi.util.Util;\nimport java.lang.management.ManagementFactory;\nimport java.text.DecimalFormat;\nimport java.util.*;\n\n/**\n* @author Zheng Jie\n* @date 2020-05-02\n*/\n@Slf4j\n@Service\npublic class MonitorServiceImpl implements MonitorService {\n\n    private final DecimalFormat df = new DecimalFormat(\"0.00\");\n\n    @Override\n    public Map<String,Object> getServers(){\n        Map<String, Object> resultMap = new LinkedHashMap<>(8);\n        try {\n            SystemInfo si = new SystemInfo();\n            OperatingSystem os = si.getOperatingSystem();\n            HardwareAbstractionLayer hal = si.getHardware();\n            // 系统信息\n            resultMap.put(\"sys\", getSystemInfo(os));\n            // cpu 信息\n            resultMap.put(\"cpu\", getCpuInfo(hal.getProcessor()));\n            // 内存信息\n            resultMap.put(\"memory\", getMemoryInfo(hal.getMemory()));\n            // 交换区信息\n            resultMap.put(\"swap\", getSwapInfo(hal.getMemory()));\n            // 磁盘\n            resultMap.put(\"disk\", getDiskInfo(os));\n            resultMap.put(\"time\", DateUtil.format(new Date(), \"HH:mm:ss\"));\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n        return resultMap;\n    }\n\n    /**\n     * 获取磁盘信息\n     * @return /\n     */\n    private Map<String,Object> getDiskInfo(OperatingSystem os) {\n        Map<String,Object> diskInfo = new LinkedHashMap<>();\n        FileSystem fileSystem = os.getFileSystem();\n        List<OSFileStore> fsArray = fileSystem.getFileStores();\n        String osName = System.getProperty(\"os.name\");\n        long available = 0, total = 0;\n        for (OSFileStore fs : fsArray){\n            // windows 需要将所有磁盘分区累加，linux 和 mac 直接累加会出现磁盘重复的问题，待修复\n            if(osName.toLowerCase().startsWith(ElConstant.WIN)) {\n                available += fs.getUsableSpace();\n                total += fs.getTotalSpace();\n            } else {\n                available = fs.getUsableSpace();\n                total = fs.getTotalSpace();\n                break;\n            }\n        }\n        long used = total - available;\n        diskInfo.put(\"total\", total > 0 ? FileUtil.getSize(total) : \"?\");\n        diskInfo.put(\"available\", FileUtil.getSize(available));\n        diskInfo.put(\"used\", FileUtil.getSize(used));\n        if(total != 0){\n            diskInfo.put(\"usageRate\", df.format(used/(double)total * 100));\n        } else {\n            diskInfo.put(\"usageRate\", 0);\n        }\n        return diskInfo;\n    }\n\n    /**\n     * 获取交换区信息\n     * @param memory /\n     * @return /\n     */\n    private Map<String,Object> getSwapInfo(GlobalMemory memory) {\n        Map<String,Object> swapInfo = new LinkedHashMap<>();\n        VirtualMemory virtualMemory = memory.getVirtualMemory();\n        long total = virtualMemory.getSwapTotal();\n        long used = virtualMemory.getSwapUsed();\n        swapInfo.put(\"total\", FormatUtil.formatBytes(total));\n        swapInfo.put(\"used\", FormatUtil.formatBytes(used));\n        swapInfo.put(\"available\", FormatUtil.formatBytes(total - used));\n        if(used == 0){\n            swapInfo.put(\"usageRate\", 0);\n        } else {\n            swapInfo.put(\"usageRate\", df.format(used/(double)total * 100));\n        }\n        return swapInfo;\n    }\n\n    /**\n     * 获取内存信息\n     * @param memory /\n     * @return /\n     */\n    private Map<String,Object> getMemoryInfo(GlobalMemory memory) {\n        Map<String,Object> memoryInfo = new LinkedHashMap<>();\n        memoryInfo.put(\"total\", FormatUtil.formatBytes(memory.getTotal()));\n        memoryInfo.put(\"available\", FormatUtil.formatBytes(memory.getAvailable()));\n        memoryInfo.put(\"used\", FormatUtil.formatBytes(memory.getTotal() - memory.getAvailable()));\n        memoryInfo.put(\"usageRate\", df.format((memory.getTotal() - memory.getAvailable())/(double)memory.getTotal() * 100));\n        return memoryInfo;\n    }\n\n    /**\n     * 获取Cpu相关信息\n     * @param processor /\n     * @return /\n     */\n    private Map<String,Object> getCpuInfo(CentralProcessor processor) {\n        Map<String,Object> cpuInfo = new LinkedHashMap<>();\n        cpuInfo.put(\"name\", processor.getProcessorIdentifier().getName());\n        cpuInfo.put(\"package\", processor.getPhysicalPackageCount() + \"个物理CPU\");\n        cpuInfo.put(\"core\", processor.getPhysicalProcessorCount() + \"个物理核心\");\n        cpuInfo.put(\"coreNumber\", processor.getPhysicalProcessorCount());\n        cpuInfo.put(\"logic\", processor.getLogicalProcessorCount() + \"个逻辑CPU\");\n        // CPU信息\n        long[] prevTicks = processor.getSystemCpuLoadTicks();\n        // 默认等待300毫秒...\n        long time = 300;\n        Util.sleep(time);\n        long[] ticks = processor.getSystemCpuLoadTicks();\n        while (Arrays.toString(prevTicks).equals(Arrays.toString(ticks)) && time < 1000){\n            time += 25;\n            Util.sleep(25);\n            ticks = processor.getSystemCpuLoadTicks();\n        }\n        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];\n        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];\n        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];\n        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];\n        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];\n        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];\n        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];\n        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];\n        long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;\n        cpuInfo.put(\"used\", df.format(100d * user / totalCpu + 100d * sys / totalCpu));\n        cpuInfo.put(\"idle\", df.format(100d * idle / totalCpu));\n        return cpuInfo;\n    }\n\n    /**\n     * 获取系统相关信息,系统、运行天数、系统IP\n     * @param os /\n     * @return /\n     */\n    private Map<String,Object> getSystemInfo(OperatingSystem os){\n        Map<String,Object> systemInfo = new LinkedHashMap<>();\n        // jvm 运行时间\n        long time = ManagementFactory.getRuntimeMXBean().getStartTime();\n        Date date = new Date(time);\n        // 计算项目运行时间\n        String formatBetween = DateUtil.formatBetween(date, new Date(), Level.HOUR);\n        // 系统信息\n        systemInfo.put(\"os\", os.toString());\n        systemInfo.put(\"day\", formatBetween);\n        systemInfo.put(\"ip\", StringUtils.getLocalIp());\n        return systemInfo;\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.security.service.UserCacheManager;\nimport me.zhengjie.modules.security.service.dto.AuthorityDto;\nimport me.zhengjie.modules.system.domain.Menu;\nimport me.zhengjie.modules.system.domain.Role;\nimport me.zhengjie.exception.EntityExistException;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.modules.system.repository.RoleRepository;\nimport me.zhengjie.modules.system.repository.UserRepository;\nimport me.zhengjie.modules.system.service.RoleService;\nimport me.zhengjie.modules.system.service.dto.RoleDto;\nimport me.zhengjie.modules.system.service.dto.RoleQueryCriteria;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport me.zhengjie.modules.system.service.mapstruct.RoleMapper;\nimport me.zhengjie.modules.system.service.mapstruct.RoleSmallMapper;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-03\n */\n@Service\n@RequiredArgsConstructor\npublic class RoleServiceImpl implements RoleService {\n\n    private final RoleRepository roleRepository;\n    private final RoleMapper roleMapper;\n    private final RoleSmallMapper roleSmallMapper;\n    private final RedisUtils redisUtils;\n    private final UserRepository userRepository;\n    private final UserCacheManager userCacheManager;\n\n    @Override\n    public List<RoleDto> queryAll() {\n        Sort sort = Sort.by(Sort.Direction.ASC, \"level\");\n        return roleMapper.toDto(roleRepository.findAll(sort));\n    }\n\n    @Override\n    public List<RoleDto> queryAll(RoleQueryCriteria criteria) {\n        return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));\n    }\n\n    @Override\n    public PageResult<RoleDto> queryAll(RoleQueryCriteria criteria, Pageable pageable) {\n        Page<Role> page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);\n        return PageUtil.toPage(page.map(roleMapper::toDto));\n    }\n\n    @Override\n    public RoleDto findById(long id) {\n        String key = CacheKey.ROLE_ID + id;\n        Role role = redisUtils.get(key, Role.class);\n        if (role == null) {\n            role = roleRepository.findById(id).orElseGet(Role::new);\n            ValidationUtil.isNull(role.getId(), \"Role\", \"id\", id);\n            redisUtils.set(key, role, 1, TimeUnit.DAYS);\n        }\n        return roleMapper.toDto(role);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(Role resources) {\n        if (roleRepository.findByName(resources.getName()) != null) {\n            throw new EntityExistException(Role.class, \"username\", resources.getName());\n        }\n        roleRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(Role resources) {\n        Role role = roleRepository.findById(resources.getId()).orElseGet(Role::new);\n        ValidationUtil.isNull(role.getId(), \"Role\", \"id\", resources.getId());\n\n        Role role1 = roleRepository.findByName(resources.getName());\n\n        if (role1 != null && !role1.getId().equals(role.getId())) {\n            throw new EntityExistException(Role.class, \"username\", resources.getName());\n        }\n        role.setName(resources.getName());\n        role.setDescription(resources.getDescription());\n        role.setDataScope(resources.getDataScope());\n        role.setDepts(resources.getDepts());\n        role.setLevel(resources.getLevel());\n        roleRepository.save(role);\n        // 更新相关缓存\n        delCaches(role.getId(), null);\n    }\n\n    @Override\n    public void updateMenu(Role resources, RoleDto roleDTO) {\n        Role role = roleMapper.toEntity(roleDTO);\n        List<User> users = userRepository.findByRoleId(role.getId());\n        // 更新菜单\n        role.setMenus(resources.getMenus());\n        delCaches(resources.getId(), users);\n        roleRepository.save(role);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void untiedMenu(Long menuId) {\n        // 更新菜单\n        roleRepository.untiedMenu(menuId);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        for (Long id : ids) {\n            // 更新相关缓存\n            delCaches(id, null);\n        }\n        roleRepository.deleteAllByIdIn(ids);\n    }\n\n    @Override\n    public List<RoleSmallDto> findByUsersId(Long userId) {\n        String key = CacheKey.ROLE_USER + userId;\n        List<RoleSmallDto> roles = redisUtils.getList(key, RoleSmallDto.class);\n        if (CollUtil.isEmpty(roles)) {\n            roles = roleSmallMapper.toDto(new ArrayList<>(roleRepository.findByUserId(userId)));\n            redisUtils.set(key, roles, 1, TimeUnit.DAYS);\n        }\n        return roles;\n    }\n\n    @Override\n    public Integer findByRoles(Set<Role> roles) {\n        if (roles.isEmpty()) {\n            return Integer.MAX_VALUE;\n        }\n        Set<RoleDto> roleDtos = new HashSet<>();\n        for (Role role : roles) {\n            roleDtos.add(findById(role.getId()));\n        }\n        return Collections.min(roleDtos.stream().map(RoleDto::getLevel).collect(Collectors.toList()));\n    }\n\n    @Override\n    public List<AuthorityDto> buildPermissions(UserDto user) {\n        String key = CacheKey.ROLE_AUTH + user.getId();\n        List<AuthorityDto> authorityDtos = redisUtils.getList(key, AuthorityDto.class);\n        if (CollUtil.isEmpty(authorityDtos)) {\n            Set<String> permissions = new HashSet<>();\n            // 如果是管理员直接返回\n            if (user.getIsAdmin()) {\n                permissions.add(\"admin\");\n                return permissions.stream().map(AuthorityDto::new)\n                        .collect(Collectors.toList());\n            }\n            Set<Role> roles = roleRepository.findByUserId(user.getId());\n            permissions = roles.stream().flatMap(role -> role.getMenus().stream())\n                    .map(Menu::getPermission)\n                    .filter(StringUtils::isNotBlank).collect(Collectors.toSet());\n            authorityDtos = permissions.stream().map(AuthorityDto::new)\n                    .collect(Collectors.toList());\n            redisUtils.set(key, authorityDtos, 1, TimeUnit.HOURS);\n        }\n        return authorityDtos;\n    }\n\n    @Override\n    public void download(List<RoleDto> roles, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (RoleDto role : roles) {\n            Map<String, Object> map = new LinkedHashMap<>();\n            map.put(\"角色名称\", role.getName());\n            map.put(\"角色级别\", role.getLevel());\n            map.put(\"描述\", role.getDescription());\n            map.put(\"创建日期\", role.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    @Override\n    public void verification(Set<Long> ids) {\n        if (userRepository.countByRoles(ids) > 0) {\n            throw new BadRequestException(\"所选角色存在用户关联，请解除关联再试！\");\n        }\n    }\n\n    @Override\n    public List<Role> findInMenuId(List<Long> menuIds) {\n        return roleRepository.findInMenuId(menuIds);\n    }\n\n    /**\n     * 清理缓存\n     * @param id /\n     */\n    public void delCaches(Long id, List<User> users) {\n        users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users;\n        if (CollectionUtil.isNotEmpty(users)) {\n            users.forEach(item -> userCacheManager.cleanUserCache(item.getUsername()));\n            Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());\n            redisUtils.delByKeys(CacheKey.DATA_USER, userIds);\n            redisUtils.delByKeys(CacheKey.MENU_USER, userIds);\n            redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds);\n            redisUtils.delByKeys(CacheKey.ROLE_USER, userIds);\n        }\n        redisUtils.del(CacheKey.ROLE_ID + id);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.utils.PageResult;\nimport me.zhengjie.config.properties.FileProperties;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.security.service.OnlineUserService;\nimport me.zhengjie.modules.security.service.UserCacheManager;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.exception.EntityExistException;\nimport me.zhengjie.exception.EntityNotFoundException;\nimport me.zhengjie.modules.system.repository.UserRepository;\nimport me.zhengjie.modules.system.service.UserService;\nimport me.zhengjie.modules.system.service.dto.*;\nimport me.zhengjie.modules.system.service.mapstruct.UserMapper;\nimport me.zhengjie.utils.*;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.validation.constraints.NotBlank;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Service\n@RequiredArgsConstructor\npublic class UserServiceImpl implements UserService {\n\n    private final UserRepository userRepository;\n    private final UserMapper userMapper;\n    private final FileProperties properties;\n    private final RedisUtils redisUtils;\n    private final UserCacheManager userCacheManager;\n    private final OnlineUserService onlineUserService;\n\n    @Override\n    public PageResult<UserDto> queryAll(UserQueryCriteria criteria, Pageable pageable) {\n        Page<User> page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);\n        return PageUtil.toPage(page.map(userMapper::toDto));\n    }\n\n    @Override\n    public List<UserDto> queryAll(UserQueryCriteria criteria) {\n        List<User> users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder));\n        return userMapper.toDto(users);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public UserDto findById(long id) {\n        String key = CacheKey.USER_ID + id;\n        User user = redisUtils.get(key, User.class);\n        if (user == null) {\n            user = userRepository.findById(id).orElseGet(User::new);\n            ValidationUtil.isNull(user.getId(), \"User\", \"id\", id);\n            redisUtils.set(key, user, 1, TimeUnit.DAYS);\n        }\n        return userMapper.toDto(user);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void create(User resources) {\n        if (userRepository.findByUsername(resources.getUsername()) != null) {\n            throw new EntityExistException(User.class, \"username\", resources.getUsername());\n        }\n        if (userRepository.findByEmail(resources.getEmail()) != null) {\n            throw new EntityExistException(User.class, \"email\", resources.getEmail());\n        }\n        if (userRepository.findByPhone(resources.getPhone()) != null) {\n            throw new EntityExistException(User.class, \"phone\", resources.getPhone());\n        }\n        userRepository.save(resources);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(User resources) throws Exception {\n        User user = userRepository.findById(resources.getId()).orElseGet(User::new);\n        ValidationUtil.isNull(user.getId(), \"User\", \"id\", resources.getId());\n        User user1 = userRepository.findByUsername(resources.getUsername());\n        User user2 = userRepository.findByEmail(resources.getEmail());\n        User user3 = userRepository.findByPhone(resources.getPhone());\n        if (user1 != null && !user.getId().equals(user1.getId())) {\n            throw new EntityExistException(User.class, \"username\", resources.getUsername());\n        }\n        if (user2 != null && !user.getId().equals(user2.getId())) {\n            throw new EntityExistException(User.class, \"email\", resources.getEmail());\n        }\n        if (user3 != null && !user.getId().equals(user3.getId())) {\n            throw new EntityExistException(User.class, \"phone\", resources.getPhone());\n        }\n        // 如果用户的角色改变\n        if (!resources.getRoles().equals(user.getRoles())) {\n            redisUtils.del(CacheKey.DATA_USER + resources.getId());\n            redisUtils.del(CacheKey.MENU_USER + resources.getId());\n            redisUtils.del(CacheKey.ROLE_AUTH + resources.getId());\n            redisUtils.del(CacheKey.ROLE_USER + resources.getId());\n        }\n        // 修改部门会影响 数据权限\n        if (!Objects.equals(resources.getDept(),user.getDept())) {\n            redisUtils.del(CacheKey.DATA_USER + resources.getId());\n        }\n        // 如果用户被禁用，则清除用户登录信息\n        if(!resources.getEnabled()){\n            onlineUserService.kickOutForUsername(resources.getUsername());\n        }\n        user.setUsername(resources.getUsername());\n        user.setEmail(resources.getEmail());\n        user.setEnabled(resources.getEnabled());\n        user.setRoles(resources.getRoles());\n        user.setDept(resources.getDept());\n        user.setJobs(resources.getJobs());\n        user.setPhone(resources.getPhone());\n        user.setNickName(resources.getNickName());\n        user.setGender(resources.getGender());\n        userRepository.save(user);\n        // 清除缓存\n        delCaches(user.getId(), user.getUsername());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateCenter(User resources) {\n        User user = userRepository.findById(resources.getId()).orElseGet(User::new);\n        User user1 = userRepository.findByPhone(resources.getPhone());\n        if (user1 != null && !user.getId().equals(user1.getId())) {\n            throw new EntityExistException(User.class, \"phone\", resources.getPhone());\n        }\n        user.setNickName(resources.getNickName());\n        user.setPhone(resources.getPhone());\n        user.setGender(resources.getGender());\n        userRepository.save(user);\n        // 清理缓存\n        delCaches(user.getId(), user.getUsername());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void delete(Set<Long> ids) {\n        for (Long id : ids) {\n            // 清理缓存\n            UserDto user = findById(id);\n            delCaches(user.getId(), user.getUsername());\n        }\n        userRepository.deleteAllByIdIn(ids);\n    }\n\n    @Override\n    public UserDto findByName(String userName) {\n        User user = userRepository.findByUsername(userName);\n        if (user == null) {\n            throw new EntityNotFoundException(User.class, \"name\", userName);\n        } else {\n            return userMapper.toDto(user);\n        }\n    }\n\n    @Override\n    public UserDto getLoginData(String userName) {\n        User user = userRepository.findByUsername(userName);\n        if (user == null) {\n            return null;\n        } else {\n            return userMapper.toDto(user);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updatePass(String username, String pass) {\n        userRepository.updatePass(username, pass, new Date());\n        flushCache(username);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void resetPwd(Set<Long> ids, String pwd) {\n        List<User> users = userRepository.findAllById(ids);\n        // 清除缓存\n        users.forEach(user -> {\n            // 清除缓存\n            flushCache(user.getUsername());\n            // 强制退出\n            onlineUserService.kickOutForUsername(user.getUsername());\n        });\n        // 重置密码\n        userRepository.resetPwd(ids, pwd);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Map<String, String> updateAvatar(MultipartFile multipartFile) {\n        // 文件大小验证\n        FileUtil.checkSize(properties.getAvatarMaxSize(), multipartFile.getSize());\n        // 验证文件上传的格式\n        String image = \"gif jpg png jpeg\";\n        String fileType = FileUtil.getExtensionName(multipartFile.getOriginalFilename());\n        if(fileType != null && !image.contains(fileType)){\n            throw new BadRequestException(\"文件格式错误！, 仅支持 \" + image +\" 格式\");\n        }\n        User user = userRepository.findByUsername(SecurityUtils.getCurrentUsername());\n        String oldPath = user.getAvatarPath();\n        File file = FileUtil.upload(multipartFile, properties.getPath().getAvatar());\n        user.setAvatarPath(Objects.requireNonNull(file).getPath());\n        user.setAvatarName(file.getName());\n        userRepository.save(user);\n        if (StringUtils.isNotBlank(oldPath)) {\n            FileUtil.del(oldPath);\n        }\n        @NotBlank String username = user.getUsername();\n        flushCache(username);\n        return new HashMap<String, String>(1) {{\n            put(\"avatar\", file.getName());\n        }};\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateEmail(String username, String email) {\n        userRepository.updateEmail(username, email);\n        flushCache(username);\n    }\n\n    @Override\n    public void download(List<UserDto> queryAll, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (UserDto userDTO : queryAll) {\n            List<String> roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList());\n            Map<String, Object> map = new LinkedHashMap<>();\n            map.put(\"用户名\", userDTO.getUsername());\n            map.put(\"角色\", roles);\n            map.put(\"部门\", userDTO.getDept().getName());\n            map.put(\"岗位\", userDTO.getJobs().stream().map(JobSmallDto::getName).collect(Collectors.toList()));\n            map.put(\"邮箱\", userDTO.getEmail());\n            map.put(\"状态\", userDTO.getEnabled() ? \"启用\" : \"禁用\");\n            map.put(\"手机号码\", userDTO.getPhone());\n            map.put(\"修改密码的时间\", userDTO.getPwdResetTime());\n            map.put(\"创建日期\", userDTO.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    /**\n     * 清理缓存\n     *\n     * @param id /\n     */\n    public void delCaches(Long id, String username) {\n        redisUtils.del(CacheKey.USER_ID + id);\n        flushCache(username);\n    }\n\n    /**\n     * 清理 登陆时 用户缓存信息\n     *\n     * @param username /\n     */\n    private void flushCache(String username) {\n        userCacheManager.cleanUserCache(username);\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/VerifyServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.impl;\n\nimport cn.hutool.core.lang.Dict;\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.extra.template.Template;\nimport cn.hutool.extra.template.TemplateConfig;\nimport cn.hutool.extra.template.TemplateEngine;\nimport cn.hutool.extra.template.TemplateUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.vo.EmailVo;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.modules.system.service.VerifyService;\nimport me.zhengjie.utils.RedisUtils;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.Collections;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-26\n */\n@Service\n@RequiredArgsConstructor\npublic class VerifyServiceImpl implements VerifyService {\n\n    @Value(\"${code.expiration}\")\n    private Long expiration;\n    private final RedisUtils redisUtils;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public EmailVo sendEmail(String email, String key) {\n        EmailVo emailVo;\n        String content;\n        String redisKey = key + email;\n        // 如果不存在有效的验证码，就创建一个新的\n        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(\"template\", TemplateConfig.ResourceMode.CLASSPATH));\n        Template template = engine.getTemplate(\"email.ftl\");\n        String oldCode =  redisUtils.get(redisKey, String.class);\n        if(oldCode == null){\n            String code = RandomUtil.randomNumbers (6);\n            // 存入缓存\n            if(!redisUtils.set(redisKey, code, expiration)){\n                throw new BadRequestException(\"服务异常，请联系网站负责人\");\n            }\n            content = template.render(Dict.create().set(\"code\",code));\n            // 存在就再次发送原来的验证码\n        } else {\n            content = template.render(Dict.create().set(\"code\",oldCode));\n        }\n        emailVo = new EmailVo(Collections.singletonList(email),\"ELADMIN后台管理系统\",content);\n        return emailVo;\n    }\n\n    @Override\n    public void validated(String key, String code) {\n        String value = redisUtils.get(key, String.class);\n        if(!code.equals(value)){\n            throw new BadRequestException(\"无效验证码\");\n        } else {\n            redisUtils.del(key);\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.service.dto.DeptDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DeptMapper extends BaseMapper<DeptDto, Dept> {\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptSmallMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Dept;\nimport me.zhengjie.modules.system.service.dto.DeptSmallDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-25\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DeptSmallMapper extends BaseMapper<DeptSmallDto, Dept> {\n\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictDetailMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.DictDetail;\nimport me.zhengjie.modules.system.service.dto.DictDetailDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Mapper(componentModel = \"spring\", uses = {DictSmallMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DictDetailMapper extends BaseMapper<DictDetailDto, DictDetail> {\n\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Dict;\nimport me.zhengjie.modules.system.service.dto.DictDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DictMapper extends BaseMapper<DictDto, Dict> {\n\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictSmallMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Dict;\nimport me.zhengjie.modules.system.service.dto.DictSmallDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-04-10\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface DictSmallMapper extends BaseMapper<DictSmallDto, Dict> {\n\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Job;\nimport me.zhengjie.modules.system.service.dto.JobDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\n@Mapper(componentModel = \"spring\",uses = {DeptMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface JobMapper extends BaseMapper<JobDto, Job> {\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobSmallMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Job;\nimport me.zhengjie.modules.system.service.dto.JobSmallDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-03-29\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface JobSmallMapper extends BaseMapper<JobSmallDto, Job> {\n\n}"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/MenuMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Menu;\nimport me.zhengjie.modules.system.service.dto.MenuDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-17\n */\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface MenuMapper extends BaseMapper<MenuDto, Menu> {\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Role;\nimport me.zhengjie.modules.system.service.dto.RoleDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Mapper(componentModel = \"spring\", uses = {MenuMapper.class, DeptMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface RoleMapper extends BaseMapper<RoleDto, Role> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleSmallMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.Role;\nimport me.zhengjie.modules.system.service.dto.RoleSmallDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n * @author Zheng Jie\n * @date 2019-5-23\n */\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface RoleSmallMapper extends BaseMapper<RoleSmallDto, Role> {\n\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/UserMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.modules.system.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.modules.system.domain.User;\nimport me.zhengjie.modules.system.service.dto.UserDto;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n * @author Zheng Jie\n * @date 2018-11-23\n */\n@Mapper(componentModel = \"spring\",uses = {RoleMapper.class, DeptMapper.class, JobMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface UserMapper extends BaseMapper<UserDto, User> {\n}\n"
  },
  {
    "path": "eladmin-system/src/main/java/me/zhengjie/sysrunner/SystemRunner.java",
    "content": "/*\n * Copyright 2019-2020 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 *      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 me.zhengjie.sysrunner;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author Zheng Jie\n * @description 程序启动后处理数据\n * @date 2025-01-13\n **/\n@Slf4j\n@Component\n@RequiredArgsConstructor\npublic class SystemRunner implements ApplicationRunner {\n\n    @Override\n    public void run(ApplicationArguments args) {\n    }\n}\n"
  },
  {
    "path": "eladmin-system/src/main/resources/banner.txt",
    "content": "       _                 _           _\n      | |               | |         (_)\n   ___| |______ __ _  __| |_ __ ___  _ _ __\n  / _ | |______/ _` |/ _` | '_ ` _ \\| | '_ \\\n |  __| |     | (_| | (_| | | | | | | | | | |\n  \\___|_|      \\__,_|\\__,_|_| |_| |_|_|_| |_|\n\n :: Spring Boot ::       (v2.6.4)"
  },
  {
    "path": "eladmin-system/src/main/resources/config/application-dev.yml",
    "content": "#配置数据源\nspring:\n  datasource:\n    druid:\n      db-type: com.alibaba.druid.pool.DruidDataSource\n      driverClassName: com.p6spy.engine.spy.P6SpyDriver\n      url: jdbc:p6spy:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false\n      username: root\n      password: 123456\n      # 初始连接数，建议设置为与最小空闲连接数相同\n      initial-size: 20\n      # 最小空闲连接数，保持足够的空闲连接以应对请求\n      min-idle: 20\n      # 最大连接数，根据并发需求适当增加\n      max-active: 50\n      # 获取连接超时时间（毫秒），调整以满足响应时间要求\n      max-wait: 3000\n      # 启用KeepAlive机制，保持长连接\n      keep-alive: true\n      # 连接有效性检测间隔时间（毫秒），定期检查连接的健康状态\n      time-between-eviction-runs-millis: 60000\n      # 连接在池中最小生存时间（毫秒），确保连接在池中至少存在一段时间\n      min-evictable-idle-time-millis: 300000\n      # 连接在池中最大生存时间（毫秒），防止连接在池中停留过长\n      max-evictable-idle-time-millis: 900000\n      # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除\n      test-while-idle: true\n      # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个\n      test-on-borrow: true\n      # 是否在归还到池中前进行检验\n      test-on-return: false\n      # 停用 com_ping 探活机制\n      use-ping-method: false\n      # 检测连接是否有效\n      validation-query: SELECT 1\n      # 配置监控统计\n      webStatFilter:\n        enabled: true\n      stat-view-servlet:\n        enabled: true\n        url-pattern: /druid/*\n        reset-enable: false\n      filter:\n        stat:\n          enabled: true\n          # 记录慢SQL\n          log-slow-sql: true\n          slow-sql-millis: 2000\n          merge-sql: true\n        wall:\n          config:\n            multi-statement-allow: true\n\n# 登录相关配置\nlogin:\n  #  是否限制单用户登录\n  single-login: false\n  # Redis用户登录缓存配置\n  user-cache:\n    # 存活时间/秒\n    idle-time: 21600\n  #  验证码\n  code:\n    #  验证码类型配置 查看 LoginProperties 类\n    code-type: arithmetic\n    #  登录图形验证码有效时间/分钟\n    expiration: 2\n    #  验证码高度\n    width: 111\n    #  验证码宽度\n    height: 36\n    # 内容长度\n    length: 2\n    # 字体名称，为空则使用默认字体\n    font-name:\n    # 字体大小\n    font-size: 25\n\n#jwt\njwt:\n  header: Authorization\n  # 令牌前缀\n  token-start-with: Bearer\n  # 必须使用最少88位的Base64对该令牌进行编码\n  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=\n  # 令牌过期时间 此处单位/毫秒 ，默认4小时，可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html\n  token-validity-in-seconds: 14400000\n  # 在线用户key\n  online-key: \"online_token:\"\n  # 验证码\n  code-key: \"captcha_code:\"\n  # token 续期检查时间范围（默认30分钟，单位毫秒），在token即将过期的一段时间内用户操作了，则给用户的token续期\n  detect: 1800000\n  # 续期时间范围，默认1小时，单位毫秒\n  renew: 3600000\n\n#是否允许生成代码，生产环境设置为false\ngenerator:\n  enabled: true\n\n#是否开启 swagger-ui\nswagger:\n  enabled: true\n\n# 文件存储路径\nfile:\n  mac:\n    path: ~/file/\n    avatar: ~/avatar/\n  linux:\n    path: /home/eladmin/file/\n    avatar: /home/eladmin/avatar/\n  windows:\n    path: C:\\eladmin\\file\\\n    avatar: C:\\eladmin\\avatar\\\n  # 文件大小 /M\n  maxSize: 100\n  avatarMaxSize: 5\n\n# 亚马逊S3协议云存储配置\n#支持七牛云，阿里云OSS，腾讯云COS，华为云OBS，移动云EOS等\namz:\n  s3:\n    # 地域\n    region: test\n    # 地域对应的 endpoint\n    endPoint: https://s3.test.com\n    # 访问的域名\n    domain: https://s3.test.com\n    # 账号的认证信息，或者子账号的认证信息\n    accessKey: 填写你的AccessKey\n    secretKey: 填写你的SecretKey\n    # 存储桶（Bucket）\n    defaultBucket: 填写你的存储桶名称\n    # 文件存储路径\n    timeformat: yyyy-MM\n\n"
  },
  {
    "path": "eladmin-system/src/main/resources/config/application-prod.yml",
    "content": "#配置数据源\nspring:\n  datasource:\n    druid:\n      db-type: com.alibaba.druid.pool.DruidDataSource\n      driverClassName: com.mysql.cj.jdbc.Driver\n      url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:eladmin}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false\n      username: ${DB_USER:root}\n      password: ${DB_PWD:123456}\n      # 初始连接数，建议设置为与最小空闲连接数相同\n      initial-size: 20\n      # 最小空闲连接数，保持足够的空闲连接以应对请求\n      min-idle: 20\n      # 最大连接数，根据并发需求适当增加\n      max-active: 50\n      # 获取连接超时时间（毫秒），调整以满足响应时间要求\n      max-wait: 3000\n      # 启用KeepAlive机制，保持长连接\n      keep-alive: true\n      # 连接有效性检测间隔时间（毫秒），定期检查连接的健康状态\n      time-between-eviction-runs-millis: 60000\n      # 连接在池中最小生存时间（毫秒），确保连接在池中至少存在一段时间\n      min-evictable-idle-time-millis: 300000\n      # 连接在池中最大生存时间（毫秒），防止连接在池中停留过长\n      max-evictable-idle-time-millis: 900000\n      # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除\n      test-while-idle: true\n      # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个\n      test-on-borrow: true\n      # 是否在归还到池中前进行检验\n      test-on-return: false\n      # 停用 com_ping 探活机制\n      use-ping-method: false\n      # 检测连接是否有效\n      validation-query: SELECT 1\n      # 配置监控统计\n      webStatFilter:\n        enabled: true\n      stat-view-servlet:\n        allow:\n        enabled: true\n        # 控制台管理用户名和密码\n        url-pattern: /druid/*\n        reset-enable: false\n        login-username: admin\n        login-password: 123456\n      filter:\n        stat:\n          enabled: true\n          # 记录慢SQL\n          log-slow-sql: true\n          slow-sql-millis: 2000\n          merge-sql: true\n        wall:\n          config:\n            multi-statement-allow: true\n\n# 登录相关配置\nlogin:\n  #  是否限制单用户登录\n  single-login: false\n  # Redis用户登录缓存配置\n  user-cache:\n    # 存活时间/秒\n    idle-time: 21600\n  #  验证码\n  code:\n    #  验证码类型配置 查看 LoginProperties 类\n    code-type: arithmetic\n    #  登录图形验证码有效时间/分钟\n    expiration: 2\n    #  验证码高度\n    width: 111\n    #  验证码宽度\n    height: 36\n    # 内容长度\n    length: 2\n    # 字体名称，为空则使用默认字体，如遇到线上乱码，设置其他字体即可\n    font-name:\n    # 字体大小\n    font-size: 25\n\n#jwt\njwt:\n  header: Authorization\n  # 令牌前缀\n  token-start-with: Bearer\n  # 必须使用最少88位的Base64对该令牌进行编码\n  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=\n  # 令牌过期时间 此处单位/毫秒 ，默认2小时，可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html\n  token-validity-in-seconds: 7200000\n  # 在线用户key\n  online-key: \"online_token:\"\n  # 验证码\n  code-key: \"captcha_code:\"\n  # token 续期检查时间范围（默认30分钟，单位默认毫秒），在token即将过期的一段时间内用户操作了，则给用户的token续期\n  detect: 1800000\n  # 续期时间范围，默认 1小时，这里单位毫秒\n  renew: 3600000\n\n#是否允许生成代码，生产环境设置为false\ngenerator:\n  enabled: false\n\n#如果生产环境要开启swagger，需要配置请求地址\n#springfox:\n#  documentation:\n#    swagger:\n#      v2:\n#        host: # 接口域名或外网ip\n\n#是否开启 swagger-ui\nswagger:\n  enabled: false\n\n# 文件存储路径\nfile:\n  mac:\n    path: ~/file/\n    avatar: ~/avatar/\n  linux:\n    path: /home/eladmin/file/\n    avatar: /home/eladmin/avatar/\n  windows:\n    path: C:\\eladmin\\file\\\n    avatar: C:\\eladmin\\avatar\\\n  # 文件大小 /M\n  maxSize: 100\n  avatarMaxSize: 5\n\n# 亚马逊S3协议云存储配置\n#支持七牛云，阿里云OSS，腾讯云COS，华为云OBS，移动云EOS等\namz:\n  s3:\n    # 地域\n    region: test\n    # 地域对应的 endpoint\n    endPoint: https://s3.test.com\n    # 访问的域名\n    domain: https://s3.test.com\n    # 账号的认证信息，或者子账号的认证信息\n    accessKey: 填写你的AccessKey\n    secretKey: 填写你的SecretKey\n    # 存储桶（Bucket）\n    defaultBucket: 填写你的存储桶名称\n    # 文件存储路径\n    timeformat: yyyy-MM"
  },
  {
    "path": "eladmin-system/src/main/resources/config/application-quartz.yml",
    "content": "# 配置 quartz 分布式支持, sql 文件在 sql 目录下，需要导入到数据库，并且需要修改 application.yml 文件的 active: dev 配置\nspring:\n  quartz:\n    # 必需，指定使用 JDBC 存储\n    job-store-type: jdbc\n    properties:\n      org:\n        quartz:\n          scheduler:\n            # 必需，指定调度器实例的名称\n            instanceName: eladmin\n            # 必需，指定调度器实例的 ID\n            instanceId: auto\n          threadPool:\n            # 可选，线程池的线程数量，可以根据需要调整\n            threadCount: 5\n          jobStore:\n            # 可选，如果你不需要集群，可以去掉\n            isClustered: true\n            # 可选，集群检查间隔时间，可以根据需要调整\n            clusterCheckinInterval: 10000\n            # 必需，指定 JDBC 驱动代理类\n            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate\n            # 可选，是否使用属性存储，可以根据需要调整\n            useProperties: false\n            # 必需，指定表的前缀\n            tablePrefix: qrtz_\n            # 可选，指定误触发阈值，可以根据需要调整\n            misfireThreshold: 60000"
  },
  {
    "path": "eladmin-system/src/main/resources/config/application.yml",
    "content": "server:\n  port: 8000\n  http2:\n    # 启用 HTTP/2 支持，提升传输效率\n    enabled: true\n  compression:\n    # 启用 GZIP 压缩，减少传输数据量\n    enabled: true\n    # 需要压缩的 MIME 类型\n    mime-types: text/html, text/xml, text/plain, application/json\n    # 最小压缩响应大小（字节）\n\nspring:\n  freemarker:\n    check-template-location: false\n  profiles:\n    # 激活的环境，如果需要 quartz 分布式支持，需要修改 active: dev,quartz\n    active: dev\n  data:\n    redis:\n      repositories:\n        enabled: false\n#  pid:\n#    file: /自行指定位置/eladmin.pid\n\n  #配置 Jpa\n  jpa:\n    hibernate:\n      ddl-auto: none\n    open-in-view: true\n    properties:\n      hibernate:\n        dialect: org.hibernate.dialect.MySQL5InnoDBDialect\n\n  redis:\n    #数据库索引\n    database: ${REDIS_DB:0}\n    host: ${REDIS_HOST:127.0.0.1}\n    port: ${REDIS_PORT:6379}\n    password: ${REDIS_PWD:}\n    #连接超时时间\n    timeout: 5000\n    # 连接池配置\n    lettuce:\n      pool:\n        # 连接池最大连接数\n        max-active: 30\n        # 连接池最大阻塞等待时间（毫秒），负值表示没有限制\n        max-wait: -1\n        # 连接池中的最大空闲连接数\n        max-idle: 20\n        # 连接池中的最小空闲连接数\n        min-idle: 1\n\ntask:\n  pool:\n    # 核心线程池大小\n    core-pool-size: 10\n    # 最大线程数\n    max-pool-size: 30\n    # 活跃时间\n    keep-alive-seconds: 60\n    # 队列容量\n    queue-capacity: 50\n\n#邮箱验证码有效时间/秒\ncode:\n  expiration: 300\n\n#密码加密传输，前端公钥加密，后端私钥解密\nrsa:\n  private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A=="
  },
  {
    "path": "eladmin-system/src/main/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration scan=\"true\" scanPeriod=\"30 seconds\" debug=\"false\">\n    <contextName>elAdmin</contextName>\n    <property name=\"log.charset\" value=\"utf-8\" />\n    <property name=\"log.pattern\" value=\"%contextName- %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %msg%n\" />\n\n    <!--输出到控制台-->\n    <appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>${log.pattern}</pattern>\n            <charset>${log.charset}</charset>\n        </encoder>\n    </appender>\n\n    <!--普通日志输出到控制台-->\n    <root level=\"info\">\n        <appender-ref ref=\"console\" />\n    </root>\n\n    <!-- Spring 日志级别控制 -->\n    <logger name=\"org.springframework\" level=\"warn\" />\n\n    <!-- DnsServerAddressStreamProviders调整为ERROR -->\n    <logger name=\"io.netty.resolver.dns.DnsServerAddressStreamProviders\" level=\"ERROR\"/>\n\n    <!-- 设置其他类的日志级别为 ERROR -->\n    <logger name=\"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]\" level=\"ERROR\"/>\n    <logger name=\"org.springframework.web.servlet.DispatcherServlet\" level=\"ERROR\"/>\n</configuration>\n"
  },
  {
    "path": "eladmin-system/src/main/resources/spy.properties",
    "content": "# 应用的拦截模块\nmodulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory\n\n# 自定义日志打印\nlogMessageFormat=me.zhengjie.config.CustomP6SpyLogger\n\n# 日志输出到控制台\nappender=com.p6spy.engine.spy.appender.Slf4JLogger\n\n# 日期格式\ndateformat=yyyy-MM-dd HH:mm:ss\n\n# 实际驱动可多个\ndriverlist=com.mysql.cj.jdbc.Driver\n\n# 是否开启慢SQL记录\noutagedetection=true\n\n# 慢SQL记录标准 2 秒\noutagedetectioninterval=2\n\n# 是否过滤 Log\nfilter=true\n\n# 过滤 Log 时所排除的 sql 关键字，以逗号分隔\nexclude=SELECT 1,INSERT INTO sys_log\n\n# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.\nexcludecategories=info,debug,result,commit,resultset"
  },
  {
    "path": "eladmin-system/src/main/resources/template/email.ftl",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n    <style>\n        @page {\n            margin: 0;\n        }\n    </style>\n</head>\n<body style=\"margin: 0px;\n            padding: 0px;\n\t\t\tfont: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;\n            color: #000;\">\n<div style=\"height: auto;\n\t\t\twidth: 820px;\n\t\t\tmin-width: 820px;\n\t\t\tmargin: 0 auto;\n\t\t\tmargin-top: 20px;\n            border: 1px solid #eee;\">\n    <div style=\"padding: 10px;padding-bottom: 0px;\">\n        <p style=\"margin-bottom: 10px;padding-bottom: 0px;\">尊敬的用户，您好：</p>\n        <p style=\"text-indent: 2em; margin-bottom: 10px;\">您正在申请邮箱验证，您的验证码为：</p>\n        <p style=\"text-align: center;\n\t\t\tfont-family: Times New Roman;\n\t\t\tfont-size: 22px;\n\t\t\tcolor: #C60024;\n\t\t\tpadding: 20px 0px;\n\t\t\tmargin-bottom: 10px;\n\t\t\tfont-weight: bold;\n\t\t\tbackground: #ebebeb;\">${code}</p>\n        <div class=\"foot-hr hr\" style=\"margin: 0 auto;\n\t\t\tz-index: 111;\n\t\t\twidth: 800px;\n\t\t\tmargin-top: 30px;\n\t\t\tborder-top: 1px solid #DA251D;\">\n        </div>\n        <div style=\"text-align: center;\n\t\t\tfont-size: 12px;\n\t\t\tpadding: 20px 0px;\n\t\t\tfont-family: Microsoft YaHei;\">\n            Copyright &copy;${.now?string(\"yyyy\")} <a hover=\"color: #DA251D;\" style=\"color: #999;\" href=\"https://github.com/elunez/eladmin\" target=\"_blank\">ELADMIN</a> 后台管理系统 All Rights Reserved.\n        </div>\n\n    </div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "eladmin-system/src/main/resources/template/taskAlarm.ftl",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n    <style>\n        @page {\n            margin: 0;\n        }\n    </style>\n</head>\n<body style=\"margin: 0px;\n            padding: 0px;\n\t\t\tfont: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;\n            color: #000;\">\n<div style=\"height: auto;\n\t\t\tmargin: 0 auto;\n\t\t\tmargin-top: 20px;\n\t\t\tpadding: 20px;\n            border: 1px solid #eee;\">\n        <div>\n            <p style=\"margin-bottom: 10px;\">任务信息：</p>\n            <table style=\"border-collapse: collapse;\">\n                <tr>\n                    <th style=\"padding: .65em;background: #666;border: 1px solid #777;color: #fff;\">任务名称</th>\n                    <th style=\"padding: .65em;background: #666;border: 1px solid #777;color: #fff;\">Bean名称</th>\n                    <th style=\"padding: .65em;background: #666;border: 1px solid #777;color: #fff;\">执行方法</th>\n                    <th style=\"padding: .65em;background: #666;border: 1px solid #777;color: #fff;\">参数内容</th>\n                    <th style=\"padding: .65em;background: #666;border: 1px solid #777;color: #fff;\">Cron表达式</th>\n                    <th style=\"padding: .65em;background: #666;border: 1px solid #777;color: #fff;\">描述内容</th>\n                </tr>\n                <tr>\n                    <td style=\"padding: .65em;border: 1px solid#777;\">${task.jobName}</td>\n                    <td style=\"padding: .65em;border: 1px solid#777;\">${task.beanName}</td>\n                    <td style=\"padding: .65em;border: 1px solid#777;\">${task.methodName}</td>\n                    <td style=\"padding: .65em;border: 1px solid#777;\">${(task.params)!\"\"}</td>\n                    <td style=\"padding: .65em;border: 1px solid#777;\">${task.cronExpression}</td>\n                    <td style=\"padding: .65em;border: 1px solid#777;\">${(task.description)!\"\"}</td>\n                </tr>\n            </table>\n        </div>\n        <div>\n            <p style=\"margin-bottom: 10px;\">异常信息：</p>\n            <pre style=\"position: relative;\n  padding: 15px;\n  line-height: 20px;\n  border-left: 5px solid #ddd;\n  color: #333;\n  font-family: Courier New, serif;\n  font-size: 12px\">\n                ${msg}\n            </pre>\n        </div>\n        <div class=\"foot-hr hr\" style=\"margin: 0 auto;\n\t\t\tz-index: 111;\n\t\t\twidth: 800px;\n\t\t\tmargin-top: 30px;\n\t\t\tborder-top: 1px solid #DA251D;\">\n        </div>\n        <div style=\"text-align: center;\n\t\t\tfont-size: 12px;\n\t\t\tpadding: 20px 0px;\n\t\t\tfont-family: Microsoft YaHei;\">\n            Copyright &copy;${.now?string(\"yyyy\")} <a hover=\"color: #DA251D;\" style=\"color: #999;\" href=\"https://github.com/elunez/eladmin\" target=\"_blank\">ELADMIN</a> 后台管理系统 All Rights Reserved.\n        </div>\n\n    </div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "eladmin-system/src/test/java/me/zhengjie/EladminSystemApplicationTests.java",
    "content": "package me.zhengjie;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\n\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\npublic class EladminSystemApplicationTests {\n\n    @Test\n    public void contextLoads() {\n    }\n\n    public static void main(String[] args) {\n    }\n}\n\n"
  },
  {
    "path": "eladmin-tools/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\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>eladmin</artifactId>\n        <groupId>me.zhengjie</groupId>\n        <version>2.7</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>eladmin-tools</artifactId>\n    <name>工具模块</name>\n\n    <properties>\n        <mail.version>1.4.7</mail.version>\n        <alipay.version>4.22.57.ALL</alipay.version>\n    </properties>\n\n    <dependencies>\n        <!-- 同时需要common模块和logging模块只需要引入logging模块即可 -->\n        <dependency>\n            <groupId>me.zhengjie</groupId>\n            <artifactId>eladmin-logging</artifactId>\n            <version>2.7</version>\n        </dependency>\n\n        <!--邮件依赖-->\n        <dependency>\n            <groupId>javax.mail</groupId>\n            <artifactId>mail</artifactId>\n            <version>${mail.version}</version>\n        </dependency>\n\n        <!--支付宝依赖-->\n        <dependency>\n            <groupId>com.alipay.sdk</groupId>\n            <artifactId>alipay-sdk-java</artifactId>\n            <version>${alipay.version}</version>\n        </dependency>\n\n        <!--amazon s3 依赖-->\n        <dependency>\n            <groupId>software.amazon.awssdk</groupId>\n            <artifactId>s3</artifactId>\n            <version>2.30.13</version>\n            <scope>compile</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java",
    "content": "package me.zhengjie.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.s3.S3Client;\n\nimport java.net.URI;\n\n/**\n * @author Zheng Jie\n * @date 2025-06-25\n */\n@Data\n@Configuration\n@ConfigurationProperties(prefix = \"amz.s3\")\npublic class AmzS3Config {\n\n\t/**\n\t * Amazon S3 的区域配置，例如 \"us-west-2\"。\n\t * 该区域决定了 S3 存储桶的地理位置。\n\t */\n\tprivate String region;\n\n\t/**\n\t * Amazon S3 的端点 URL\n\t * 该端点用于访问 S3 服务。\n\t */\n\tprivate String endPoint;\n\n\t/**\n\t * Amazon S3 的域名\n\t * 该域名用于构建访问 S3 服务的完整 URL。\n\t */\n\tprivate String domain;\n\n\t/**\n\t * Amazon S3 的访问密钥 ID，用于身份验证。\n\t * 该密钥与 secretKey 一起使用来授权对 S3 服务的访问。\n\t */\n\tprivate String accessKey;\n\n\t/**\n\t * Amazon S3 的秘密访问密钥，用于身份验证。\n\t * 该密钥与 accessKey 一起使用来授权对 S3 服务的访问。\n\t */\n\tprivate String secretKey;\n\n\t/**\n\t * 默认的 S3 存储桶名称。\n\t * 该存储桶用于存储上传的文件和数据。\n\t */\n\tprivate String defaultBucket;\n\n\t/**\n\t * 文件上传后存储的文件夹格式，默认为 \"yyyy-MM\"。\n\t */\n\tprivate String timeformat;\n\n\t/**\n\t * 创建并返回一个 AmazonS3 客户端实例。\n\t * 使用当前配置类的 endPoint, region, accessKey 和 secretKey。\n\t * 声明为 @Bean 后，Spring 会将其作为单例管理，并在需要时自动注入。\n\t *\n\t * @return 配置好的 AmazonS3 客户端实例\n\t */\n\t@Bean\n\tpublic S3Client amazonS3Client() {\n\t\treturn S3Client.builder().region(Region.of(region))\n\t\t\t\t.endpointOverride(URI.create(endPoint))\n\t\t\t\t.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)))\n\t\t\t\t.build();\n\t}\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/AlipayConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport java.io.Serializable;\n\n/**\n * 支付宝配置类\n * @author Zheng Jie\n * @date 2018-12-31\n */\n@Data\n@Entity\n@Table(name = \"tool_alipay_config\")\npublic class AlipayConfig implements Serializable {\n\n    @Id\n    @Column(name = \"config_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    private Long id;\n\n    @NotBlank\n    @ApiModelProperty(value = \"应用ID\")\n    private String appId;\n\n    @NotBlank\n    @ApiModelProperty(value = \"商户私钥\")\n    private String privateKey;\n\n    @NotBlank\n    @ApiModelProperty(value = \"支付宝公钥\")\n    private String publicKey;\n\n    @ApiModelProperty(value = \"签名方式\")\n    private String signType=\"RSA2\";\n\n    @Column(name = \"gateway_url\")\n    @ApiModelProperty(value = \"支付宝开放安全地址\", hidden = true)\n    private String gatewayUrl = \"https://openapi.alipaydev.com/gateway.do\";\n\n    @ApiModelProperty(value = \"编码\", hidden = true)\n    private String charset= \"utf-8\";\n\n    @NotBlank\n    @ApiModelProperty(value = \"异步通知地址\")\n    private String notifyUrl;\n\n    @NotBlank\n    @ApiModelProperty(value = \"订单完成后返回的页面\")\n    private String returnUrl;\n\n    @ApiModelProperty(value = \"类型\")\n    private String format=\"JSON\";\n\n    @NotBlank\n    @ApiModelProperty(value = \"商户号\")\n    private String sysServiceProviderId;\n\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/EmailConfig.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport java.io.Serializable;\n\n/**\n * 邮件配置类，数据存覆盖式存入数据存\n * @author Zheng Jie\n * @date 2018-12-26\n */\n@Entity\n@Data\n@Table(name = \"tool_email_config\")\npublic class EmailConfig implements Serializable {\n\n    @Id\n    @Column(name = \"config_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    private Long id;\n\n    @NotBlank\n    @ApiModelProperty(value = \"邮件服务器SMTP地址\")\n    private String host;\n\n    @NotBlank\n    @ApiModelProperty(value = \"邮件服务器 SMTP 端口\")\n    private String port;\n\n    @NotBlank\n    @ApiModelProperty(value = \"发件者用户名\")\n    private String user;\n\n    @NotBlank\n    @ApiModelProperty(value = \"密码\")\n    private String pass;\n\n    @NotBlank\n    @ApiModelProperty(value = \"收件人\")\n    private String fromUser;\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/LocalStorage.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.*;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\n@Getter\n@Setter\n@Entity\n@Table(name=\"tool_local_storage\")\n@NoArgsConstructor\npublic class LocalStorage extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"storage_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @ApiModelProperty(value = \"真实文件名\")\n    private String realName;\n\n    @ApiModelProperty(value = \"文件名\")\n    private String name;\n\n    @ApiModelProperty(value = \"后缀\")\n    private String suffix;\n\n    @ApiModelProperty(value = \"路径\")\n    private String path;\n\n    @ApiModelProperty(value = \"类型\")\n    private String type;\n\n    @ApiModelProperty(value = \"大小\")\n    private String size;\n\n    public LocalStorage(String realName,String name, String suffix, String path, String type, String size) {\n        this.realName = realName;\n        this.name = name;\n        this.suffix = suffix;\n        this.path = path;\n        this.type = type;\n        this.size = size;\n    }\n\n    public void copy(LocalStorage source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport me.zhengjie.base.BaseEntity;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotBlank;\nimport java.io.Serializable;\n\n/**\n* @description S3存储实体类\n* @author Zheng Jie\n* @date 2025-06-25\n**/\n@Data\n@Entity\n@Table(name = \"tool_s3_storage\")\n@EqualsAndHashCode(callSuper = true)\npublic class S3Storage extends BaseEntity implements Serializable {\n\n    @Id\n    @Column(name = \"storage_id\")\n    @ApiModelProperty(value = \"ID\", hidden = true)\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @NotBlank\n    @ApiModelProperty(value = \"文件名称\")\n    private String fileName;\n\n    @NotBlank\n    @ApiModelProperty(value = \"真实存储的名称\")\n    private String fileRealName;\n\n    @NotBlank\n    @ApiModelProperty(value = \"文件大小\")\n    private String fileSize;\n\n    @NotBlank\n    @ApiModelProperty(value = \"文件MIME 类型\")\n    private String fileMimeType;\n\n    @NotBlank\n    @ApiModelProperty(value = \"文件类型\")\n    private String fileType;\n\n    @NotBlank\n    @ApiModelProperty(value = \"文件路径\")\n    private String filePath;\n\n    public void copy(S3Storage source){\n        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/enums/AliPayStatusEnum.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain.enums;\n\n/**\n * 支付状态\n * @author zhengjie\n * @date 2018/08/01 16:45:43\n */\npublic enum  AliPayStatusEnum {\n\n    /** 交易成功 */\n    FINISHED(\"TRADE_FINISHED\"),\n\n    /** 支付成功 */\n    SUCCESS(\"TRADE_SUCCESS\"),\n\n    /** 交易创建 */\n    BUYER_PAY(\"WAIT_BUYER_PAY\"),\n\n    /** 交易关闭 */\n    CLOSED(\"TRADE_CLOSED\");\n\n    private final String value;\n\n    AliPayStatusEnum(String value) {\n        this.value = value;\n    }\n\n    public String getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/vo/EmailVo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotEmpty;\nimport java.util.List;\n\n/**\n * 发送邮件时，接收参数的类\n * @author 郑杰\n * @date 2018/09/28 12:02:14\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class EmailVo {\n\n    @NotEmpty\n    @ApiModelProperty(value = \"收件人\")\n    private List<String> tos;\n\n    @NotBlank\n    @ApiModelProperty(value = \"主题\")\n    private String subject;\n\n    @NotBlank\n    @ApiModelProperty(value = \"内容\")\n    private String content;\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/domain/vo/TradeVo.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.domain.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport javax.validation.constraints.NotBlank;\nimport java.sql.Date;\nimport java.sql.Timestamp;\n\n/**\n * 交易详情，按需应该存入数据库，这里存入数据库，仅供临时测试\n * @author Zheng Jie\n * @date 2018-12-31\n */\n@Data\npublic class TradeVo {\n\n    @NotBlank\n    @ApiModelProperty(value = \"商品描述\")\n    private String body;\n\n    @NotBlank\n    @ApiModelProperty(value = \"商品名称\")\n    private String subject;\n\n    @ApiModelProperty(value = \"商户订单号\", hidden = true)\n    private String outTradeNo;\n\n    @ApiModelProperty(value = \"第三方订单号\", hidden = true)\n    private String tradeNo;\n\n    @NotBlank\n    @ApiModelProperty(value = \"价格\")\n    private String totalAmount;\n\n    @ApiModelProperty(value = \"订单状态,已支付，未支付，作废\", hidden = true)\n    private String state;\n\n    @ApiModelProperty(value = \"创建时间\", hidden = true)\n    private Timestamp createTime;\n\n    @ApiModelProperty(value = \"作废时间\", hidden = true)\n    private Date cancelTime;\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/repository/AliPayRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.AlipayConfig;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-31\n */\npublic interface AliPayRepository extends JpaRepository<AlipayConfig,Long> {\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/repository/EmailRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.EmailConfig;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-26\n */\npublic interface EmailRepository extends JpaRepository<EmailConfig,Long> {\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/repository/LocalStorageRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.LocalStorage;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\npublic interface LocalStorageRepository extends JpaRepository<LocalStorage, Long>, JpaSpecificationExecutor<LocalStorage> {\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.repository;\n\nimport me.zhengjie.domain.S3Storage;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.jpa.repository.Query;\n\n/**\n* @author Zheng Jie\n* @date 2025-06-25\n*/\npublic interface S3StorageRepository extends JpaRepository<S3Storage, Long>, JpaSpecificationExecutor<S3Storage> {\n\n\t/**\n\t * 根据ID查询文件路径\n\t * @param id 文件ID\n\t * @return 文件路径\n\t */\n\t@Query(value = \"SELECT file_path FROM s3_storage WHERE id = ?1\", nativeQuery = true)\n\tString selectFilePathById(Long id);\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.rest.AnonymousAccess;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.annotation.rest.AnonymousGetMapping;\nimport me.zhengjie.domain.vo.TradeVo;\nimport me.zhengjie.domain.AlipayConfig;\nimport me.zhengjie.domain.enums.AliPayStatusEnum;\nimport me.zhengjie.utils.AlipayUtils;\nimport me.zhengjie.service.AliPayService;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport springfox.documentation.annotations.ApiIgnore;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-31\n */\n@Slf4j\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/aliPay\")\n@Api(tags = \"工具：支付宝管理\")\npublic class AliPayController {\n\n    private final AlipayUtils alipayUtils;\n    private final AliPayService alipayService;\n\n    @GetMapping\n    public ResponseEntity<AlipayConfig> queryAliConfig() {\n        return new ResponseEntity<>(alipayService.find(), HttpStatus.OK);\n    }\n\n    @Log(\"配置支付宝\")\n    @ApiOperation(\"配置支付宝\")\n    @PutMapping\n    public ResponseEntity<Object> updateAliPayConfig(@Validated @RequestBody AlipayConfig alipayConfig) {\n        alipayService.config(alipayConfig);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @Log(\"支付宝PC网页支付\")\n    @ApiOperation(\"PC网页支付\")\n    @PostMapping(value = \"/toPayAsPC\")\n    public ResponseEntity<String> toPayAsPc(@Validated @RequestBody TradeVo trade) throws Exception {\n        AlipayConfig aliPay = alipayService.find();\n        trade.setOutTradeNo(alipayUtils.getOrderCode());\n        String payUrl = alipayService.toPayAsPc(aliPay, trade);\n        return ResponseEntity.ok(payUrl);\n    }\n\n    @Log(\"支付宝手机网页支付\")\n    @ApiOperation(\"手机网页支付\")\n    @PostMapping(value = \"/toPayAsWeb\")\n    public ResponseEntity<String> toPayAsWeb(@Validated @RequestBody TradeVo trade) throws Exception {\n        AlipayConfig alipay = alipayService.find();\n        trade.setOutTradeNo(alipayUtils.getOrderCode());\n        String payUrl = alipayService.toPayAsWeb(alipay, trade);\n        return ResponseEntity.ok(payUrl);\n    }\n\n    @ApiIgnore\n    @AnonymousGetMapping(\"/return\")\n    @ApiOperation(\"支付之后跳转的链接\")\n    public ResponseEntity<String> returnPage(HttpServletRequest request, HttpServletResponse response) {\n        AlipayConfig alipay = alipayService.find();\n        response.setContentType(\"text/html;charset=\" + alipay.getCharset());\n        //内容验签，防止黑客篡改参数\n        if (alipayUtils.rsaCheck(request, alipay)) {\n            //商户订单号\n            String outTradeNo = new String(request.getParameter(\"out_trade_no\").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);\n            //支付宝交易号\n            String tradeNo = new String(request.getParameter(\"trade_no\").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);\n            System.out.println(\"商户订单号\" + outTradeNo + \"  \" + \"第三方交易号\" + tradeNo);\n\n            // 根据业务需要返回数据，这里统一返回OK\n            return new ResponseEntity<>(\"payment successful\", HttpStatus.OK);\n        } else {\n            // 根据业务需要返回数据\n            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);\n        }\n    }\n\n    @ApiIgnore\n    @RequestMapping(\"/notify\")\n    @AnonymousAccess\n    @ApiOperation(\"支付异步通知(要公网访问)，接收异步通知，检查通知内容app_id、out_trade_no、total_amount是否与请求中的一致，根据trade_status进行后续业务处理\")\n    public ResponseEntity<Object> notify(HttpServletRequest request) {\n        AlipayConfig alipay = alipayService.find();\n        Map<String, String[]> parameterMap = request.getParameterMap();\n        //内容验签，防止黑客篡改参数\n        if (alipayUtils.rsaCheck(request, alipay)) {\n            //交易状态\n            String tradeStatus = new String(request.getParameter(\"trade_status\").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);\n            // 商户订单号\n            String outTradeNo = new String(request.getParameter(\"out_trade_no\").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);\n            //支付宝交易号\n            String tradeNo = new String(request.getParameter(\"trade_no\").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);\n            //付款金额\n            String totalAmount = new String(request.getParameter(\"total_amount\").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);\n            //验证\n            if (tradeStatus.equals(AliPayStatusEnum.SUCCESS.getValue()) || tradeStatus.equals(AliPayStatusEnum.FINISHED.getValue())) {\n                // 验证通过后应该根据业务需要处理订单\n            }\n            return new ResponseEntity<>(HttpStatus.OK);\n        }\n        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/rest/EmailController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.domain.vo.EmailVo;\nimport me.zhengjie.domain.EmailConfig;\nimport me.zhengjie.service.EmailService;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * 发送邮件\n * @author 郑杰\n * @date 2018/09/28 6:55:53\n */\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"api/email\")\n@Api(tags = \"工具：邮件管理\")\npublic class EmailController {\n\n    private final EmailService emailService;\n\n    @GetMapping\n    public ResponseEntity<EmailConfig> queryEmailConfig(){\n        return new ResponseEntity<>(emailService.find(),HttpStatus.OK);\n    }\n\n    @Log(\"配置邮件\")\n    @PutMapping\n    @ApiOperation(\"配置邮件\")\n    public ResponseEntity<Object> updateEmailConfig(@Validated @RequestBody EmailConfig emailConfig) throws Exception {\n        emailService.config(emailConfig,emailService.find());\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n\n    @Log(\"发送邮件\")\n    @PostMapping\n    @ApiOperation(\"发送邮件\")\n    public ResponseEntity<Object> sendEmail(@Validated @RequestBody EmailVo emailVo){\n        emailService.send(emailVo,emailService.find());\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/rest/LocalStorageController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.domain.LocalStorage;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.service.LocalStorageService;\nimport me.zhengjie.service.dto.LocalStorageDto;\nimport me.zhengjie.service.dto.LocalStorageQueryCriteria;\nimport me.zhengjie.utils.FileUtil;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport io.swagger.annotations.*;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\n@RestController\n@RequiredArgsConstructor\n@Api(tags = \"工具：本地存储管理\")\n@RequestMapping(\"/api/localStorage\")\npublic class LocalStorageController {\n\n    private final LocalStorageService localStorageService;\n\n    @GetMapping\n    @ApiOperation(\"查询文件\")\n    @PreAuthorize(\"@el.check('storage:list')\")\n    public ResponseEntity<PageResult<LocalStorageDto>> queryFile(LocalStorageQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(localStorageService.queryAll(criteria,pageable),HttpStatus.OK);\n    }\n\n    @ApiOperation(\"导出数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('storage:list')\")\n    public void exportFile(HttpServletResponse response, LocalStorageQueryCriteria criteria) throws IOException {\n        localStorageService.download(localStorageService.queryAll(criteria), response);\n    }\n\n    @PostMapping\n    @ApiOperation(\"上传文件\")\n    @PreAuthorize(\"@el.check('storage:add')\")\n    public ResponseEntity<Object> createFile(@RequestParam String name, @RequestParam(\"file\") MultipartFile file){\n        localStorageService.create(name, file);\n        return new ResponseEntity<>(HttpStatus.CREATED);\n    }\n\n    @ApiOperation(\"上传图片\")\n    @PostMapping(\"/pictures\")\n    public ResponseEntity<LocalStorage> uploadPicture(@RequestParam MultipartFile file){\n        // 判断文件是否为图片\n        String suffix = FileUtil.getExtensionName(file.getOriginalFilename());\n        if(!FileUtil.IMAGE.equals(FileUtil.getFileType(suffix))){\n            throw new BadRequestException(\"只能上传图片\");\n        }\n        LocalStorage localStorage = localStorageService.create(null, file);\n        return new ResponseEntity<>(localStorage, HttpStatus.OK);\n    }\n\n    @PutMapping\n    @Log(\"修改文件\")\n    @ApiOperation(\"修改文件\")\n    @PreAuthorize(\"@el.check('storage:edit')\")\n    public ResponseEntity<Object> updateFile(@Validated @RequestBody LocalStorage resources){\n        localStorageService.update(resources);\n        return new ResponseEntity<>(HttpStatus.NO_CONTENT);\n    }\n\n    @Log(\"删除文件\")\n    @DeleteMapping\n    @ApiOperation(\"多选删除\")\n    public ResponseEntity<Object> deleteFile(@RequestBody Long[] ids) {\n        localStorageService.deleteAll(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.rest;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.annotation.Log;\nimport me.zhengjie.config.AmzS3Config;\nimport me.zhengjie.domain.S3Storage;\nimport me.zhengjie.service.S3StorageService;\nimport me.zhengjie.service.dto.S3StorageQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * amz S3 协议云存储管理\n * @author 郑杰\n * @date 2025-06-25\n */\n@Slf4j\n@RestController\n@RequiredArgsConstructor\n@RequestMapping(\"/api/s3Storage\")\n@Api(tags = \"工具：S3协议云存储管理\")\npublic class S3StorageController {\n\n    private final AmzS3Config amzS3Config;\n    private final S3StorageService s3StorageService;\n\n    @ApiOperation(\"导出数据\")\n    @GetMapping(value = \"/download\")\n    @PreAuthorize(\"@el.check('storage:list')\")\n    public void exportS3Storage(HttpServletResponse response, S3StorageQueryCriteria criteria) throws IOException {\n        s3StorageService.download(s3StorageService.queryAll(criteria), response);\n    }\n\n    @GetMapping\n    @ApiOperation(\"查询文件\")\n    @PreAuthorize(\"@el.check('storage:list')\")\n    public ResponseEntity<PageResult<S3Storage>> queryS3Storage(S3StorageQueryCriteria criteria, Pageable pageable){\n        return new ResponseEntity<>(s3StorageService.queryAll(criteria, pageable),HttpStatus.OK);\n    }\n\n    @PostMapping\n    @ApiOperation(\"上传文件\")\n    public ResponseEntity<Object> uploadS3Storage(@RequestParam MultipartFile file){\n        S3Storage storage = s3StorageService.upload(file);\n        Map<String,Object> map = new HashMap<>(3);\n        map.put(\"id\",storage.getId());\n        map.put(\"errno\",0);\n        map.put(\"data\",new String[]{amzS3Config.getDomain() + \"/\" + storage.getFilePath()});\n        return new ResponseEntity<>(map,HttpStatus.OK);\n    }\n\n    @Log(\"下载文件\")\n    @ApiOperation(\"下载文件\")\n    @GetMapping(value = \"/download/{id}\")\n    public ResponseEntity<Object> downloadS3Storage(@PathVariable Long id){\n        Map<String,Object> map = new HashMap<>(1);\n        S3Storage storage = s3StorageService.getById(id);\n        if (storage == null) {\n            map.put(\"message\", \"文件不存在或已被删除\");\n            return new ResponseEntity<>(map, HttpStatus.NOT_FOUND);\n        }\n        // 仅适合公开文件访问，私有文件可以使用服务中的 privateDownload 方法\n        String url = amzS3Config.getDomain() + \"/\" + storage.getFilePath();\n        map.put(\"url\", url);\n        return new ResponseEntity<>(map,HttpStatus.OK);\n    }\n\n    @Log(\"删除多个文件\")\n    @DeleteMapping\n    @ApiOperation(\"删除多个文件\")\n    @PreAuthorize(\"@el.check('storage:del')\")\n    public ResponseEntity<Object> deleteAllS3Storage(@RequestBody List<Long> ids) {\n        s3StorageService.deleteAll(ids);\n        return new ResponseEntity<>(HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/AliPayService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.vo.TradeVo;\nimport me.zhengjie.domain.AlipayConfig;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-31\n */\npublic interface AliPayService {\n\n    /**\n     * 查询配置\n     * @return AlipayConfig\n     */\n    AlipayConfig find();\n\n    /**\n     * 更新配置\n     * @param alipayConfig 支付宝配置\n     * @return AlipayConfig\n     */\n    AlipayConfig config(AlipayConfig alipayConfig);\n\n    /**\n     * 处理来自PC的交易请求\n     * @param alipay 支付宝配置\n     * @param trade 交易详情\n     * @return String\n     * @throws Exception 异常\n     */\n    String toPayAsPc(AlipayConfig alipay, TradeVo trade) throws Exception;\n\n    /**\n     * 处理来自手机网页的交易请求\n     * @param alipay 支付宝配置\n     * @param trade 交易详情\n     * @return String\n     * @throws Exception 异常\n     */\n    String toPayAsWeb(AlipayConfig alipay, TradeVo trade) throws Exception;\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/EmailService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.vo.EmailVo;\nimport me.zhengjie.domain.EmailConfig;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-26\n */\npublic interface EmailService {\n\n    /**\n     * 更新邮件配置\n     * @param emailConfig 邮箱配置\n     * @param old /\n     * @return /\n     * @throws Exception /\n     */\n    EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception;\n\n    /**\n     * 查询配置\n     * @return EmailConfig 邮件配置\n     */\n    EmailConfig find();\n\n    /**\n     * 发送邮件\n     * @param emailVo 邮件发送的内容\n     * @param emailConfig 邮件配置\n     */\n    void send(EmailVo emailVo, EmailConfig emailConfig);\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/LocalStorageService.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.LocalStorage;\nimport me.zhengjie.service.dto.LocalStorageDto;\nimport me.zhengjie.service.dto.LocalStorageQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\npublic interface LocalStorageService {\n\n    /**\n     * 分页查询\n     * @param criteria 条件\n     * @param pageable 分页参数\n     * @return /\n     */\n    PageResult<LocalStorageDto> queryAll(LocalStorageQueryCriteria criteria, Pageable pageable);\n\n    /**\n     * 查询全部数据\n     * @param criteria 条件\n     * @return /\n     */\n    List<LocalStorageDto> queryAll(LocalStorageQueryCriteria criteria);\n\n    /**\n     * 根据ID查询\n     * @param id /\n     * @return /\n     */\n    LocalStorageDto findById(Long id);\n\n    /**\n     * 上传\n     * @param name 文件名称\n     * @param file 文件\n     * @return /\n     */\n    LocalStorage create(String name, MultipartFile file);\n\n    /**\n     * 编辑\n     * @param resources 文件信息\n     */\n    void update(LocalStorage resources);\n\n    /**\n     * 多选删除\n     * @param ids /\n     */\n    void deleteAll(Long[] ids);\n\n    /**\n     * 导出数据\n     * @param localStorageDtos 待导出的数据\n     * @param response /\n     * @throws IOException /\n     */\n    void download(List<LocalStorageDto> localStorageDtos, HttpServletResponse response) throws IOException;\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service;\n\nimport me.zhengjie.domain.S3Storage;\nimport me.zhengjie.service.dto.S3StorageQueryCriteria;\nimport me.zhengjie.utils.PageResult;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n* @description 服务接口\n* @author Zheng Jie\n* @date 2025-06-25\n**/\npublic interface S3StorageService {\n\n    /**\n    * 查询数据分页\n    * @param criteria 条件\n    * @param pageable 分页参数\n    * @return PageResult\n    */\n    PageResult<S3Storage> queryAll(S3StorageQueryCriteria criteria, Pageable pageable);\n\n    /**\n    * 查询所有数据不分页\n    * @param criteria 条件参数\n    * @return List<S3StorageDto>\n    */\n    List<S3Storage> queryAll(S3StorageQueryCriteria criteria);\n\n    /**\n    * 多选删除\n    * @param ids /\n    */\n    void deleteAll(List<Long> ids);\n\n    /**\n    * 导出数据\n    * @param all 待导出的数据\n    * @param response /\n    * @throws IOException /\n    */\n    void download(List<S3Storage> all, HttpServletResponse response) throws IOException;\n\n    /**\n     * 私有化下载，仅供参考，还有许多方式\n     * @param id 文件ID\n     */\n    Map<String, String> privateDownload(Long id);\n\n    /**\n     * 上传文件\n     * @param file 上传的文件\n     * @return S3Storage 对象，包含文件存储信息\n     */\n    S3Storage upload(MultipartFile file);\n\n    /**\n     * 根据ID获取文件信息\n     * @param id 文件ID\n     * @return S3Storage 对象，包含文件存储信息\n     */\n    S3Storage getById(Long id);\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageDto.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Getter;\nimport lombok.Setter;\nimport me.zhengjie.base.BaseDTO;\nimport java.io.Serializable;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\n@Getter\n@Setter\npublic class LocalStorageDto extends BaseDTO implements Serializable {\n\n    @ApiModelProperty(value = \"ID\")\n    private Long id;\n\n    @ApiModelProperty(value = \"真实文件名\")\n    private String realName;\n\n    @ApiModelProperty(value = \"文件名\")\n    private String name;\n\n    @ApiModelProperty(value = \"后缀\")\n    private String suffix;\n\n    @ApiModelProperty(value = \"文件类型\")\n    private String type;\n\n    @ApiModelProperty(value = \"文件大小\")\n    private String size;\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageQueryCriteria.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport java.sql.Timestamp;\nimport java.util.List;\n\nimport me.zhengjie.annotation.Query;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\n@Data\npublic class LocalStorageQueryCriteria{\n\n    @ApiModelProperty(value = \"模糊查询\")\n    @Query(blurry = \"name,suffix,type,createBy,size\")\n    private String blurry;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @Query(type = Query.Type.BETWEEN)\n    private List<Timestamp> createTime;\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport me.zhengjie.annotation.Query;\nimport java.sql.Timestamp;\nimport java.util.List;\n\n/**\n* @author Zheng Jie\n* @date 2025-06-25\n**/\n@Data\npublic class S3StorageQueryCriteria {\n\n    @Query(type =  Query.Type.INNER_LIKE)\n    @ApiModelProperty(value = \"文件名称\")\n    private String fileName;\n\n    @Query(type = Query.Type.BETWEEN)\n    @ApiModelProperty(value = \"创建时间\")\n    private List<Timestamp> createTime;\n\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/impl/AliPayServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport com.alipay.api.AlipayClient;\nimport com.alipay.api.DefaultAlipayClient;\nimport com.alipay.api.request.AlipayTradePagePayRequest;\nimport com.alipay.api.request.AlipayTradeWapPayRequest;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.vo.TradeVo;\nimport me.zhengjie.domain.AlipayConfig;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.repository.AliPayRepository;\nimport me.zhengjie.service.AliPayService;\nimport org.springframework.cache.annotation.CacheConfig;\nimport org.springframework.cache.annotation.CachePut;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.Optional;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-31\n */\n@Service\n@RequiredArgsConstructor\n@CacheConfig(cacheNames = \"aliPay\")\npublic class AliPayServiceImpl implements AliPayService {\n\n    private final AliPayRepository alipayRepository;\n\n    @Override\n    @Cacheable(key = \"'config'\")\n    public AlipayConfig find() {\n        Optional<AlipayConfig> alipayConfig = alipayRepository.findById(1L);\n        return alipayConfig.orElseGet(AlipayConfig::new);\n    }\n\n    @Override\n    @CachePut(key = \"'config'\")\n    @Transactional(rollbackFor = Exception.class)\n    public AlipayConfig config(AlipayConfig alipayConfig) {\n        alipayConfig.setId(1L);\n        return alipayRepository.save(alipayConfig);\n    }\n\n    @Override\n    public String toPayAsPc(AlipayConfig alipay, TradeVo trade) throws Exception {\n\n        if(alipay.getId() == null){\n            throw new BadRequestException(\"请先添加相应配置，再操作\");\n        }\n        AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppId(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType());\n\n        // 创建API对应的request(电脑网页版)\n        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();\n\n        // 订单完成后返回的页面和异步通知地址\n        request.setReturnUrl(alipay.getReturnUrl());\n        request.setNotifyUrl(alipay.getNotifyUrl());\n        // 填充订单参数\n        request.setBizContent(\"{\" +\n                \"    \\\"out_trade_no\\\":\\\"\"+trade.getOutTradeNo()+\"\\\",\" +\n                \"    \\\"product_code\\\":\\\"FAST_INSTANT_TRADE_PAY\\\",\" +\n                \"    \\\"total_amount\\\":\"+trade.getTotalAmount()+\",\" +\n                \"    \\\"subject\\\":\\\"\"+trade.getSubject()+\"\\\",\" +\n                \"    \\\"body\\\":\\\"\"+trade.getBody()+\"\\\",\" +\n                \"    \\\"extend_params\\\":{\" +\n                \"    \\\"sys_service_provider_id\\\":\\\"\"+alipay.getSysServiceProviderId()+\"\\\"\" +\n                \"    }\"+\n                \"  }\");//填充业务参数\n        // 调用SDK生成表单, 通过GET方式，口可以获取url\n        return alipayClient.pageExecute(request, \"GET\").getBody();\n\n    }\n\n    @Override\n    public String toPayAsWeb(AlipayConfig alipay, TradeVo trade) throws Exception {\n        if(alipay.getId() == null){\n            throw new BadRequestException(\"请先添加相应配置，再操作\");\n        }\n        AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppId(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType());\n\n        double money = Double.parseDouble(trade.getTotalAmount());\n        double maxMoney = 5000;\n        if(money <= 0 || money >= maxMoney){\n            throw new BadRequestException(\"测试金额过大\");\n        }\n        // 创建API对应的request(手机网页版)\n        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();\n        request.setReturnUrl(alipay.getReturnUrl());\n        request.setNotifyUrl(alipay.getNotifyUrl());\n        request.setBizContent(\"{\" +\n                \"    \\\"out_trade_no\\\":\\\"\"+trade.getOutTradeNo()+\"\\\",\" +\n                \"    \\\"product_code\\\":\\\"FAST_INSTANT_TRADE_PAY\\\",\" +\n                \"    \\\"total_amount\\\":\"+trade.getTotalAmount()+\",\" +\n                \"    \\\"subject\\\":\\\"\"+trade.getSubject()+\"\\\",\" +\n                \"    \\\"body\\\":\\\"\"+trade.getBody()+\"\\\",\" +\n                \"    \\\"extend_params\\\":{\" +\n                \"    \\\"sys_service_provider_id\\\":\\\"\"+alipay.getSysServiceProviderId()+\"\\\"\" +\n                \"    }\"+\n                \"  }\");\n        return alipayClient.pageExecute(request, \"GET\").getBody();\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/impl/EmailServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport cn.hutool.extra.mail.Mail;\nimport cn.hutool.extra.mail.MailAccount;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.domain.EmailConfig;\nimport me.zhengjie.domain.vo.EmailVo;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.repository.EmailRepository;\nimport me.zhengjie.service.EmailService;\nimport me.zhengjie.utils.EncryptUtils;\nimport org.springframework.cache.annotation.CacheConfig;\nimport org.springframework.cache.annotation.CachePut;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.Optional;\n\n/**\n * @author Zheng Jie\n * @date 2018-12-26\n */\n@Service\n@RequiredArgsConstructor\n@CacheConfig(cacheNames = \"email\")\npublic class EmailServiceImpl implements EmailService {\n\n    private final EmailRepository emailRepository;\n\n    @Override\n    @CachePut(key = \"'config'\")\n    @Transactional(rollbackFor = Exception.class)\n    public EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception {\n        emailConfig.setId(1L);\n        if(!emailConfig.getPass().equals(old.getPass())){\n            // 对称加密\n            emailConfig.setPass(EncryptUtils.desEncrypt(emailConfig.getPass()));\n        }\n        return emailRepository.save(emailConfig);\n    }\n\n    @Override\n    @Cacheable(key = \"'config'\")\n    public EmailConfig find() {\n        Optional<EmailConfig> emailConfig = emailRepository.findById(1L);\n        return emailConfig.orElseGet(EmailConfig::new);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void send(EmailVo emailVo, EmailConfig emailConfig){\n        if(emailConfig.getId() == null){\n            throw new BadRequestException(\"请先配置，再操作\");\n        }\n        // 封装\n        MailAccount account = new MailAccount();\n        // 设置用户\n        String user = emailConfig.getFromUser().split(\"@\")[0];\n        account.setUser(user);\n        account.setHost(emailConfig.getHost());\n        account.setPort(Integer.parseInt(emailConfig.getPort()));\n        account.setAuth(true);\n        try {\n            // 对称解密\n            account.setPass(EncryptUtils.desDecrypt(emailConfig.getPass()));\n        } catch (Exception e) {\n            throw new BadRequestException(e.getMessage());\n        }\n        account.setFrom(emailConfig.getUser()+\"<\"+emailConfig.getFromUser()+\">\");\n        // ssl方式发送\n        account.setSslEnable(true);\n        // 使用STARTTLS安全连接\n        account.setStarttlsEnable(true);\n        // 解决jdk8之后默认禁用部分tls协议，导致邮件发送失败的问题\n        account.setSslProtocols(\"TLSv1 TLSv1.1 TLSv1.2\");\n        String content = emailVo.getContent();\n        // 发送\n        try {\n            int size = emailVo.getTos().size();\n            Mail.create(account)\n                    .setTos(emailVo.getTos().toArray(new String[size]))\n                    .setTitle(emailVo.getSubject())\n                    .setContent(content)\n                    .setHtml(true)\n                    //关闭session\n                    .setUseGlobalSession(false)\n                    .send();\n        }catch (Exception e){\n            throw new BadRequestException(e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/impl/LocalStorageServiceImpl.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport lombok.RequiredArgsConstructor;\nimport me.zhengjie.config.properties.FileProperties;\nimport me.zhengjie.domain.LocalStorage;\nimport me.zhengjie.service.dto.LocalStorageDto;\nimport me.zhengjie.service.dto.LocalStorageQueryCriteria;\nimport me.zhengjie.service.mapstruct.LocalStorageMapper;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.utils.*;\nimport me.zhengjie.repository.LocalStorageRepository;\nimport me.zhengjie.service.LocalStorageService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.web.multipart.MultipartFile;\nimport javax.servlet.http.HttpServletResponse;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\n@Service\n@RequiredArgsConstructor\npublic class LocalStorageServiceImpl implements LocalStorageService {\n\n    private final LocalStorageRepository localStorageRepository;\n    private final LocalStorageMapper localStorageMapper;\n    private final FileProperties properties;\n\n    @Override\n    public PageResult<LocalStorageDto> queryAll(LocalStorageQueryCriteria criteria, Pageable pageable){\n        Page<LocalStorage> page = localStorageRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page.map(localStorageMapper::toDto));\n    }\n\n    @Override\n    public List<LocalStorageDto> queryAll(LocalStorageQueryCriteria criteria){\n        return localStorageMapper.toDto(localStorageRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));\n    }\n\n    @Override\n    public LocalStorageDto findById(Long id){\n        LocalStorage localStorage = localStorageRepository.findById(id).orElseGet(LocalStorage::new);\n        ValidationUtil.isNull(localStorage.getId(),\"LocalStorage\",\"id\",id);\n        return localStorageMapper.toDto(localStorage);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public LocalStorage create(String name, MultipartFile multipartFile) {\n        FileUtil.checkSize(properties.getMaxSize(), multipartFile.getSize());\n        String suffix = FileUtil.getExtensionName(multipartFile.getOriginalFilename());\n        String type = FileUtil.getFileType(suffix);\n        File file = FileUtil.upload(multipartFile, properties.getPath().getPath() + type +  File.separator);\n        if(ObjectUtil.isNull(file)){\n            throw new BadRequestException(\"上传失败\");\n        }\n        try {\n            name = StringUtils.isBlank(name) ? FileUtil.getFileNameNoEx(multipartFile.getOriginalFilename()) : name;\n            LocalStorage localStorage = new LocalStorage(\n                    file.getName(),\n                    name,\n                    suffix,\n                    file.getPath(),\n                    type,\n                    FileUtil.getSize(multipartFile.getSize())\n            );\n            return localStorageRepository.save(localStorage);\n        }catch (Exception e){\n            FileUtil.del(file);\n            throw e;\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void update(LocalStorage resources) {\n        LocalStorage localStorage = localStorageRepository.findById(resources.getId()).orElseGet(LocalStorage::new);\n        ValidationUtil.isNull( localStorage.getId(),\"LocalStorage\",\"id\",resources.getId());\n        localStorage.copy(resources);\n        localStorageRepository.save(localStorage);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteAll(Long[] ids) {\n        for (Long id : ids) {\n            LocalStorage storage = localStorageRepository.findById(id).orElseGet(LocalStorage::new);\n            FileUtil.del(storage.getPath());\n            localStorageRepository.delete(storage);\n        }\n    }\n\n    @Override\n    public void download(List<LocalStorageDto> queryAll, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (LocalStorageDto localStorageDTO : queryAll) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"文件名\", localStorageDTO.getRealName());\n            map.put(\"备注名\", localStorageDTO.getName());\n            map.put(\"文件类型\", localStorageDTO.getType());\n            map.put(\"文件大小\", localStorageDTO.getSize());\n            map.put(\"创建者\", localStorageDTO.getCreateBy());\n            map.put(\"创建日期\", localStorageDTO.getCreateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n}\n"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java",
    "content": "/*\n*  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.impl;\n\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.IdUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.zhengjie.config.AmzS3Config;\nimport me.zhengjie.domain.S3Storage;\nimport me.zhengjie.exception.BadRequestException;\nimport me.zhengjie.repository.S3StorageRepository;\nimport me.zhengjie.service.S3StorageService;\nimport me.zhengjie.service.dto.S3StorageQueryCriteria;\nimport me.zhengjie.utils.*;\nimport org.apache.commons.io.IOUtils;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\nimport software.amazon.awssdk.core.ResponseInputStream;\nimport software.amazon.awssdk.core.sync.RequestBody;\nimport software.amazon.awssdk.core.waiters.WaiterResponse;\nimport software.amazon.awssdk.services.s3.S3Client;\nimport software.amazon.awssdk.services.s3.model.*;\nimport software.amazon.awssdk.services.s3.waiters.S3Waiter;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n* @description 服务实现\n* @author Zheng Jie\n* @date 2025-06-25\n**/\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class S3StorageServiceImpl implements S3StorageService {\n\n    private final S3Client s3Client;\n    private final AmzS3Config amzS3Config;\n    private final S3StorageRepository s3StorageRepository;\n\n    @Override\n    public S3Storage getById(Long id) {\n        return s3StorageRepository.findById(id).orElse(null);\n    }\n\n    @Override\n    public PageResult<S3Storage> queryAll(S3StorageQueryCriteria criteria, Pageable pageable){\n        Page<S3Storage> page = s3StorageRepository.findAll((root, criteriaQuery, criteriaBuilder)\n                -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);\n        return PageUtil.toPage(page);\n    }\n\n    @Override\n    public List<S3Storage> queryAll(S3StorageQueryCriteria criteria){\n        return s3StorageRepository.findAll((root, criteriaQuery, criteriaBuilder)\n                -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteAll(List<Long> ids) {\n        // 检查桶是否存在\n        String bucketName = amzS3Config.getDefaultBucket();\n        if (!bucketExists(bucketName)) {\n            throw new BadRequestException(\"存储桶不存在，请检查配置或权限。\");\n        }\n        // 遍历 ID 列表，删除对应的文件和数据库记录\n        for (Long id : ids) {\n            String filePath = s3StorageRepository.selectFilePathById(id);\n            if (filePath == null) {\n                System.err.println(\"未找到 ID 为 \" + id + \" 的文件记录，无法删除。\");\n                continue;\n            }\n            try {\n                // 创建 DeleteObjectRequest，指定存储桶和文件键\n                DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()\n                        .bucket(bucketName)\n                        .key(filePath)\n                        .build();\n                // 调用 deleteObject 方法\n                s3Client.deleteObject(deleteObjectRequest);\n                // 删除数据库数据\n                s3StorageRepository.deleteById(id);\n            } catch (S3Exception e) {\n                // 处理 AWS 特定的异常\n                log.error(\"从 S3 删除文件时出错: {}\", e.awsErrorDetails().errorMessage(), e);\n            }\n        }\n    }\n\n    @Override\n    public S3Storage upload(MultipartFile file) {\n        String bucketName = amzS3Config.getDefaultBucket();\n        // 检查存储桶是否存在\n        if (!bucketExists(bucketName)) {\n            log.warn(\"存储桶 {} 不存在，尝试创建...\", bucketName);\n            if (createBucket(bucketName)){\n                log.info(\"存储桶 {} 创建成功。\", bucketName);\n            } else {\n                throw new BadRequestException(\"存储桶创建失败，请检查配置或权限。\");\n            }\n        }\n        // 获取文件名\n        String originalName = file.getOriginalFilename();\n        if (StringUtils.isBlank(originalName)) {\n            throw new IllegalArgumentException(\"文件名不能为空\");\n        }\n        // 生成存储路径和文件名\n        String folder = DateUtil.format(new Date(), amzS3Config.getTimeformat());\n        String fileName = IdUtil.simpleUUID() + \".\" + FileUtil.getExtensionName(originalName);\n        String filePath = folder + \"/\" + fileName;\n        // 构建上传请求\n        PutObjectRequest putObjectRequest = PutObjectRequest.builder()\n                .bucket(amzS3Config.getDefaultBucket())\n                .key(filePath)\n                .build();\n        // 创建 S3Storage 实例\n        S3Storage s3Storage = new S3Storage();\n        try {\n            // 上传文件到 S3\n            s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));\n            // 设置 S3Storage 属性\n            s3Storage.setFileMimeType(FileUtil.getMimeType(originalName));\n            s3Storage.setFileName(originalName);\n            s3Storage.setFileRealName(fileName);\n            s3Storage.setFileSize(FileUtil.getSize(file.getSize()));\n            s3Storage.setFileType(FileUtil.getExtensionName(originalName));\n            s3Storage.setFilePath(filePath);\n            // 保存入库\n            s3StorageRepository.save(s3Storage);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        // 设置地址\n        return s3Storage;\n    }\n\n    @Override\n    public void download(List<S3Storage> all, HttpServletResponse response) throws IOException {\n        List<Map<String, Object>> list = new ArrayList<>();\n        for (S3Storage s3Storage : all) {\n            Map<String,Object> map = new LinkedHashMap<>();\n            map.put(\"文件名称\", s3Storage.getFileName());\n            map.put(\"真实存储的名称\", s3Storage.getFileRealName());\n            map.put(\"文件大小\", s3Storage.getFileSize());\n            map.put(\"文件MIME 类型\", s3Storage.getFileMimeType());\n            map.put(\"文件类型\", s3Storage.getFileType());\n            map.put(\"文件路径\", s3Storage.getFilePath());\n            map.put(\"创建者\", s3Storage.getCreateBy());\n            map.put(\"更新者\", s3Storage.getUpdateBy());\n            map.put(\"创建日期\", s3Storage.getCreateTime());\n            map.put(\"更新时间\", s3Storage.getUpdateTime());\n            list.add(map);\n        }\n        FileUtil.downloadExcel(list, response);\n    }\n\n    public Map<String, String> privateDownload(Long id) {\n        S3Storage storage = s3StorageRepository.findById(id).orElse(null);\n        if (storage == null) {\n            throw new BadRequestException(\"文件不存在或已被删除\");\n        }\n        // 创建 GetObjectRequest，指定存储桶和文件键\n        GetObjectRequest getObjectRequest = GetObjectRequest.builder()\n                .bucket(amzS3Config.getDefaultBucket())\n                .key(storage.getFilePath())\n                .build();\n        String base64Data;\n        // 使用 try-with-resources 确保流能被自动关闭\n        // s3Client.getObject() 返回一个 ResponseInputStream，它是一个包含S3对象数据的输入流\n        try (ResponseInputStream<GetObjectResponse> s3InputStream = s3Client.getObject(getObjectRequest)) {\n            // 使用 IOUtils.toByteArray 将输入流直接转换为字节数组\n            byte[] fileBytes = IOUtils.toByteArray(s3InputStream);\n            // 使用 Java 内置的 Base64 编码器将字节数组转换为 Base64 字符串\n            base64Data = Base64.getEncoder().encodeToString(fileBytes);\n        } catch (S3Exception e) {\n            // 处理 AWS 特定的异常\n            throw new BadRequestException(\"从 S3 下载文件时出错: \" + e.awsErrorDetails().errorMessage());\n        } catch (IOException e) {\n            // 处理通用的 IO 异常 (IOUtils.toByteArray 可能会抛出)\n            throw new BadRequestException(\"读取 S3 输入流时出错: \" + e.getMessage());\n        }\n        // 构造返回数据\n        Map<String, String> responseData = new HashMap<>();\n        // 文件名\n        responseData.put(\"fileName\", storage.getFileName());\n        // 文件类型\n        responseData.put(\"fileMimeType\", storage.getFileMimeType());\n        // 文件内容\n        responseData.put(\"base64Data\", base64Data);\n        return responseData;\n    }\n\n    /**\n     * 检查云存储桶是否存在\n     * @param bucketName 存储桶名称\n     */\n    @SuppressWarnings({\"all\"})\n    private boolean bucketExists(String bucketName) {\n        try {\n            HeadBucketRequest headBucketRequest = HeadBucketRequest.builder()\n                    .bucket(bucketName)\n                    .build();\n            s3Client.headBucket(headBucketRequest);\n            return true;\n        } catch (S3Exception e) {\n            // 如果状态码是 404 (Not Found), 说明存储桶不存在\n            if (e.statusCode() == 404) {\n                log.error(\"存储桶 '{}' 不存在。\", bucketName);\n                return false;\n            }\n            // 其他异常 (如 403 Forbidden) 说明存在问题，但不能断定它不存在\n            throw new BadRequestException(\"检查存储桶时出错: \" + e.awsErrorDetails().errorMessage());\n        }\n    }\n\n    /**\n     * 创建云存储桶\n     * @param bucketName 存储桶名称\n     */\n    private boolean createBucket(String bucketName) {\n        try {\n            // 使用 S3Waiter 等待存储桶创建完成\n            S3Waiter s3Waiter = s3Client.waiter();\n            CreateBucketRequest bucketRequest = CreateBucketRequest.builder()\n                    .bucket(bucketName)\n                    .acl(BucketCannedACL.PRIVATE)\n                    .build();\n            s3Client.createBucket(bucketRequest);\n            // 等待直到存储桶创建完成\n            HeadBucketRequest bucketRequestWait = HeadBucketRequest.builder()\n                    .bucket(bucketName)\n                    .build();\n            // 使用 WaiterResponse 等待存储桶存在\n            WaiterResponse<HeadBucketResponse> waiterResponse = s3Waiter.waitUntilBucketExists(bucketRequestWait);\n            waiterResponse.matched().response().ifPresent(response ->\n                    log.info(\"存储桶 '{}' 创建成功，状态: {}\", bucketName, response.sdkHttpResponse().statusCode())\n            );\n        } catch (BucketAlreadyOwnedByYouException e) {\n            log.warn(\"存储桶 '{}' 已经被您拥有，无需重复创建。\", bucketName);\n        } catch (S3Exception e) {\n            throw new BadRequestException(\"创建存储桶时出错: \" + e.awsErrorDetails().errorMessage());\n        }\n        return true;\n    }\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/service/mapstruct/LocalStorageMapper.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.service.mapstruct;\n\nimport me.zhengjie.base.BaseMapper;\nimport me.zhengjie.service.dto.LocalStorageDto;\nimport me.zhengjie.domain.LocalStorage;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n/**\n* @author Zheng Jie\n* @date 2019-09-05\n*/\n@Mapper(componentModel = \"spring\",unmappedTargetPolicy = ReportingPolicy.IGNORE)\npublic interface LocalStorageMapper extends BaseMapper<LocalStorageDto, LocalStorage> {\n\n}"
  },
  {
    "path": "eladmin-tools/src/main/java/me/zhengjie/utils/AlipayUtils.java",
    "content": "/*\n *  Copyright 2019-2025 Zheng Jie\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 me.zhengjie.utils;\n\nimport com.alipay.api.AlipayApiException;\nimport com.alipay.api.internal.util.AlipaySignature;\nimport me.zhengjie.domain.AlipayConfig;\nimport org.springframework.stereotype.Component;\nimport javax.servlet.http.HttpServletRequest;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 支付宝工具类\n * @author zhengjie\n * @date 2018/09/30 14:04:35\n */\n@Component\npublic class AlipayUtils {\n\n    /**\n     * 生成订单号\n     * @return String\n     */\n    public String getOrderCode() {\n        SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        int a = (int)(Math.random() * 9000.0D) + 1000;\n        System.out.println(a);\n        Date date = new Date();\n        String str = sdf.format(date);\n        String[] split = str.split(\"-\");\n        String s = split[0] + split[1] + split[2];\n        String[] split1 = s.split(\" \");\n        String s1 = split1[0] + split1[1];\n        String[] split2 = s1.split(\":\");\n        return split2[0] + split2[1] + split2[2] + a;\n    }\n\n    /**\n     * 校验签名\n     * @param request HttpServletRequest\n     * @param alipay 阿里云配置\n     * @return boolean\n     */\n    public boolean rsaCheck(HttpServletRequest request, AlipayConfig alipay){\n\n        // 获取支付宝POST过来反馈信息\n        Map<String,String> params = new HashMap<>(1);\n        Map<String, String[]> requestParams = request.getParameterMap();\n        for (Object o : requestParams.keySet()) {\n            String name = (String) o;\n            String[] values = requestParams.get(name);\n            String valueStr = \"\";\n            for (int i = 0; i < values.length; i++) {\n                valueStr = (i == values.length - 1) ? valueStr + values[i]\n                        : valueStr + values[i] + \",\";\n            }\n            params.put(name, valueStr);\n        }\n\n        try {\n            return AlipaySignature.rsaCheckV1(params,\n                    alipay.getPublicKey(),\n                    alipay.getCharset(),\n                    alipay.getSignType());\n        } catch (AlipayApiException e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "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\" 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    <groupId>me.zhengjie</groupId>\n    <artifactId>eladmin</artifactId>\n    <packaging>pom</packaging>\n    <version>2.7</version>\n\n    <modules>\n        <module>eladmin-common</module>\n        <module>eladmin-logging</module>\n        <module>eladmin-system</module>\n        <module>eladmin-tools</module>\n        <module>eladmin-generator</module>\n    </modules>\n\n    <name>ELADMIN 后台管理</name>\n    <url>https://eladmin.vip</url>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.7.18</version>\n    </parent>\n\n    <properties>\n        <logback.version>1.2.9</logback.version>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <fastjson2.version>2.0.54</fastjson2.version>\n        <druid.version>1.2.19</druid.version>\n        <commons-pool2.version>2.11.1</commons-pool2.version>\n        <mapstruct.version>1.4.2.Final</mapstruct.version>\n    </properties>\n\n    <dependencies>\n        <!--Spring boot 核心-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n\n        <!-- Spring boot websocket -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-websocket</artifactId>\n        </dependency>\n\n        <!--Spring boot Web容器-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n            <exclusions>\n                <!-- 去掉Jackson依赖，用fastjson -->\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-json</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!--Spring boot 测试-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!--Spring boot 安全框架-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- spring boot 缓存 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-cache</artifactId>\n        </dependency>\n\n        <!--Spring boot Redis-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-redis</artifactId>\n        </dependency>\n\n        <!--Spring boot redisson-->\n        <dependency>\n            <groupId>org.redisson</groupId>\n            <artifactId>redisson-spring-boot-starter</artifactId>\n            <version>3.17.1</version>\n        </dependency>\n\n        <!--spring boot 集成redis所需common-pool2-->\n        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-pool2</artifactId>\n            <version>${commons-pool2.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n        </dependency>\n\n        <!--监控sql日志-->\n        <dependency>\n            <groupId>p6spy</groupId>\n            <artifactId>p6spy</artifactId>\n            <version>3.9.1</version>\n        </dependency>\n\n        <!-- Swagger UI 相关 -->\n        <dependency>\n            <groupId>com.github.xiaoymin</groupId>\n            <artifactId>knife4j-spring-boot-starter</artifactId>\n            <version>3.0.3</version>\n            <exclusions>\n                <!-- 去掉 swagger-annotations 依赖，避免冲突 -->\n                <exclusion>\n                    <groupId>io.swagger</groupId>\n                    <artifactId>swagger-annotations</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!-- 添加swagger-annotations依赖 -->\n        <dependency>\n            <groupId>io.swagger</groupId>\n            <artifactId>swagger-annotations</artifactId>\n            <version>1.5.22</version>\n        </dependency>\n\n        <!--Mysql依赖包-->\n        <dependency>\n            <groupId>com.mysql</groupId>\n            <artifactId>mysql-connector-j</artifactId>\n            <version>9.2.0</version>\n            <scope>runtime</scope>\n        </dependency>\n\n        <!-- druid数据源驱动 -->\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>druid-spring-boot-starter</artifactId>\n            <version>${druid.version}</version>\n        </dependency>\n\n        <!-- IP地址解析库 -->\n        <dependency>\n            <groupId>net.dreamlu</groupId>\n            <artifactId>mica-ip2region</artifactId>\n            <version>2.7.18.9</version>\n        </dependency>\n\n        <!--lombok插件-->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- excel工具 -->\n        <dependency>\n            <groupId>org.apache.poi</groupId>\n            <artifactId>poi</artifactId>\n            <version>5.4.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.poi</groupId>\n            <artifactId>poi-ooxml</artifactId>\n            <version>5.4.0</version>\n        </dependency>\n        <dependency>\n            <groupId>xerces</groupId>\n            <artifactId>xercesImpl</artifactId>\n            <version>2.12.2</version>\n        </dependency>\n\n        <!-- fastjson2 -->\n        <dependency>\n            <groupId>com.alibaba.fastjson2</groupId>\n            <artifactId>fastjson2</artifactId>\n            <version>${fastjson2.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alibaba.fastjson2</groupId>\n            <artifactId>fastjson2-extension-spring5</artifactId>\n            <version>${fastjson2.version}</version>\n        </dependency>\n\n        <!-- Java图形验证码 -->\n        <dependency>\n            <groupId>com.github.whvcse</groupId>\n            <artifactId>easy-captcha</artifactId>\n            <version>1.6.2</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-text</artifactId>\n            <version>1.13.0</version>\n        </dependency>\n\n        <!--mapStruct依赖-->\n        <dependency>\n            <groupId>org.mapstruct</groupId>\n            <artifactId>mapstruct</artifactId>\n            <version>${mapstruct.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.mapstruct</groupId>\n            <artifactId>mapstruct-processor</artifactId>\n            <version>${mapstruct.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.inject</groupId>\n            <artifactId>javax.inject</artifactId>\n            <version>1</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <!-- 打包时跳过测试 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <configuration>\n                    <skip>true</skip>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n    <repositories>\n        <repository>\n            <id>public</id>\n            <name>aliyun nexus</name>\n            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>\n            <releases>\n                <enabled>true</enabled>\n            </releases>\n        </repository>\n    </repositories>\n\n    <pluginRepositories>\n        <pluginRepository>\n            <id>public</id>\n            <name>aliyun nexus</name>\n            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>\n            <releases>\n                <enabled>true</enabled>\n            </releases>\n            <snapshots>\n                <enabled>false</enabled>\n            </snapshots>\n        </pluginRepository>\n    </pluginRepositories>\n</project>\n"
  },
  {
    "path": "sql/eladmin.sql",
    "content": "/*\n Navicat Premium Dump SQL\n\n Source Server         : localhost\n Source Server Type    : MariaDB\n Source Server Version : 110206 (11.2.6-MariaDB)\n Source Host           : localhost:3306\n Source Schema         : eladmin\n\n Target Server Type    : MariaDB\n Target Server Version : 110206 (11.2.6-MariaDB)\n File Encoding         : 65001\n\n Date: 25/06/2025 15:56:51\n*/\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for code_column\n-- ----------------------------\nDROP TABLE IF EXISTS `code_column`;\nCREATE TABLE `code_column` (\n  `column_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `table_name` varchar(180) DEFAULT NULL COMMENT '表名',\n  `column_name` varchar(255) DEFAULT NULL COMMENT '数据库字段名称',\n  `column_type` varchar(255) DEFAULT NULL COMMENT '数据库字段类型',\n  `dict_name` varchar(255) DEFAULT NULL COMMENT '字典名称',\n  `extra` varchar(255) DEFAULT NULL COMMENT '字段额外的参数',\n  `form_show` bit(1) DEFAULT NULL COMMENT '是否表单显示',\n  `form_type` varchar(255) DEFAULT NULL COMMENT '表单类型',\n  `key_type` varchar(255) DEFAULT NULL COMMENT '数据库字段键类型',\n  `list_show` bit(1) DEFAULT NULL COMMENT '是否在列表显示',\n  `not_null` bit(1) DEFAULT NULL COMMENT '是否为空',\n  `query_type` varchar(255) DEFAULT NULL COMMENT '查询类型',\n  `remark` varchar(255) DEFAULT NULL COMMENT '描述',\n  `date_annotation` varchar(255) DEFAULT NULL COMMENT '日期注解',\n  PRIMARY KEY (`column_id`) USING BTREE,\n  KEY `idx_table_name` (`table_name`)\n) ENGINE=InnoDB AUTO_INCREMENT=266 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='代码生成字段信息存储';\n\n-- ----------------------------\n-- Records of code_column\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for code_config\n-- ----------------------------\nDROP TABLE IF EXISTS `code_config`;\nCREATE TABLE `code_config` (\n  `config_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `table_name` varchar(255) DEFAULT NULL COMMENT '表名',\n  `author` varchar(255) DEFAULT NULL COMMENT '作者',\n  `cover` bit(1) DEFAULT NULL COMMENT '是否覆盖',\n  `module_name` varchar(255) DEFAULT NULL COMMENT '模块名称',\n  `pack` varchar(255) DEFAULT NULL COMMENT '至于哪个包下',\n  `path` varchar(255) DEFAULT NULL COMMENT '前端代码生成的路径',\n  `api_path` varchar(255) DEFAULT NULL COMMENT '前端Api文件路径',\n  `prefix` varchar(255) DEFAULT NULL COMMENT '表前缀',\n  `api_alias` varchar(255) DEFAULT NULL COMMENT '接口名称',\n  PRIMARY KEY (`config_id`) USING BTREE,\n  KEY `idx_table_name` (`table_name`(100))\n) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='代码生成器配置';\n\n-- ----------------------------\n-- Records of code_config\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for mnt_app\n-- ----------------------------\nDROP TABLE IF EXISTS `mnt_app`;\nCREATE TABLE `mnt_app` (\n  `app_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `name` varchar(255) DEFAULT NULL COMMENT '应用名称',\n  `upload_path` varchar(255) DEFAULT NULL COMMENT '上传目录',\n  `deploy_path` varchar(255) DEFAULT NULL COMMENT '部署路径',\n  `backup_path` varchar(255) DEFAULT NULL COMMENT '备份路径',\n  `port` int(255) DEFAULT NULL COMMENT '应用端口',\n  `start_script` varchar(4000) DEFAULT NULL COMMENT '启动脚本',\n  `deploy_script` varchar(4000) DEFAULT NULL COMMENT '部署脚本',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`app_id`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='应用管理';\n\n-- ----------------------------\n-- Records of mnt_app\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for mnt_database\n-- ----------------------------\nDROP TABLE IF EXISTS `mnt_database`;\nCREATE TABLE `mnt_database` (\n  `db_id` varchar(50) NOT NULL COMMENT 'ID',\n  `name` varchar(255) NOT NULL COMMENT '名称',\n  `jdbc_url` varchar(255) NOT NULL COMMENT 'jdbc连接',\n  `user_name` varchar(255) NOT NULL COMMENT '账号',\n  `pwd` varchar(255) NOT NULL COMMENT '密码',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`db_id`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='数据库管理';\n\n-- ----------------------------\n-- Records of mnt_database\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for mnt_deploy\n-- ----------------------------\nDROP TABLE IF EXISTS `mnt_deploy`;\nCREATE TABLE `mnt_deploy` (\n  `deploy_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `app_id` bigint(20) DEFAULT NULL COMMENT '应用编号',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL,\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`deploy_id`) USING BTREE,\n  KEY `idx_app_id` (`app_id`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='部署管理';\n\n-- ----------------------------\n-- Records of mnt_deploy\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for mnt_deploy_history\n-- ----------------------------\nDROP TABLE IF EXISTS `mnt_deploy_history`;\nCREATE TABLE `mnt_deploy_history` (\n  `history_id` varchar(50) NOT NULL COMMENT 'ID',\n  `app_name` varchar(255) NOT NULL COMMENT '应用名称',\n  `deploy_date` datetime NOT NULL COMMENT '部署日期',\n  `deploy_user` varchar(50) NOT NULL COMMENT '部署用户',\n  `ip` varchar(20) NOT NULL COMMENT '服务器IP',\n  `deploy_id` bigint(20) DEFAULT NULL COMMENT '部署编号',\n  PRIMARY KEY (`history_id`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='部署历史管理';\n\n-- ----------------------------\n-- Records of mnt_deploy_history\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for mnt_deploy_server\n-- ----------------------------\nDROP TABLE IF EXISTS `mnt_deploy_server`;\nCREATE TABLE `mnt_deploy_server` (\n  `deploy_id` bigint(20) NOT NULL COMMENT '部署ID',\n  `server_id` bigint(20) NOT NULL COMMENT '服务ID',\n  PRIMARY KEY (`deploy_id`,`server_id`) USING BTREE,\n  KEY `idx_deploy_id` (`deploy_id`),\n  KEY `idx_server_id` (`server_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='应用与服务器关联';\n\n-- ----------------------------\n-- Records of mnt_deploy_server\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for mnt_server\n-- ----------------------------\nDROP TABLE IF EXISTS `mnt_server`;\nCREATE TABLE `mnt_server` (\n  `server_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `account` varchar(50) DEFAULT NULL COMMENT '账号',\n  `ip` varchar(20) DEFAULT NULL COMMENT 'IP地址',\n  `name` varchar(100) DEFAULT NULL COMMENT '名称',\n  `password` varchar(100) DEFAULT NULL COMMENT '密码',\n  `port` int(11) DEFAULT NULL COMMENT '端口',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`server_id`) USING BTREE,\n  KEY `idx_ip` (`ip`)\n) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='服务器管理';\n\n-- ----------------------------\n-- Records of mnt_server\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dept`;\nCREATE TABLE `sys_dept` (\n  `dept_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `pid` bigint(20) DEFAULT NULL COMMENT '上级部门',\n  `sub_count` int(5) DEFAULT 0 COMMENT '子部门数目',\n  `name` varchar(255) NOT NULL COMMENT '名称',\n  `dept_sort` int(5) DEFAULT 999 COMMENT '排序',\n  `enabled` bit(1) NOT NULL COMMENT '状态',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`dept_id`) USING BTREE,\n  KEY `idx_pid` (`pid`),\n  KEY `idx_enabled` (`enabled`)\n) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='部门';\n\n-- ----------------------------\n-- Records of sys_dept\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 7, 1, '研发部', 3, b'1', 'admin', 'admin', '2019-03-25 09:15:32', '2020-08-02 14:48:47');\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 7, 0, '运维部', 4, b'1', 'admin', 'admin', '2019-03-25 09:20:44', '2020-05-17 14:27:27');\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, 8, 0, '测试部', 6, b'1', 'admin', 'admin', '2019-03-25 09:52:18', '2020-06-08 11:59:21');\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (7, NULL, 2, '华南分部', 0, b'1', 'admin', 'admin', '2019-03-25 11:04:50', '2020-06-08 12:08:56');\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (8, NULL, 2, '华北分部', 1, b'1', 'admin', 'admin', '2019-03-25 11:04:53', '2020-05-14 12:54:00');\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (15, 8, 0, 'UI部门', 7, b'1', 'admin', 'admin', '2020-05-13 22:56:53', '2020-05-14 12:54:13');\nINSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (17, 2, 0, '研发一组', 999, b'1', 'admin', 'admin', '2020-08-02 14:49:07', '2020-08-02 14:49:07');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dict\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dict`;\nCREATE TABLE `sys_dict` (\n  `dict_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `name` varchar(255) NOT NULL COMMENT '字典名称',\n  `description` varchar(255) DEFAULT NULL COMMENT '描述',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`dict_id`) USING BTREE\n) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='数据字典';\n\n-- ----------------------------\n-- Records of sys_dict\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dict` (`dict_id`, `name`, `description`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, 'user_status', '用户状态', NULL, NULL, '2019-10-27 20:31:36', NULL);\nINSERT INTO `sys_dict` (`dict_id`, `name`, `description`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 'dept_status', '部门状态', NULL, NULL, '2019-10-27 20:31:36', NULL);\nINSERT INTO `sys_dict` (`dict_id`, `name`, `description`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 'job_status', '岗位状态', NULL, 'admin', '2019-10-27 20:31:36', '2025-01-14 15:48:29');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dict_detail\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dict_detail`;\nCREATE TABLE `sys_dict_detail` (\n  `detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `dict_id` bigint(11) DEFAULT NULL COMMENT '字典id',\n  `label` varchar(255) NOT NULL COMMENT '字典标签',\n  `value` varchar(255) NOT NULL COMMENT '字典值',\n  `dict_sort` int(5) DEFAULT NULL COMMENT '排序',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`detail_id`) USING BTREE,\n  KEY `idx_dict_id` (`dict_id`) USING BTREE\n) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='数据字典详情';\n\n-- ----------------------------\n-- Records of sys_dict_detail\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, 1, '激活', 'true', 1, NULL, NULL, '2019-10-27 20:31:36', NULL);\nINSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 1, '禁用', 'false', 2, NULL, NULL, NULL, NULL);\nINSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (3, 4, '启用', 'true', 1, NULL, NULL, NULL, NULL);\nINSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 4, '停用', 'false', 2, NULL, NULL, '2019-10-27 20:31:36', NULL);\nINSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 5, '启用', 'true', 1, NULL, NULL, NULL, NULL);\nINSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, 5, '停用', 'false', 2, NULL, NULL, '2019-10-27 20:31:36', NULL);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_job\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_job`;\nCREATE TABLE `sys_job` (\n  `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `name` varchar(180) NOT NULL COMMENT '岗位名称',\n  `enabled` bit(1) NOT NULL COMMENT '岗位状态',\n  `job_sort` int(5) DEFAULT NULL COMMENT '排序',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`job_id`) USING BTREE,\n  UNIQUE KEY `uniq_name` (`name`),\n  KEY `idx_enabled` (`enabled`)\n) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='岗位';\n\n-- ----------------------------\n-- Records of sys_job\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (8, '人事专员', b'1', 3, NULL, NULL, '2019-03-29 14:52:28', NULL);\nINSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (10, '产品经理', b'1', 4, NULL, NULL, '2019-03-29 14:55:51', NULL);\nINSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (11, '全栈开发', b'1', 2, NULL, 'admin', '2019-03-31 13:39:30', '2020-05-05 11:33:43');\nINSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (12, '软件测试', b'1', 5, NULL, 'admin', '2019-03-31 13:39:43', '2020-05-10 19:56:26');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_log`;\nCREATE TABLE `sys_log` (\n  `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `description` varchar(255) DEFAULT NULL COMMENT '描述',\n  `log_type` varchar(10) NOT NULL COMMENT '日志类型：INFI/ERROR',\n  `method` varchar(255) DEFAULT NULL COMMENT '方法名',\n  `params` text DEFAULT NULL COMMENT '参数',\n  `request_ip` varchar(255) DEFAULT NULL COMMENT '请求IP',\n  `time` bigint(20) DEFAULT NULL COMMENT '执行时间',\n  `username` varchar(255) DEFAULT NULL COMMENT '用户名',\n  `address` varchar(255) DEFAULT NULL COMMENT '地址',\n  `browser` varchar(255) DEFAULT NULL COMMENT '浏览器',\n  `exception_detail` text DEFAULT NULL COMMENT '异常',\n  `create_time` datetime NOT NULL COMMENT '创建时间',\n  PRIMARY KEY (`log_id`) USING BTREE,\n  KEY `idx_create_time_index` (`create_time`),\n  KEY `idx_log_type` (`log_type`)\n) ENGINE=InnoDB AUTO_INCREMENT=3636 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='系统日志';\n\n-- ----------------------------\n-- Records of sys_log\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_menu`;\nCREATE TABLE `sys_menu` (\n  `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `pid` bigint(20) DEFAULT NULL COMMENT '上级菜单ID',\n  `sub_count` int(5) DEFAULT 0 COMMENT '子菜单数目',\n  `type` int(11) DEFAULT NULL COMMENT '菜单类型',\n  `title` varchar(100) DEFAULT NULL COMMENT '菜单标题',\n  `name` varchar(100) DEFAULT NULL COMMENT '组件名称',\n  `component` varchar(255) DEFAULT NULL COMMENT '组件',\n  `menu_sort` int(5) DEFAULT NULL COMMENT '排序',\n  `icon` varchar(255) DEFAULT NULL COMMENT '图标',\n  `path` varchar(255) DEFAULT NULL COMMENT '链接地址',\n  `i_frame` bit(1) DEFAULT NULL COMMENT '是否外链',\n  `cache` bit(1) DEFAULT b'0' COMMENT '缓存',\n  `hidden` bit(1) DEFAULT b'0' COMMENT '隐藏',\n  `permission` varchar(255) DEFAULT NULL COMMENT '权限',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`menu_id`) USING BTREE,\n  UNIQUE KEY `uniq_name` (`name`),\n  UNIQUE KEY `uniq_title` (`title`),\n  KEY `idx_pid` (`pid`)\n) ENGINE=InnoDB AUTO_INCREMENT=117 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='系统菜单';\n\n-- ----------------------------\n-- Records of sys_menu\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, NULL, 7, 0, '系统管理', NULL, NULL, 1, 'system', 'system', b'0', b'0', b'0', NULL, NULL, 'admin', '2018-12-18 15:11:29', '2025-01-14 15:48:18');\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 1, 3, 1, '用户管理', 'User', 'system/user/index', 2, 'peoples', 'user', b'0', b'0', b'0', 'user:list', NULL, NULL, '2018-12-18 15:14:44', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (3, 1, 3, 1, '角色管理', 'Role', 'system/role/index', 3, 'role', 'role', b'0', b'0', b'0', 'roles:list', NULL, NULL, '2018-12-18 15:16:07', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 1, 3, 1, '菜单管理', 'Menu', 'system/menu/index', 5, 'menu', 'menu', b'0', b'0', b'0', 'menu:list', NULL, NULL, '2018-12-18 15:17:28', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, NULL, 5, 0, '系统监控', NULL, NULL, 10, 'monitor', 'monitor', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-18 15:17:48', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (7, 6, 0, 1, '操作日志', 'Log', 'monitor/log/index', 11, 'log', 'logs', b'0', b'1', b'0', NULL, NULL, 'admin', '2018-12-18 15:18:26', '2020-06-06 13:11:57');\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (9, 6, 0, 1, 'SQL监控', 'Sql', 'monitor/sql/index', 18, 'sqlMonitor', 'druid', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-18 15:19:34', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (10, NULL, 5, 0, '组件管理', NULL, NULL, 50, 'zujian', 'components', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-19 13:38:16', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (11, 10, 0, 1, '图标库', 'Icons', 'components/icons/index', 51, 'icon', 'icon', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-19 13:38:49', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (14, 36, 0, 1, '邮件工具', 'Email', 'tools/email/index', 35, 'email', 'email', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-27 10:13:09', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (15, 10, 0, 1, '富文本', 'Editor', 'components/Editor', 52, 'fwb', 'tinymce', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-27 11:58:25', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (18, 36, 3, 1, '存储管理', 'Storage', 'tools/storage/index', 34, 'qiniu', 'storage', b'0', b'0', b'0', 'storage:list', NULL, NULL, '2018-12-31 11:12:15', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (19, 36, 0, 1, '支付宝工具', 'AliPay', 'tools/aliPay/index', 37, 'alipay', 'aliPay', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-31 14:52:38', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (21, NULL, 2, 0, '多级菜单', NULL, '', 900, 'menu', 'nested', b'0', b'0', b'0', NULL, NULL, 'admin', '2019-01-04 16:22:03', '2020-06-21 17:27:35');\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (22, 21, 2, 0, '二级菜单1', NULL, '', 999, 'menu', 'menu1', b'0', b'0', b'0', NULL, NULL, 'admin', '2019-01-04 16:23:29', '2020-06-21 17:27:20');\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (23, 21, 0, 1, '二级菜单2', NULL, 'nested/menu2/index', 999, 'menu', 'menu2', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-04 16:23:57', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (24, 22, 0, 1, '三级菜单1', 'Test', 'nested/menu1/menu1-1', 999, 'menu', 'menu1-1', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-04 16:24:48', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (27, 22, 0, 1, '三级菜单2', NULL, 'nested/menu1/menu1-2', 999, 'menu', 'menu1-2', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-07 17:27:32', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (28, 1, 3, 1, '任务调度', 'Timing', 'system/timing/index', 999, 'timing', 'timing', b'0', b'0', b'0', 'timing:list', NULL, NULL, '2019-01-07 20:34:40', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (30, 36, 0, 1, '代码生成', 'GeneratorIndex', 'generator/index', 32, 'dev', 'generator', b'0', b'1', b'0', NULL, NULL, NULL, '2019-01-11 15:45:55', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (32, 6, 0, 1, '异常日志', 'ErrorLog', 'monitor/log/errorLog', 12, 'error', 'errorLog', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-13 13:49:03', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (33, 10, 0, 1, 'Markdown', 'Markdown', 'components/MarkDown', 53, 'markdown', 'markdown', b'0', b'0', b'0', NULL, NULL, NULL, '2019-03-08 13:46:44', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (34, 10, 0, 1, 'Yaml编辑器', 'YamlEdit', 'components/YamlEdit', 54, 'dev', 'yaml', b'0', b'0', b'0', NULL, NULL, NULL, '2019-03-08 15:49:40', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (35, 1, 3, 1, '部门管理', 'Dept', 'system/dept/index', 6, 'dept', 'dept', b'0', b'0', b'0', 'dept:list', NULL, NULL, '2019-03-25 09:46:00', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (36, NULL, 6, 0, '系统工具', NULL, '', 30, 'sys-tools', 'sys-tools', b'0', b'0', b'0', NULL, NULL, NULL, '2019-03-29 10:57:35', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (37, 1, 3, 1, '岗位管理', 'Job', 'system/job/index', 7, 'Steve-Jobs', 'job', b'0', b'0', b'0', 'job:list', NULL, NULL, '2019-03-29 13:51:18', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (39, 1, 3, 1, '字典管理', 'Dict', 'system/dict/index', 8, 'dictionary', 'dict', b'0', b'0', b'0', 'dict:list', NULL, NULL, '2019-04-10 11:49:04', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (41, 6, 0, 1, '在线用户', 'OnlineUser', 'monitor/online/index', 10, 'Steve-Jobs', 'online', b'0', b'0', b'0', NULL, NULL, NULL, '2019-10-26 22:08:43', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (44, 2, 0, 2, '用户新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'user:add', NULL, NULL, '2019-10-29 10:59:46', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (45, 2, 0, 2, '用户编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'user:edit', NULL, NULL, '2019-10-29 11:00:08', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (46, 2, 0, 2, '用户删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'user:del', NULL, NULL, '2019-10-29 11:00:23', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (48, 3, 0, 2, '角色创建', NULL, '', 2, '', '', b'0', b'0', b'0', 'roles:add', NULL, NULL, '2019-10-29 12:45:34', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (49, 3, 0, 2, '角色修改', NULL, '', 3, '', '', b'0', b'0', b'0', 'roles:edit', NULL, NULL, '2019-10-29 12:46:16', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (50, 3, 0, 2, '角色删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'roles:del', NULL, NULL, '2019-10-29 12:46:51', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (52, 5, 0, 2, '菜单新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'menu:add', NULL, NULL, '2019-10-29 12:55:07', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (53, 5, 0, 2, '菜单编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'menu:edit', NULL, NULL, '2019-10-29 12:55:40', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (54, 5, 0, 2, '菜单删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'menu:del', NULL, NULL, '2019-10-29 12:56:00', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (56, 35, 0, 2, '部门新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'dept:add', NULL, NULL, '2019-10-29 12:57:09', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (57, 35, 0, 2, '部门编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'dept:edit', NULL, NULL, '2019-10-29 12:57:27', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (58, 35, 0, 2, '部门删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'dept:del', NULL, NULL, '2019-10-29 12:57:41', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (60, 37, 0, 2, '岗位新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'job:add', NULL, NULL, '2019-10-29 12:58:27', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (61, 37, 0, 2, '岗位编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'job:edit', NULL, NULL, '2019-10-29 12:58:45', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (62, 37, 0, 2, '岗位删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'job:del', NULL, NULL, '2019-10-29 12:59:04', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (64, 39, 0, 2, '字典新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'dict:add', NULL, NULL, '2019-10-29 13:00:17', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (65, 39, 0, 2, '字典编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'dict:edit', NULL, NULL, '2019-10-29 13:00:42', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (66, 39, 0, 2, '字典删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'dict:del', NULL, NULL, '2019-10-29 13:00:59', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (73, 28, 0, 2, '任务新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'timing:add', NULL, NULL, '2019-10-29 13:07:28', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (74, 28, 0, 2, '任务编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'timing:edit', NULL, NULL, '2019-10-29 13:07:41', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (75, 28, 0, 2, '任务删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'timing:del', NULL, NULL, '2019-10-29 13:07:54', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (77, 18, 0, 2, '上传文件', NULL, '', 2, '', '', b'0', b'0', b'0', 'storage:add', NULL, NULL, '2019-10-29 13:09:09', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (78, 18, 0, 2, '文件编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'storage:edit', NULL, NULL, '2019-10-29 13:09:22', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (79, 18, 0, 2, '文件删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'storage:del', NULL, NULL, '2019-10-29 13:09:34', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (80, 6, 0, 1, '服务监控', 'ServerMonitor', 'monitor/server/index', 14, 'codeConsole', 'server', b'0', b'0', b'0', 'monitor:list', NULL, 'admin', '2019-11-07 13:06:39', '2020-05-04 18:20:50');\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (82, 36, 0, 1, '生成配置', 'GeneratorConfig', 'generator/config', 33, 'dev', 'generator/config/:tableName', b'0', b'1', b'1', '', NULL, NULL, '2019-11-17 20:08:56', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (83, 10, 0, 1, '图表库', 'Echarts', 'components/Echarts', 50, 'chart', 'echarts', b'0', b'1', b'0', '', NULL, NULL, '2019-11-21 09:04:32', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (90, NULL, 5, 1, '运维管理', 'Mnt', '', 20, 'mnt', 'mnt', b'0', b'0', b'0', NULL, NULL, NULL, '2019-11-09 10:31:08', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (92, 90, 3, 1, '服务器', 'ServerDeploy', 'maint/server/index', 22, 'server', 'maint/serverDeploy', b'0', b'0', b'0', 'serverDeploy:list', NULL, NULL, '2019-11-10 10:29:25', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (93, 90, 3, 1, '应用管理', 'App', 'maint/app/index', 23, 'app', 'maint/app', b'0', b'0', b'0', 'app:list', NULL, NULL, '2019-11-10 11:05:16', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (94, 90, 3, 1, '部署管理', 'Deploy', 'maint/deploy/index', 24, 'deploy', 'maint/deploy', b'0', b'0', b'0', 'deploy:list', NULL, NULL, '2019-11-10 15:56:55', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (97, 90, 1, 1, '部署备份', 'DeployHistory', 'maint/deployHistory/index', 25, 'backup', 'maint/deployHistory', b'0', b'0', b'0', 'deployHistory:list', NULL, NULL, '2019-11-10 16:49:44', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (98, 90, 3, 1, '数据库管理', 'Database', 'maint/database/index', 26, 'database', 'maint/database', b'0', b'0', b'0', 'database:list', NULL, NULL, '2019-11-10 20:40:04', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (102, 97, 0, 2, '删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'deployHistory:del', NULL, NULL, '2019-11-17 09:32:48', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (103, 92, 0, 2, '服务器新增', NULL, '', 999, '', '', b'0', b'0', b'0', 'serverDeploy:add', NULL, NULL, '2019-11-17 11:08:33', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (104, 92, 0, 2, '服务器编辑', NULL, '', 999, '', '', b'0', b'0', b'0', 'serverDeploy:edit', NULL, NULL, '2019-11-17 11:08:57', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (105, 92, 0, 2, '服务器删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'serverDeploy:del', NULL, NULL, '2019-11-17 11:09:15', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (106, 93, 0, 2, '应用新增', NULL, '', 999, '', '', b'0', b'0', b'0', 'app:add', NULL, NULL, '2019-11-17 11:10:03', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (107, 93, 0, 2, '应用编辑', NULL, '', 999, '', '', b'0', b'0', b'0', 'app:edit', NULL, NULL, '2019-11-17 11:10:28', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (108, 93, 0, 2, '应用删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'app:del', NULL, NULL, '2019-11-17 11:10:55', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (109, 94, 0, 2, '部署新增', NULL, '', 999, '', '', b'0', b'0', b'0', 'deploy:add', NULL, NULL, '2019-11-17 11:11:22', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (110, 94, 0, 2, '部署编辑', NULL, '', 999, '', '', b'0', b'0', b'0', 'deploy:edit', NULL, NULL, '2019-11-17 11:11:41', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (111, 94, 0, 2, '部署删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'deploy:del', NULL, NULL, '2019-11-17 11:12:01', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (116, 36, 0, 1, '生成预览', 'Preview', 'generator/preview', 999, 'java', 'generator/preview/:tableName', b'0', b'1', b'1', NULL, NULL, NULL, '2019-11-26 14:54:36', NULL);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_quartz_job\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_quartz_job`;\nCREATE TABLE `sys_quartz_job` (\n  `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `bean_name` varchar(255) DEFAULT NULL COMMENT 'Spring Bean名称',\n  `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron 表达式',\n  `is_pause` bit(1) DEFAULT NULL COMMENT '状态：1暂停、0启用',\n  `job_name` varchar(255) DEFAULT NULL COMMENT '任务名称',\n  `method_name` varchar(255) DEFAULT NULL COMMENT '方法名称',\n  `params` varchar(255) DEFAULT NULL COMMENT '参数',\n  `description` varchar(255) DEFAULT NULL COMMENT '备注',\n  `person_in_charge` varchar(100) DEFAULT NULL COMMENT '负责人',\n  `email` varchar(100) DEFAULT NULL COMMENT '报警邮箱',\n  `sub_task` varchar(100) DEFAULT NULL COMMENT '子任务ID',\n  `pause_after_failure` bit(1) DEFAULT NULL COMMENT '任务失败后是否暂停',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`job_id`) USING BTREE,\n  KEY `idx_is_pause` (`is_pause`)\n) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='定时任务';\n\n-- ----------------------------\n-- Records of sys_quartz_job\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 'testTask', '0/5 * * * * ?', b'1', '测试1', 'run1', 'test', '带参测试，多参使用json', '测试', NULL, NULL, NULL, NULL, 'admin', '2019-08-22 14:08:29', '2020-05-24 13:58:33');\nINSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (3, 'testTask', '0/5 * * * * ?', b'1', '测试', 'run', '', '不带参测试', 'Zheng Jie', '', '6', b'1', NULL, 'admin', '2019-09-26 16:44:39', '2020-05-24 14:48:12');\nINSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 'Test', '0/5 * * * * ?', b'1', '任务告警测试', 'run', NULL, '测试', 'test', '', NULL, b'1', 'admin', 'admin', '2020-05-05 20:32:41', '2020-05-05 20:36:13');\nINSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, 'testTask', '0/5 * * * * ?', b'1', '测试3', 'run2', NULL, '测试3', 'Zheng Jie', '', NULL, b'1', 'admin', 'admin', '2020-05-05 20:35:41', '2020-05-05 20:36:07');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_quartz_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_quartz_log`;\nCREATE TABLE `sys_quartz_log` (\n  `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `bean_name` varchar(255) DEFAULT NULL COMMENT 'Bean名称',\n  `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron 表达式',\n  `is_success` bit(1) DEFAULT NULL COMMENT '是否执行成功',\n  `job_name` varchar(255) DEFAULT NULL COMMENT '任务名称',\n  `method_name` varchar(255) DEFAULT NULL COMMENT '方法名称',\n  `params` varchar(255) DEFAULT NULL COMMENT '参数',\n  `time` bigint(20) DEFAULT NULL COMMENT '执行耗时',\n  `exception_detail` text DEFAULT NULL COMMENT '异常详情',\n  `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n  PRIMARY KEY (`log_id`) USING BTREE\n) ENGINE=InnoDB AUTO_INCREMENT=262 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='定时任务日志';\n\n-- ----------------------------\n-- Records of sys_quartz_log\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_role`;\nCREATE TABLE `sys_role` (\n  `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `name` varchar(100) NOT NULL COMMENT '名称',\n  `level` int(50) DEFAULT NULL COMMENT '角色级别',\n  `description` varchar(255) DEFAULT NULL COMMENT '描述',\n  `data_scope` varchar(255) DEFAULT NULL COMMENT '数据权限',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`role_id`) USING BTREE,\n  UNIQUE KEY `uniq_name` (`name`),\n  KEY `idx_level` (`level`)\n) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色表';\n\n-- ----------------------------\n-- Records of sys_role\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, '管理员', 1, '-', '全部', NULL, 'admin', '2018-11-23 11:04:37', '2025-01-21 14:53:13');\nINSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, '普通用户', 2, '-', '本级', NULL, 'admin', '2018-11-23 13:09:06', '2020-09-05 10:45:12');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_roles_depts\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_roles_depts`;\nCREATE TABLE `sys_roles_depts` (\n  `role_id` bigint(20) NOT NULL COMMENT '角色ID',\n  `dept_id` bigint(20) NOT NULL COMMENT '部门ID',\n  PRIMARY KEY (`role_id`,`dept_id`) USING BTREE,\n  KEY `idx_role_id` (`role_id`),\n  KEY `idx_dept_id` (`dept_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色部门关联';\n\n-- ----------------------------\n-- Records of sys_roles_depts\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_roles_menus\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_roles_menus`;\nCREATE TABLE `sys_roles_menus` (\n  `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',\n  `role_id` bigint(20) NOT NULL COMMENT '角色ID',\n  PRIMARY KEY (`menu_id`,`role_id`) USING BTREE,\n  KEY `idx_menu_id` (`menu_id`),\n  KEY `idx_role_id` (`role_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色菜单关联';\n\n-- ----------------------------\n-- Records of sys_roles_menus\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (1, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (1, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (2, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (2, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (3, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (5, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (6, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (6, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (7, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (7, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (9, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (9, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (10, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (10, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (11, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (11, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (14, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (14, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (15, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (15, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (18, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (19, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (19, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (21, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (21, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (22, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (22, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (23, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (23, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (24, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (24, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (27, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (27, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (28, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (30, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (30, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (32, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (32, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (33, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (33, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (34, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (34, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (35, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (36, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (36, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (37, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (39, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (41, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (44, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (45, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (46, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (48, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (49, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (50, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (52, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (53, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (54, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (56, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (57, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (58, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (60, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (61, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (62, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (64, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (65, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (66, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (73, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (74, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (75, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (77, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (78, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (79, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (80, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (80, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (82, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (82, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (83, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (83, 2);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (90, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (92, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (93, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (94, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (97, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (98, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (102, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (103, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (104, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (105, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (106, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (107, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (108, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (109, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (110, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (111, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (116, 1);\nINSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (116, 2);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_user\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user`;\nCREATE TABLE `sys_user` (\n  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `dept_id` bigint(20) DEFAULT NULL COMMENT '部门名称',\n  `username` varchar(180) DEFAULT NULL COMMENT '用户名',\n  `nick_name` varchar(255) DEFAULT NULL COMMENT '昵称',\n  `gender` varchar(2) DEFAULT NULL COMMENT '性别',\n  `phone` varchar(255) DEFAULT NULL COMMENT '手机号码',\n  `email` varchar(180) DEFAULT NULL COMMENT '邮箱',\n  `avatar_name` varchar(255) DEFAULT NULL COMMENT '头像地址',\n  `avatar_path` varchar(255) DEFAULT NULL COMMENT '头像真实路径',\n  `password` varchar(255) DEFAULT NULL COMMENT '密码',\n  `is_admin` bit(1) DEFAULT b'0' COMMENT '是否为admin账号',\n  `enabled` bit(1) DEFAULT NULL COMMENT '状态：1启用、0禁用',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `pwd_reset_time` datetime DEFAULT NULL COMMENT '修改密码的时间',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`user_id`) USING BTREE,\n  UNIQUE KEY `uniq_email` (`email`) USING BTREE,\n  UNIQUE KEY `uniq_username` (`username`) USING BTREE,\n  KEY `idx_dept_id` (`dept_id`) USING BTREE,\n  KEY `idx_enabled` (`enabled`)\n) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='系统用户';\n\n-- ----------------------------\n-- Records of sys_user\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (1, 2, 'admin', '管理员', '男', '18888888888', '201507802@qq.com', 'avatar-20250122102642222.png', '/Users/jie/Documents/work/private/eladmin/~/avatar/avatar-20250122102642222.png', '$2a$10$Egp1/gvFlt7zhlXVfEFw4OfWQCGPw0ClmMcc6FjTnvXNRVf9zdMRa', b'1', b'1', NULL, 'admin', '2020-05-03 16:38:31', '2018-08-23 09:11:56', '2025-01-22 10:26:42');\nINSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (2, 7, 'test', '测试', '男', '19999999999', '231@qq.com', NULL, NULL, '$2a$10$4XcyudOYTSz6fue6KFNMHeUQnCX5jbBQypLEnGk1PmekXt5c95JcK', b'0', b'1', 'admin', 'admin', NULL, '2020-05-05 11:15:49', '2025-01-21 14:53:04');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_users_jobs\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_users_jobs`;\nCREATE TABLE `sys_users_jobs` (\n  `user_id` bigint(20) NOT NULL COMMENT '用户ID',\n  `job_id` bigint(20) NOT NULL COMMENT '岗位ID',\n  PRIMARY KEY (`user_id`,`job_id`),\n  KEY `idx_user_id` (`user_id`),\n  KEY `idx_job_id` (`job_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci COMMENT='用户与岗位关联表';\n\n-- ----------------------------\n-- Records of sys_users_jobs\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_users_jobs` (`user_id`, `job_id`) VALUES (1, 11);\nINSERT INTO `sys_users_jobs` (`user_id`, `job_id`) VALUES (2, 12);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_users_roles\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_users_roles`;\nCREATE TABLE `sys_users_roles` (\n  `user_id` bigint(20) NOT NULL COMMENT '用户ID',\n  `role_id` bigint(20) NOT NULL COMMENT '角色ID',\n  PRIMARY KEY (`user_id`,`role_id`) USING BTREE,\n  KEY `idx_user_id` (`user_id`),\n  KEY `idx_role_id` (`role_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='用户角色关联';\n\n-- ----------------------------\n-- Records of sys_users_roles\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_users_roles` (`user_id`, `role_id`) VALUES (1, 1);\nINSERT INTO `sys_users_roles` (`user_id`, `role_id`) VALUES (2, 2);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for tool_alipay_config\n-- ----------------------------\nDROP TABLE IF EXISTS `tool_alipay_config`;\nCREATE TABLE `tool_alipay_config` (\n  `config_id` bigint(20) NOT NULL COMMENT 'ID',\n  `app_id` varchar(255) DEFAULT NULL COMMENT '应用ID',\n  `charset` varchar(255) DEFAULT NULL COMMENT '编码',\n  `format` varchar(255) DEFAULT NULL COMMENT '类型 固定格式json',\n  `gateway_url` varchar(255) DEFAULT NULL COMMENT '网关地址',\n  `notify_url` varchar(255) DEFAULT NULL COMMENT '异步回调',\n  `private_key` text DEFAULT NULL COMMENT '私钥',\n  `public_key` text DEFAULT NULL COMMENT '公钥',\n  `return_url` varchar(255) DEFAULT NULL COMMENT '回调地址',\n  `sign_type` varchar(255) DEFAULT NULL COMMENT '签名方式',\n  `sys_service_provider_id` varchar(255) DEFAULT NULL COMMENT '商户号',\n  PRIMARY KEY (`config_id`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='支付宝配置类';\n\n-- ----------------------------\n-- Records of tool_alipay_config\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for tool_email_config\n-- ----------------------------\nDROP TABLE IF EXISTS `tool_email_config`;\nCREATE TABLE `tool_email_config` (\n  `config_id` bigint(20) NOT NULL COMMENT 'ID',\n  `from_user` varchar(255) DEFAULT NULL COMMENT '收件人',\n  `host` varchar(255) DEFAULT NULL COMMENT '邮件服务器SMTP地址',\n  `pass` varchar(255) DEFAULT NULL COMMENT '密码',\n  `port` varchar(255) DEFAULT NULL COMMENT '端口',\n  `user` varchar(255) DEFAULT NULL COMMENT '发件者用户名',\n  PRIMARY KEY (`config_id`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='邮箱配置';\n\n-- ----------------------------\n-- Records of tool_email_config\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for tool_local_storage\n-- ----------------------------\nDROP TABLE IF EXISTS `tool_local_storage`;\nCREATE TABLE `tool_local_storage` (\n  `storage_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `real_name` varchar(255) DEFAULT NULL COMMENT '文件真实的名称',\n  `name` varchar(255) DEFAULT NULL COMMENT '文件名',\n  `suffix` varchar(255) DEFAULT NULL COMMENT '后缀',\n  `path` varchar(255) DEFAULT NULL COMMENT '路径',\n  `type` varchar(255) DEFAULT NULL COMMENT '类型',\n  `size` varchar(100) DEFAULT NULL COMMENT '大小',\n  `create_by` varchar(255) DEFAULT NULL COMMENT '创建者',\n  `update_by` varchar(255) DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime DEFAULT NULL COMMENT '创建日期',\n  `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n  PRIMARY KEY (`storage_id`) USING BTREE\n) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='本地存储';\n\n-- ----------------------------\n-- Records of tool_local_storage\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for tool_s3_storage\n-- ----------------------------\nDROP TABLE IF EXISTS `tool_s3_storage`;\nCREATE TABLE `tool_s3_storage` (\n  `storage_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `file_name` varchar(255) NOT NULL COMMENT '文件名称',\n  `file_real_name` varchar(255) NOT NULL COMMENT '真实存储的名称',\n  `file_size` varchar(100) NOT NULL COMMENT '文件大小',\n  `file_mime_type` varchar(50) NOT NULL COMMENT '文件MIME 类型',\n  `file_type` varchar(50) NOT NULL COMMENT '文件类型',\n  `file_path` tinytext NOT NULL COMMENT '文件路径',\n  `create_by` varchar(255) NOT NULL COMMENT '创建者',\n  `update_by` varchar(255) NOT NULL COMMENT '更新者',\n  `create_time` datetime NOT NULL COMMENT '创建日期',\n  `update_time` datetime NOT NULL COMMENT '更新时间',\n  PRIMARY KEY (`storage_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='s3 协议对象存储';\n\n-- ----------------------------\n-- Records of tool_s3_storage\n-- ----------------------------\nBEGIN;\nINSERT INTO `tool_s3_storage` (`storage_id`, `file_name`, `file_real_name`, `file_size`, `file_mime_type`, `file_type`, `file_path`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 'tx.jpg', '2ca1de24d8fa422eae4ede30e97c46d8.jpg', '29.67KB', 'image/jpeg', 'jpg', '2025-06/2ca1de24d8fa422eae4ede30e97c46d8.jpg', 'admin', 'admin', '2025-06-25 15:48:22', '2025-06-25 15:48:22');\nCOMMIT;\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "sql/quartz.sql",
    "content": "drop 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\ncreate table qrtz_job_details(\nsched_name varchar(120) not null,\njob_name varchar(200) not null,\njob_group varchar(200) not null,\ndescription varchar(250) null,\njob_class_name varchar(250) not null,\nis_durable varchar(1) not null,\nis_nonconcurrent varchar(1) not null,\nis_update_data varchar(1) not null,\nrequests_recovery varchar(1) not null,\njob_data blob null,\nprimary key (sched_name, job_name, job_group))\nengine=innodb;\n\ncreate table qrtz_triggers (\nsched_name varchar(120) not null,\ntrigger_name varchar(200) not null,\ntrigger_group varchar(200) not null,\njob_name varchar(200) not null,\njob_group varchar(200) not null,\ndescription varchar(250) null,\nnext_fire_time bigint(13) null,\nprev_fire_time bigint(13) null,\npriority integer null,\ntrigger_state varchar(16) not null,\ntrigger_type varchar(8) not null,\nstart_time bigint(13) not null,\nend_time bigint(13) null,\ncalendar_name varchar(200) null,\nmisfire_instr smallint(2) null,\njob_data blob null,\nprimary key (sched_name, trigger_name, trigger_group),\nforeign key (sched_name, job_name, job_group)\nreferences qrtz_job_details(sched_name, job_name, job_group))\nengine=innodb;\n\ncreate table qrtz_simple_triggers (\nsched_name varchar(120) not null,\ntrigger_name varchar(200) not null,\ntrigger_group varchar(200) not null,\nrepeat_count bigint(7) not null,\nrepeat_interval bigint(12) not null,\ntimes_triggered bigint(10) not null,\nprimary key (sched_name, trigger_name, trigger_group),\nforeign key (sched_name, trigger_name, trigger_group)\nreferences qrtz_triggers(sched_name, trigger_name, trigger_group))\nengine=innodb;\n\ncreate table qrtz_cron_triggers (\nsched_name varchar(120) not null,\ntrigger_name varchar(200) not null,\ntrigger_group varchar(200) not null,\ncron_expression varchar(120) not null,\ntime_zone_id varchar(80),\nprimary key (sched_name, trigger_name, trigger_group),\nforeign key (sched_name, trigger_name, trigger_group)\nreferences qrtz_triggers(sched_name, trigger_name, trigger_group))\nengine=innodb;\n\ncreate table qrtz_simprop_triggers (\nsched_name varchar(120) not null,\ntrigger_name varchar(200) not null,\ntrigger_group varchar(200) not null,\nstr_prop_1 varchar(512) null,\nstr_prop_2 varchar(512) null,\nstr_prop_3 varchar(512) null,\nint_prop_1 int null,\nint_prop_2 int null,\nlong_prop_1 bigint null,\nlong_prop_2 bigint null,\ndec_prop_1 numeric(13, 4) null,\ndec_prop_2 numeric(13, 4) null,\nbool_prop_1 varchar(1) null,\nbool_prop_2 varchar(1) null,\nprimary key (sched_name, trigger_name, trigger_group),\nforeign key (sched_name, trigger_name, trigger_group)\nreferences qrtz_triggers(sched_name, trigger_name, trigger_group))\nengine=innodb;\n\ncreate table qrtz_blob_triggers (\nsched_name varchar(120) not null,\ntrigger_name varchar(200) not null,\ntrigger_group varchar(200) not null,\nblob_data blob null,\nprimary key (sched_name, trigger_name, trigger_group),\nindex (sched_name, trigger_name, trigger_group),\nforeign key (sched_name, trigger_name, trigger_group)\nreferences qrtz_triggers(sched_name, trigger_name, trigger_group))\nengine=innodb;\n\ncreate table qrtz_calendars (\nsched_name varchar(120) not null,\ncalendar_name varchar(200) not null,\ncalendar blob not null,\nprimary key (sched_name, calendar_name))\nengine=innodb;\n\ncreate table qrtz_paused_trigger_grps (\nsched_name varchar(120) not null,\ntrigger_group varchar(200) not null,\nprimary key (sched_name, trigger_group))\nengine=innodb;\n\ncreate table qrtz_fired_triggers (\nsched_name varchar(120) not null,\nentry_id varchar(95) not null,\ntrigger_name varchar(200) not null,\ntrigger_group varchar(200) not null,\ninstance_name varchar(200) not null,\nfired_time bigint(13) not null,\nsched_time bigint(13) not null,\npriority integer not null,\nstate varchar(16) not null,\njob_name varchar(200) null,\njob_group varchar(200) null,\nis_nonconcurrent varchar(1) null,\nrequests_recovery varchar(1) null,\nprimary key (sched_name, entry_id))\nengine=innodb;\n\ncreate table qrtz_scheduler_state (\nsched_name varchar(120) not null,\ninstance_name varchar(200) not null,\nlast_checkin_time bigint(13) not null,\ncheckin_interval bigint(13) not null,\nprimary key (sched_name, instance_name))\nengine=innodb;\n\ncreate table qrtz_locks (\nsched_name varchar(120) not null,\nlock_name varchar(40) not null,\nprimary key (sched_name, lock_name))\nengine=innodb;\n\ncreate index idx_qrtz_j_req_recovery on qrtz_job_details(sched_name, requests_recovery);\ncreate index idx_qrtz_j_grp on qrtz_job_details(sched_name, job_group);\n\ncreate index idx_qrtz_t_j on qrtz_triggers(sched_name, job_name, job_group);\ncreate index idx_qrtz_t_jg on qrtz_triggers(sched_name, job_group);\ncreate index idx_qrtz_t_c on qrtz_triggers(sched_name, calendar_name);\ncreate index idx_qrtz_t_g on qrtz_triggers(sched_name, trigger_group);\ncreate index idx_qrtz_t_state on qrtz_triggers(sched_name, trigger_state);\ncreate index idx_qrtz_t_n_state on qrtz_triggers(sched_name, trigger_name, trigger_group, trigger_state);\ncreate index idx_qrtz_t_n_g_state on qrtz_triggers(sched_name, trigger_group, trigger_state);\ncreate index idx_qrtz_t_next_fire_time on qrtz_triggers(sched_name, next_fire_time);\ncreate index idx_qrtz_t_nft_st on qrtz_triggers(sched_name, trigger_state, next_fire_time);\ncreate index idx_qrtz_t_nft_misfire on qrtz_triggers(sched_name, misfire_instr, next_fire_time);\ncreate index idx_qrtz_t_nft_st_misfire on qrtz_triggers(sched_name, misfire_instr, next_fire_time, trigger_state);\ncreate index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(sched_name, misfire_instr, next_fire_time, trigger_group, trigger_state);\n\ncreate index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(sched_name, instance_name);\ncreate index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(sched_name, instance_name, requests_recovery);\ncreate index idx_qrtz_ft_j_g on qrtz_fired_triggers(sched_name, job_name, job_group);\ncreate index idx_qrtz_ft_jg on qrtz_fired_triggers(sched_name, job_group);\ncreate index idx_qrtz_ft_t_g on qrtz_fired_triggers(sched_name, trigger_name, trigger_group);\ncreate index idx_qrtz_ft_tg on qrtz_fired_triggers(sched_name, trigger_group);\n\ncommit;"
  }
]