commit 06a53e75346cb8e6824d35e8a72dfec5b3a119b8 Author: alex_q <914269@qq.com> Date: Fri Jul 10 10:08:52 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0934da --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Created by .ignore support plugin (hsz.mobi) +### Example sysUserDetails template template +### Example sysUserDetails template + +# IntelliJ project files +.idea +*.iml +out +gen +target +*.log +logs \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2d5e5e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# SpringBoot单体应用部署Dockerfile +FROM openjdk:17-jdk-alpine + +RUN apk --update --no-cache add tini +ENTRYPOINT ["tini"] + +# /tmp 目录就会在运行时自动挂载为匿名卷,任何向 /tmp 中写入的信息都不会记录进容器存储层 +VOLUME /tmp + +ADD target/youlai-boot.jar app.jar + +CMD java \ + -Djava.security.egd=file:/dev/./urandom \ + -jar /app.jar + +EXPOSE 8989 +# 时区修改 +RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \&& echo 'Asia/Shanghai' >/etc/timezone + +RUN echo -e https://mirrors.ustc.edu.cn/alpine/v3.7/main/ > /etc/apk/repositories + +RUN apk --no-cache add ttf-dejavu fontconfig \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..090c4ab --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 有来开源组织 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..00973a0 --- /dev/null +++ b/README.md @@ -0,0 +1,143 @@ +# 项目简述 + +项目基于 Spring Boot 3.0 、Spring Security 6.0 、 JWT 、 Redis 、 Mybatis-Plus 、 Knife4j 等技术栈搭建的前后端分离开源权限管理系统。 + + +## 项目预览 +**在线预览地址** + +[http://vue3.youlai.tech/](http://vue3.youlai.tech/) + +**首页控制台** + +| ![明亮模式](https://s2.loli.net/2023/03/26/oltnAHfFcbw18GL.png) | +|-------------------------------------------------------------| +| ![暗黑模式](https://s2.loli.net/2023/03/13/QvjY4zf3VCGteNF.png) | + +**接口文档** + +![接口文档](https://s2.loli.net/2023/03/13/bH4J3O6WRgCUpwt.png) + +**权限管理系统** + +| ![用户管理](https://s2.loli.net/2023/03/13/L9xgT5sSMVZukQj.png) | ![角色管理](https://s2.loli.net/2023/03/13/nQg6HmrtFUkPDYv.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![菜单管理](https://s2.loli.net/2023/03/13/C4fDRJeTuUO7gPI.png) | ![字典管理](https://s2.loli.net/2023/03/13/BzqjHpa64wfeWhE.png) | + + + +## 项目特色 +- Spring Boot 3.0 + Vue3 前后端分离单体应用,适合快速开发; +- Spring Security + JWT 认证鉴权方案; +- 基于 RBAC 模型的权限设计,细粒度接口方法、按钮级别权限控制。 + +## 运行环境 +- JDK 17 +- IDEA Lombok 插件 +- IDEA MapStruct Support 插件 +- MySQL 8.x + +## 项目地址 + +| 项目名称 | 项目地址 | +|------|------------------------------------------------| +| 后端工程 | https://gitee.com/youlaiorg/youlai-boot | +| 前端工程 | https://gitee.com/youlaiorg/vue3-element-admin | + + + +## 接口文档 + +- `knife4j` 接口文档:[http://localhost:8989/doc.html](http://localhost:8989/doc.html) + +- `swagger` 接口文档:[http://localhost:8989/swagger-ui/index.html](http://localhost:8989/swagger-ui/index.html) + +## 项目运行 + +### 1. 数据库创建 + +执行 [youlai_boot.sql](sql/youlai_boot.sql) 脚本完成数据库创建、表结构和基础数据的初始化。 + +### 2. 配置修改 + +[application-dev.yml](src/main/resources/application-dev.yml) 修改MySQL、Redis连接配置; + +### 3. 后端启动 +执行 [SystemApplication.java](src/main/java/com/datao/admin/SystemApplication.java) 的 main 方法完成后端项目启动; + +访问接口文档地址 [http://localhost:8989/doc.html](http://localhost:8989/doc.html) 验证项目启动。 + +### 4. 前端启动 + +文档:[README.md](https://gitee.com/youlaiorg/vue3-element-admin#%E9%A1%B9%E7%9B%AE%E5%90%AF%E5%8A%A8) + +## 开发规范 + +### 方法命名 + +以下命名涵盖了Controller、Service和Mapper层 + +|作用|示例| +|---|---| +|分页查询|getUserPage| +|列表查询|listUsers| +|单个查询|getUser/getUserDetail/getUserInfo ...| +|新增|saveUser| +|修改|updateUser| +|删除|deleteUser/removeUser| + + +### 实体命名 + +| 名称 | 定义 | 示例 | +|--------|------------------|-----------| +| entity | 映射数据库实体,字段属性完全对应 | SysUser | +| bo | 多表关联查询的业务实体 | UserBO | +| query | 查询传参,建议参数≥3使用 | UserQuery | +| form | 表单对象 | UserForm | +| dto | RPC调用,可替代VO | UserDTO | +| vo | 视图层对象 | UserVO | + +### API规范 +在RESTFul架构中,每个URL代表一种资源,所以不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合",所以API中的名词也应该使用复数。 + +**请求示例:** + +|请求描述|请求方法|请求路径| +|---|---|---| +|获取所有用户信息|GET|/api/v1/users| +|获取标识为1用户信息|GET|/api/v1/users/1| +|删除标识为1用户信息|DELETE|/api/v1/users/1| +|新增用户|POST|/api/v1/users| +|修改标识为1用户信息|PUT|/api/v1/users/1| +|修改标识为1用户状态|PATCH|/api/v1/users/1/status| +|获取当前登录用户信息|GET|/api/v1/users/{me,current}| + + +## 请求状态码规范 + +参考 [阿里Java开发手册](https://developer.aliyun.com/topic/java2020?utm_content=g_1000113416) + +## Git 提交规范 + + +参考 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) 社区规范,建议 IDEA 安装 Git Commit Template 插件 + +- `feat` 增加新功能 +- `fix` 修复问题/BUG +- `style` 代码风格相关无影响运行结果的 +- `perf` 优化/性能提升 +- `refactor` 重构 +- `revert` 撤销修改 +- `test` 测试相关 +- `docs` 文档/注释 +- `chore` 依赖更新/脚手架配置修改等 +- `workflow` 工作流改进 +- `ci` 持续集成 + +## 联系我们 + +> 欢迎添加开发者微信,备注「有来」进群 + +| ![郝先瑞](https://s2.loli.net/2022/04/06/yRx8uzj4emA5QVr.jpg) | ![张川](https://s2.loli.net/2022/04/06/cQihGv9uPsTjXk1.jpg) | +| --- | --- | diff --git a/lib/HdrHistogram-2.1.12.jar b/lib/HdrHistogram-2.1.12.jar new file mode 100644 index 0000000..1e43d5e Binary files /dev/null and b/lib/HdrHistogram-2.1.12.jar differ diff --git a/lib/HikariCP-5.0.1.jar b/lib/HikariCP-5.0.1.jar new file mode 100644 index 0000000..9d7eafb Binary files /dev/null and b/lib/HikariCP-5.0.1.jar differ diff --git a/lib/LatencyUtils-2.0.3.jar b/lib/LatencyUtils-2.0.3.jar new file mode 100644 index 0000000..e7251b7 Binary files /dev/null and b/lib/LatencyUtils-2.0.3.jar differ diff --git a/lib/SparseBitSet-1.2.jar b/lib/SparseBitSet-1.2.jar new file mode 100644 index 0000000..bdb6771 Binary files /dev/null and b/lib/SparseBitSet-1.2.jar differ diff --git a/lib/asm-7.3.1.jar b/lib/asm-7.3.1.jar new file mode 100644 index 0000000..8a50266 Binary files /dev/null and b/lib/asm-7.3.1.jar differ diff --git a/lib/asm-analysis-7.3.1.jar b/lib/asm-analysis-7.3.1.jar new file mode 100644 index 0000000..1f83a5e Binary files /dev/null and b/lib/asm-analysis-7.3.1.jar differ diff --git a/lib/asm-commons-7.3.1.jar b/lib/asm-commons-7.3.1.jar new file mode 100644 index 0000000..65fb30e Binary files /dev/null and b/lib/asm-commons-7.3.1.jar differ diff --git a/lib/asm-tree-7.3.1.jar b/lib/asm-tree-7.3.1.jar new file mode 100644 index 0000000..28858f4 Binary files /dev/null and b/lib/asm-tree-7.3.1.jar differ diff --git a/lib/asm-util-7.3.1.jar b/lib/asm-util-7.3.1.jar new file mode 100644 index 0000000..4fe6c52 Binary files /dev/null and b/lib/asm-util-7.3.1.jar differ diff --git a/lib/aspectjweaver-1.9.19.jar b/lib/aspectjweaver-1.9.19.jar new file mode 100644 index 0000000..ebef6ce Binary files /dev/null and b/lib/aspectjweaver-1.9.19.jar differ diff --git a/lib/byte-buddy-1.14.4.jar b/lib/byte-buddy-1.14.4.jar new file mode 100644 index 0000000..43baf3b Binary files /dev/null and b/lib/byte-buddy-1.14.4.jar differ diff --git a/lib/cache-api-1.1.1.jar b/lib/cache-api-1.1.1.jar new file mode 100644 index 0000000..53665d2 Binary files /dev/null and b/lib/cache-api-1.1.1.jar differ diff --git a/lib/classgraph-4.8.149.jar b/lib/classgraph-4.8.149.jar new file mode 100644 index 0000000..095f666 Binary files /dev/null and b/lib/classgraph-4.8.149.jar differ diff --git a/lib/classmate-1.5.1.jar b/lib/classmate-1.5.1.jar new file mode 100644 index 0000000..819f5ea Binary files /dev/null and b/lib/classmate-1.5.1.jar differ diff --git a/lib/commons-codec-1.15.jar b/lib/commons-codec-1.15.jar new file mode 100644 index 0000000..f14985a Binary files /dev/null and b/lib/commons-codec-1.15.jar differ diff --git a/lib/commons-collections4-4.4.jar b/lib/commons-collections4-4.4.jar new file mode 100644 index 0000000..da06c3e Binary files /dev/null and b/lib/commons-collections4-4.4.jar differ diff --git a/lib/commons-compress-1.19.jar b/lib/commons-compress-1.19.jar new file mode 100644 index 0000000..5c9f52a Binary files /dev/null and b/lib/commons-compress-1.19.jar differ diff --git a/lib/commons-csv-1.8.jar b/lib/commons-csv-1.8.jar new file mode 100644 index 0000000..127c5a2 Binary files /dev/null and b/lib/commons-csv-1.8.jar differ diff --git a/lib/commons-lang3-3.12.0.jar b/lib/commons-lang3-3.12.0.jar new file mode 100644 index 0000000..4d434a2 Binary files /dev/null and b/lib/commons-lang3-3.12.0.jar differ diff --git a/lib/commons-math3-3.6.1.jar b/lib/commons-math3-3.6.1.jar new file mode 100644 index 0000000..0ff582c Binary files /dev/null and b/lib/commons-math3-3.6.1.jar differ diff --git a/lib/commons-pool2-2.11.1.jar b/lib/commons-pool2-2.11.1.jar new file mode 100644 index 0000000..8da8a82 Binary files /dev/null and b/lib/commons-pool2-2.11.1.jar differ diff --git a/lib/curvesapi-1.06.jar b/lib/curvesapi-1.06.jar new file mode 100644 index 0000000..800ab9b Binary files /dev/null and b/lib/curvesapi-1.06.jar differ diff --git a/lib/druid-1.2.16.jar b/lib/druid-1.2.16.jar new file mode 100644 index 0000000..86385a1 Binary files /dev/null and b/lib/druid-1.2.16.jar differ diff --git a/lib/druid-spring-boot-starter-1.2.16.jar b/lib/druid-spring-boot-starter-1.2.16.jar new file mode 100644 index 0000000..bcd5ec4 Binary files /dev/null and b/lib/druid-spring-boot-starter-1.2.16.jar differ diff --git a/lib/easy-captcha-1.6.2.jar b/lib/easy-captcha-1.6.2.jar new file mode 100644 index 0000000..e8985a0 Binary files /dev/null and b/lib/easy-captcha-1.6.2.jar differ diff --git a/lib/easyexcel-3.2.1.jar b/lib/easyexcel-3.2.1.jar new file mode 100644 index 0000000..cbf1309 Binary files /dev/null and b/lib/easyexcel-3.2.1.jar differ diff --git a/lib/easyexcel-core-3.2.1.jar b/lib/easyexcel-core-3.2.1.jar new file mode 100644 index 0000000..17e92d2 Binary files /dev/null and b/lib/easyexcel-core-3.2.1.jar differ diff --git a/lib/easyexcel-support-3.2.1.jar b/lib/easyexcel-support-3.2.1.jar new file mode 100644 index 0000000..80b45e2 Binary files /dev/null and b/lib/easyexcel-support-3.2.1.jar differ diff --git a/lib/ehcache-3.9.9.jar b/lib/ehcache-3.9.9.jar new file mode 100644 index 0000000..5681cc5 Binary files /dev/null and b/lib/ehcache-3.9.9.jar differ diff --git a/lib/hibernate-validator-8.0.0.Final.jar b/lib/hibernate-validator-8.0.0.Final.jar new file mode 100644 index 0000000..cd5ab41 Binary files /dev/null and b/lib/hibernate-validator-8.0.0.Final.jar differ diff --git a/lib/hutool-all-5.8.18.jar b/lib/hutool-all-5.8.18.jar new file mode 100644 index 0000000..2fafdda Binary files /dev/null and b/lib/hutool-all-5.8.18.jar differ diff --git a/lib/jackson-annotations-2.15.0.jar b/lib/jackson-annotations-2.15.0.jar new file mode 100644 index 0000000..fc66d44 Binary files /dev/null and b/lib/jackson-annotations-2.15.0.jar differ diff --git a/lib/jackson-core-2.15.0.jar b/lib/jackson-core-2.15.0.jar new file mode 100644 index 0000000..574894d Binary files /dev/null and b/lib/jackson-core-2.15.0.jar differ diff --git a/lib/jackson-databind-2.15.0.jar b/lib/jackson-databind-2.15.0.jar new file mode 100644 index 0000000..b4d9383 Binary files /dev/null and b/lib/jackson-databind-2.15.0.jar differ diff --git a/lib/jackson-dataformat-yaml-2.15.0.jar b/lib/jackson-dataformat-yaml-2.15.0.jar new file mode 100644 index 0000000..e65e39e Binary files /dev/null and b/lib/jackson-dataformat-yaml-2.15.0.jar differ diff --git a/lib/jackson-datatype-jdk8-2.15.0.jar b/lib/jackson-datatype-jdk8-2.15.0.jar new file mode 100644 index 0000000..2bc4e32 Binary files /dev/null and b/lib/jackson-datatype-jdk8-2.15.0.jar differ diff --git a/lib/jackson-datatype-jsr310-2.15.0.jar b/lib/jackson-datatype-jsr310-2.15.0.jar new file mode 100644 index 0000000..b6e528d Binary files /dev/null and b/lib/jackson-datatype-jsr310-2.15.0.jar differ diff --git a/lib/jackson-module-parameter-names-2.15.0.jar b/lib/jackson-module-parameter-names-2.15.0.jar new file mode 100644 index 0000000..1a18cd3 Binary files /dev/null and b/lib/jackson-module-parameter-names-2.15.0.jar differ diff --git a/lib/jakarta.activation-api-2.1.2.jar b/lib/jakarta.activation-api-2.1.2.jar new file mode 100644 index 0000000..ea57b3e Binary files /dev/null and b/lib/jakarta.activation-api-2.1.2.jar differ diff --git a/lib/jakarta.annotation-api-2.1.1.jar b/lib/jakarta.annotation-api-2.1.1.jar new file mode 100644 index 0000000..e13b7df Binary files /dev/null and b/lib/jakarta.annotation-api-2.1.1.jar differ diff --git a/lib/jakarta.validation-api-3.0.2.jar b/lib/jakarta.validation-api-3.0.2.jar new file mode 100644 index 0000000..254c7a2 Binary files /dev/null and b/lib/jakarta.validation-api-3.0.2.jar differ diff --git a/lib/jakarta.xml.bind-api-4.0.0.jar b/lib/jakarta.xml.bind-api-4.0.0.jar new file mode 100644 index 0000000..b10d606 Binary files /dev/null and b/lib/jakarta.xml.bind-api-4.0.0.jar differ diff --git a/lib/jboss-logging-3.5.0.Final.jar b/lib/jboss-logging-3.5.0.Final.jar new file mode 100644 index 0000000..d39dfcf Binary files /dev/null and b/lib/jboss-logging-3.5.0.Final.jar differ diff --git a/lib/jboss-marshalling-2.0.11.Final.jar b/lib/jboss-marshalling-2.0.11.Final.jar new file mode 100644 index 0000000..5a0a337 Binary files /dev/null and b/lib/jboss-marshalling-2.0.11.Final.jar differ diff --git a/lib/jboss-marshalling-river-2.0.11.Final.jar b/lib/jboss-marshalling-river-2.0.11.Final.jar new file mode 100644 index 0000000..4823683 Binary files /dev/null and b/lib/jboss-marshalling-river-2.0.11.Final.jar differ diff --git a/lib/jjwt-api-0.11.5.jar b/lib/jjwt-api-0.11.5.jar new file mode 100644 index 0000000..95115bf Binary files /dev/null and b/lib/jjwt-api-0.11.5.jar differ diff --git a/lib/jjwt-impl-0.11.5.jar b/lib/jjwt-impl-0.11.5.jar new file mode 100644 index 0000000..2d1088d Binary files /dev/null and b/lib/jjwt-impl-0.11.5.jar differ diff --git a/lib/jjwt-jackson-0.11.5.jar b/lib/jjwt-jackson-0.11.5.jar new file mode 100644 index 0000000..41aeec0 Binary files /dev/null and b/lib/jjwt-jackson-0.11.5.jar differ diff --git a/lib/jodd-bean-5.1.6.jar b/lib/jodd-bean-5.1.6.jar new file mode 100644 index 0000000..daa04d2 Binary files /dev/null and b/lib/jodd-bean-5.1.6.jar differ diff --git a/lib/jodd-core-5.1.6.jar b/lib/jodd-core-5.1.6.jar new file mode 100644 index 0000000..d6cf08b Binary files /dev/null and b/lib/jodd-core-5.1.6.jar differ diff --git a/lib/jsqlparser-4.4.jar b/lib/jsqlparser-4.4.jar new file mode 100644 index 0000000..b3eeba5 Binary files /dev/null and b/lib/jsqlparser-4.4.jar differ diff --git a/lib/jul-to-slf4j-2.0.7.jar b/lib/jul-to-slf4j-2.0.7.jar new file mode 100644 index 0000000..11ed9ff Binary files /dev/null and b/lib/jul-to-slf4j-2.0.7.jar differ diff --git a/lib/knife4j-core-4.0.0.jar b/lib/knife4j-core-4.0.0.jar new file mode 100644 index 0000000..4bb92b7 Binary files /dev/null and b/lib/knife4j-core-4.0.0.jar differ diff --git a/lib/knife4j-openapi3-jakarta-spring-boot-starter-4.0.0.jar b/lib/knife4j-openapi3-jakarta-spring-boot-starter-4.0.0.jar new file mode 100644 index 0000000..2daa0aa Binary files /dev/null and b/lib/knife4j-openapi3-jakarta-spring-boot-starter-4.0.0.jar differ diff --git a/lib/knife4j-openapi3-ui-4.0.0.jar b/lib/knife4j-openapi3-ui-4.0.0.jar new file mode 100644 index 0000000..f5efac5 Binary files /dev/null and b/lib/knife4j-openapi3-ui-4.0.0.jar differ diff --git a/lib/kryo-5.5.0.jar b/lib/kryo-5.5.0.jar new file mode 100644 index 0000000..1004d83 Binary files /dev/null and b/lib/kryo-5.5.0.jar differ diff --git a/lib/lettuce-core-6.2.4.RELEASE.jar b/lib/lettuce-core-6.2.4.RELEASE.jar new file mode 100644 index 0000000..702f36a Binary files /dev/null and b/lib/lettuce-core-6.2.4.RELEASE.jar differ diff --git a/lib/log4j-api-2.20.0.jar b/lib/log4j-api-2.20.0.jar new file mode 100644 index 0000000..29d1b52 Binary files /dev/null and b/lib/log4j-api-2.20.0.jar differ diff --git a/lib/log4j-to-slf4j-2.20.0.jar b/lib/log4j-to-slf4j-2.20.0.jar new file mode 100644 index 0000000..5eb8bc8 Binary files /dev/null and b/lib/log4j-to-slf4j-2.20.0.jar differ diff --git a/lib/logback-classic-1.4.7.jar b/lib/logback-classic-1.4.7.jar new file mode 100644 index 0000000..20d5299 Binary files /dev/null and b/lib/logback-classic-1.4.7.jar differ diff --git a/lib/logback-core-1.4.7.jar b/lib/logback-core-1.4.7.jar new file mode 100644 index 0000000..34267ae Binary files /dev/null and b/lib/logback-core-1.4.7.jar differ diff --git a/lib/lombok-1.18.26.jar b/lib/lombok-1.18.26.jar new file mode 100644 index 0000000..8f95540 Binary files /dev/null and b/lib/lombok-1.18.26.jar differ diff --git a/lib/lombok-mapstruct-binding-0.2.0.jar b/lib/lombok-mapstruct-binding-0.2.0.jar new file mode 100644 index 0000000..7b806db Binary files /dev/null and b/lib/lombok-mapstruct-binding-0.2.0.jar differ diff --git a/lib/mapstruct-1.5.3.Final.jar b/lib/mapstruct-1.5.3.Final.jar new file mode 100644 index 0000000..1dc9fb9 Binary files /dev/null and b/lib/mapstruct-1.5.3.Final.jar differ diff --git a/lib/micrometer-commons-1.11.0.jar b/lib/micrometer-commons-1.11.0.jar new file mode 100644 index 0000000..9455d1b Binary files /dev/null and b/lib/micrometer-commons-1.11.0.jar differ diff --git a/lib/micrometer-core-1.11.0.jar b/lib/micrometer-core-1.11.0.jar new file mode 100644 index 0000000..69859e4 Binary files /dev/null and b/lib/micrometer-core-1.11.0.jar differ diff --git a/lib/micrometer-observation-1.11.0.jar b/lib/micrometer-observation-1.11.0.jar new file mode 100644 index 0000000..b231d51 Binary files /dev/null and b/lib/micrometer-observation-1.11.0.jar differ diff --git a/lib/minlog-1.3.1.jar b/lib/minlog-1.3.1.jar new file mode 100644 index 0000000..9a15570 Binary files /dev/null and b/lib/minlog-1.3.1.jar differ diff --git a/lib/mybatis-3.5.10.jar b/lib/mybatis-3.5.10.jar new file mode 100644 index 0000000..0f941e0 Binary files /dev/null and b/lib/mybatis-3.5.10.jar differ diff --git a/lib/mybatis-plus-3.5.3.1.jar b/lib/mybatis-plus-3.5.3.1.jar new file mode 100644 index 0000000..d02f4ee Binary files /dev/null and b/lib/mybatis-plus-3.5.3.1.jar differ diff --git a/lib/mybatis-plus-annotation-3.5.3.1.jar b/lib/mybatis-plus-annotation-3.5.3.1.jar new file mode 100644 index 0000000..2de6716 Binary files /dev/null and b/lib/mybatis-plus-annotation-3.5.3.1.jar differ diff --git a/lib/mybatis-plus-boot-starter-3.5.3.1.jar b/lib/mybatis-plus-boot-starter-3.5.3.1.jar new file mode 100644 index 0000000..653c748 Binary files /dev/null and b/lib/mybatis-plus-boot-starter-3.5.3.1.jar differ diff --git a/lib/mybatis-plus-core-3.5.3.1.jar b/lib/mybatis-plus-core-3.5.3.1.jar new file mode 100644 index 0000000..4f8cde8 Binary files /dev/null and b/lib/mybatis-plus-core-3.5.3.1.jar differ diff --git a/lib/mybatis-plus-extension-3.5.3.1.jar b/lib/mybatis-plus-extension-3.5.3.1.jar new file mode 100644 index 0000000..333fa8e Binary files /dev/null and b/lib/mybatis-plus-extension-3.5.3.1.jar differ diff --git a/lib/mybatis-spring-2.0.7.jar b/lib/mybatis-spring-2.0.7.jar new file mode 100644 index 0000000..a930b60 Binary files /dev/null and b/lib/mybatis-spring-2.0.7.jar differ diff --git a/lib/mysql-connector-j-8.0.33.jar b/lib/mysql-connector-j-8.0.33.jar new file mode 100644 index 0000000..3f741f5 Binary files /dev/null and b/lib/mysql-connector-j-8.0.33.jar differ diff --git a/lib/nashorn-core-15.4.jar b/lib/nashorn-core-15.4.jar new file mode 100644 index 0000000..b472660 Binary files /dev/null and b/lib/nashorn-core-15.4.jar differ diff --git a/lib/netty-buffer-4.1.92.Final.jar b/lib/netty-buffer-4.1.92.Final.jar new file mode 100644 index 0000000..d2a2708 Binary files /dev/null and b/lib/netty-buffer-4.1.92.Final.jar differ diff --git a/lib/netty-codec-4.1.92.Final.jar b/lib/netty-codec-4.1.92.Final.jar new file mode 100644 index 0000000..0b285ca Binary files /dev/null and b/lib/netty-codec-4.1.92.Final.jar differ diff --git a/lib/netty-codec-dns-4.1.92.Final.jar b/lib/netty-codec-dns-4.1.92.Final.jar new file mode 100644 index 0000000..040a4af Binary files /dev/null and b/lib/netty-codec-dns-4.1.92.Final.jar differ diff --git a/lib/netty-common-4.1.92.Final.jar b/lib/netty-common-4.1.92.Final.jar new file mode 100644 index 0000000..23c6d86 Binary files /dev/null and b/lib/netty-common-4.1.92.Final.jar differ diff --git a/lib/netty-handler-4.1.92.Final.jar b/lib/netty-handler-4.1.92.Final.jar new file mode 100644 index 0000000..371c075 Binary files /dev/null and b/lib/netty-handler-4.1.92.Final.jar differ diff --git a/lib/netty-resolver-4.1.92.Final.jar b/lib/netty-resolver-4.1.92.Final.jar new file mode 100644 index 0000000..59440ac Binary files /dev/null and b/lib/netty-resolver-4.1.92.Final.jar differ diff --git a/lib/netty-resolver-dns-4.1.92.Final.jar b/lib/netty-resolver-dns-4.1.92.Final.jar new file mode 100644 index 0000000..c44ab9b Binary files /dev/null and b/lib/netty-resolver-dns-4.1.92.Final.jar differ diff --git a/lib/netty-transport-4.1.92.Final.jar b/lib/netty-transport-4.1.92.Final.jar new file mode 100644 index 0000000..2739323 Binary files /dev/null and b/lib/netty-transport-4.1.92.Final.jar differ diff --git a/lib/netty-transport-native-unix-common-4.1.92.Final.jar b/lib/netty-transport-native-unix-common-4.1.92.Final.jar new file mode 100644 index 0000000..b419c38 Binary files /dev/null and b/lib/netty-transport-native-unix-common-4.1.92.Final.jar differ diff --git a/lib/objenesis-3.3.jar b/lib/objenesis-3.3.jar new file mode 100644 index 0000000..d660190 Binary files /dev/null and b/lib/objenesis-3.3.jar differ diff --git a/lib/poi-4.1.2.jar b/lib/poi-4.1.2.jar new file mode 100644 index 0000000..3803353 Binary files /dev/null and b/lib/poi-4.1.2.jar differ diff --git a/lib/poi-ooxml-4.1.2.jar b/lib/poi-ooxml-4.1.2.jar new file mode 100644 index 0000000..6038a51 Binary files /dev/null and b/lib/poi-ooxml-4.1.2.jar differ diff --git a/lib/poi-ooxml-schemas-4.1.2.jar b/lib/poi-ooxml-schemas-4.1.2.jar new file mode 100644 index 0000000..74d3cd5 Binary files /dev/null and b/lib/poi-ooxml-schemas-4.1.2.jar differ diff --git a/lib/reactive-streams-1.0.4.jar b/lib/reactive-streams-1.0.4.jar new file mode 100644 index 0000000..e58c483 Binary files /dev/null and b/lib/reactive-streams-1.0.4.jar differ diff --git a/lib/reactor-core-3.5.6.jar b/lib/reactor-core-3.5.6.jar new file mode 100644 index 0000000..f0635ac Binary files /dev/null and b/lib/reactor-core-3.5.6.jar differ diff --git a/lib/redisson-3.23.3.jar b/lib/redisson-3.23.3.jar new file mode 100644 index 0000000..516eb84 Binary files /dev/null and b/lib/redisson-3.23.3.jar differ diff --git a/lib/redisson-spring-boot-starter-3.23.3.jar b/lib/redisson-spring-boot-starter-3.23.3.jar new file mode 100644 index 0000000..f2b4fa2 Binary files /dev/null and b/lib/redisson-spring-boot-starter-3.23.3.jar differ diff --git a/lib/redisson-spring-data-31-3.23.3.jar b/lib/redisson-spring-data-31-3.23.3.jar new file mode 100644 index 0000000..94b352f Binary files /dev/null and b/lib/redisson-spring-data-31-3.23.3.jar differ diff --git a/lib/reflectasm-1.11.9.jar b/lib/reflectasm-1.11.9.jar new file mode 100644 index 0000000..050129b Binary files /dev/null and b/lib/reflectasm-1.11.9.jar differ diff --git a/lib/rxjava-3.1.6.jar b/lib/rxjava-3.1.6.jar new file mode 100644 index 0000000..8e23e71 Binary files /dev/null and b/lib/rxjava-3.1.6.jar differ diff --git a/lib/slf4j-api-2.0.7.jar b/lib/slf4j-api-2.0.7.jar new file mode 100644 index 0000000..be5447c Binary files /dev/null and b/lib/slf4j-api-2.0.7.jar differ diff --git a/lib/snakeyaml-1.33.jar b/lib/snakeyaml-1.33.jar new file mode 100644 index 0000000..5a95195 Binary files /dev/null and b/lib/snakeyaml-1.33.jar differ diff --git a/lib/spring-aop-6.0.9.jar b/lib/spring-aop-6.0.9.jar new file mode 100644 index 0000000..37d03c3 Binary files /dev/null and b/lib/spring-aop-6.0.9.jar differ diff --git a/lib/spring-beans-6.0.9.jar b/lib/spring-beans-6.0.9.jar new file mode 100644 index 0000000..737902e Binary files /dev/null and b/lib/spring-beans-6.0.9.jar differ diff --git a/lib/spring-boot-3.1.0.jar b/lib/spring-boot-3.1.0.jar new file mode 100644 index 0000000..2d74b13 Binary files /dev/null and b/lib/spring-boot-3.1.0.jar differ diff --git a/lib/spring-boot-actuator-3.1.0.jar b/lib/spring-boot-actuator-3.1.0.jar new file mode 100644 index 0000000..315dcb9 Binary files /dev/null and b/lib/spring-boot-actuator-3.1.0.jar differ diff --git a/lib/spring-boot-actuator-autoconfigure-3.1.0.jar b/lib/spring-boot-actuator-autoconfigure-3.1.0.jar new file mode 100644 index 0000000..c7d10f5 Binary files /dev/null and b/lib/spring-boot-actuator-autoconfigure-3.1.0.jar differ diff --git a/lib/spring-boot-autoconfigure-3.1.0.jar b/lib/spring-boot-autoconfigure-3.1.0.jar new file mode 100644 index 0000000..aaa7346 Binary files /dev/null and b/lib/spring-boot-autoconfigure-3.1.0.jar differ diff --git a/lib/spring-boot-jarmode-layertools-3.1.0.jar b/lib/spring-boot-jarmode-layertools-3.1.0.jar new file mode 100644 index 0000000..5f789f1 Binary files /dev/null and b/lib/spring-boot-jarmode-layertools-3.1.0.jar differ diff --git a/lib/spring-context-6.0.9.jar b/lib/spring-context-6.0.9.jar new file mode 100644 index 0000000..3e803ae Binary files /dev/null and b/lib/spring-context-6.0.9.jar differ diff --git a/lib/spring-context-support-6.0.9.jar b/lib/spring-context-support-6.0.9.jar new file mode 100644 index 0000000..5650ee5 Binary files /dev/null and b/lib/spring-context-support-6.0.9.jar differ diff --git a/lib/spring-core-6.0.9.jar b/lib/spring-core-6.0.9.jar new file mode 100644 index 0000000..10b52df Binary files /dev/null and b/lib/spring-core-6.0.9.jar differ diff --git a/lib/spring-data-commons-3.1.0.jar b/lib/spring-data-commons-3.1.0.jar new file mode 100644 index 0000000..96ee65c Binary files /dev/null and b/lib/spring-data-commons-3.1.0.jar differ diff --git a/lib/spring-data-keyvalue-3.1.0.jar b/lib/spring-data-keyvalue-3.1.0.jar new file mode 100644 index 0000000..f8f7983 Binary files /dev/null and b/lib/spring-data-keyvalue-3.1.0.jar differ diff --git a/lib/spring-data-redis-3.1.0.jar b/lib/spring-data-redis-3.1.0.jar new file mode 100644 index 0000000..bc642b4 Binary files /dev/null and b/lib/spring-data-redis-3.1.0.jar differ diff --git a/lib/spring-expression-6.0.9.jar b/lib/spring-expression-6.0.9.jar new file mode 100644 index 0000000..fb5a330 Binary files /dev/null and b/lib/spring-expression-6.0.9.jar differ diff --git a/lib/spring-jcl-6.0.9.jar b/lib/spring-jcl-6.0.9.jar new file mode 100644 index 0000000..30f1328 Binary files /dev/null and b/lib/spring-jcl-6.0.9.jar differ diff --git a/lib/spring-jdbc-6.0.9.jar b/lib/spring-jdbc-6.0.9.jar new file mode 100644 index 0000000..48e130e Binary files /dev/null and b/lib/spring-jdbc-6.0.9.jar differ diff --git a/lib/spring-oxm-6.0.9.jar b/lib/spring-oxm-6.0.9.jar new file mode 100644 index 0000000..9a179d9 Binary files /dev/null and b/lib/spring-oxm-6.0.9.jar differ diff --git a/lib/spring-security-config-6.1.0.jar b/lib/spring-security-config-6.1.0.jar new file mode 100644 index 0000000..b988fc6 Binary files /dev/null and b/lib/spring-security-config-6.1.0.jar differ diff --git a/lib/spring-security-core-6.1.0.jar b/lib/spring-security-core-6.1.0.jar new file mode 100644 index 0000000..ca2d21d Binary files /dev/null and b/lib/spring-security-core-6.1.0.jar differ diff --git a/lib/spring-security-crypto-6.1.0.jar b/lib/spring-security-crypto-6.1.0.jar new file mode 100644 index 0000000..8465266 Binary files /dev/null and b/lib/spring-security-crypto-6.1.0.jar differ diff --git a/lib/spring-security-web-6.1.0.jar b/lib/spring-security-web-6.1.0.jar new file mode 100644 index 0000000..d4de1f2 Binary files /dev/null and b/lib/spring-security-web-6.1.0.jar differ diff --git a/lib/spring-tx-6.0.9.jar b/lib/spring-tx-6.0.9.jar new file mode 100644 index 0000000..9d03c68 Binary files /dev/null and b/lib/spring-tx-6.0.9.jar differ diff --git a/lib/spring-web-6.0.9.jar b/lib/spring-web-6.0.9.jar new file mode 100644 index 0000000..3966adf Binary files /dev/null and b/lib/spring-web-6.0.9.jar differ diff --git a/lib/spring-webflux-6.0.9.jar b/lib/spring-webflux-6.0.9.jar new file mode 100644 index 0000000..befd6f7 Binary files /dev/null and b/lib/spring-webflux-6.0.9.jar differ diff --git a/lib/spring-webmvc-6.0.9.jar b/lib/spring-webmvc-6.0.9.jar new file mode 100644 index 0000000..fe5a143 Binary files /dev/null and b/lib/spring-webmvc-6.0.9.jar differ diff --git a/lib/springdoc-openapi-starter-common-2.0.0.jar b/lib/springdoc-openapi-starter-common-2.0.0.jar new file mode 100644 index 0000000..bc54576 Binary files /dev/null and b/lib/springdoc-openapi-starter-common-2.0.0.jar differ diff --git a/lib/springdoc-openapi-starter-webflux-api-2.0.0.jar b/lib/springdoc-openapi-starter-webflux-api-2.0.0.jar new file mode 100644 index 0000000..ad784f6 Binary files /dev/null and b/lib/springdoc-openapi-starter-webflux-api-2.0.0.jar differ diff --git a/lib/springdoc-openapi-starter-webflux-ui-2.0.0.jar b/lib/springdoc-openapi-starter-webflux-ui-2.0.0.jar new file mode 100644 index 0000000..6e95770 Binary files /dev/null and b/lib/springdoc-openapi-starter-webflux-ui-2.0.0.jar differ diff --git a/lib/springdoc-openapi-starter-webmvc-api-2.0.0.jar b/lib/springdoc-openapi-starter-webmvc-api-2.0.0.jar new file mode 100644 index 0000000..771fb25 Binary files /dev/null and b/lib/springdoc-openapi-starter-webmvc-api-2.0.0.jar differ diff --git a/lib/springdoc-openapi-starter-webmvc-ui-2.0.0.jar b/lib/springdoc-openapi-starter-webmvc-ui-2.0.0.jar new file mode 100644 index 0000000..a829d2f Binary files /dev/null and b/lib/springdoc-openapi-starter-webmvc-ui-2.0.0.jar differ diff --git a/lib/swagger-annotations-jakarta-2.2.7.jar b/lib/swagger-annotations-jakarta-2.2.7.jar new file mode 100644 index 0000000..29f10e3 Binary files /dev/null and b/lib/swagger-annotations-jakarta-2.2.7.jar differ diff --git a/lib/swagger-core-jakarta-2.2.7.jar b/lib/swagger-core-jakarta-2.2.7.jar new file mode 100644 index 0000000..a79bc31 Binary files /dev/null and b/lib/swagger-core-jakarta-2.2.7.jar differ diff --git a/lib/swagger-models-jakarta-2.2.7.jar b/lib/swagger-models-jakarta-2.2.7.jar new file mode 100644 index 0000000..723a390 Binary files /dev/null and b/lib/swagger-models-jakarta-2.2.7.jar differ diff --git a/lib/swagger-ui-4.15.5.jar b/lib/swagger-ui-4.15.5.jar new file mode 100644 index 0000000..b9371a1 Binary files /dev/null and b/lib/swagger-ui-4.15.5.jar differ diff --git a/lib/tomcat-embed-core-10.1.8.jar b/lib/tomcat-embed-core-10.1.8.jar new file mode 100644 index 0000000..664ed33 Binary files /dev/null and b/lib/tomcat-embed-core-10.1.8.jar differ diff --git a/lib/tomcat-embed-el-10.1.8.jar b/lib/tomcat-embed-el-10.1.8.jar new file mode 100644 index 0000000..54fde22 Binary files /dev/null and b/lib/tomcat-embed-el-10.1.8.jar differ diff --git a/lib/tomcat-embed-websocket-10.1.8.jar b/lib/tomcat-embed-websocket-10.1.8.jar new file mode 100644 index 0000000..60b2400 Binary files /dev/null and b/lib/tomcat-embed-websocket-10.1.8.jar differ diff --git a/lib/webjars-locator-core-0.52.jar b/lib/webjars-locator-core-0.52.jar new file mode 100644 index 0000000..ae5759b Binary files /dev/null and b/lib/webjars-locator-core-0.52.jar differ diff --git a/lib/xmlbeans-3.1.0.jar b/lib/xmlbeans-3.1.0.jar new file mode 100644 index 0000000..18e568f Binary files /dev/null and b/lib/xmlbeans-3.1.0.jar differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4d78c2f --- /dev/null +++ b/pom.xml @@ -0,0 +1,246 @@ + + + 4.0.0 + + com.datao + datao-admin + 1.0.7 + datao权限管理系统 + + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + + + 17 + 17 + + true + + 5.8.18 + + 8.0.33 + 1.2.16 + 3.5.3.1 + + 4.0.0 + + 1.5.3.Final + 0.2.0 + + 2.4.0 + + 0.11.5 + + 3.2.1 + + + 8.5.2 + 4.8.1 + + + 1.6.2 + 15.4 + + + 3.23.3 + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + org.projectlombok + lombok + + provided + + + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + provided + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.apache.commons + commons-pool2 + + + + com.mysql + mysql-connector-j + ${mysql.version} + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + + + + + com.GitHub.whvcse + easy-captcha + ${easy-captcha.version} + + + + + org.openjdk.nashorn + nashorn-core + ${nashorn.version} + + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + + + + + + + + + + ${project.artifactId}-${project.version} + + + org.springframework.boot + spring-boot-maven-plugin + + + ZIP + + + nothing + nothing + + + + + + + + + diff --git a/src/main/java/com/datao/admin/SystemApplication.java b/src/main/java/com/datao/admin/SystemApplication.java new file mode 100644 index 0000000..94e055d --- /dev/null +++ b/src/main/java/com/datao/admin/SystemApplication.java @@ -0,0 +1,11 @@ +package com.datao.admin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SystemApplication { + public static void main(String[] args) { + SpringApplication.run(SystemApplication.class, args); + } +} diff --git a/src/main/java/com/datao/admin/common/base/BaseEntity.java b/src/main/java/com/datao/admin/common/base/BaseEntity.java new file mode 100644 index 0000000..93031d1 --- /dev/null +++ b/src/main/java/com/datao/admin/common/base/BaseEntity.java @@ -0,0 +1,25 @@ +package com.datao.admin.common.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +public class BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + + @TableField(fill = FieldFill.INSERT) + @JsonInclude(value = JsonInclude.Include.NON_NULL) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonInclude(value = JsonInclude.Include.NON_NULL) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/datao/admin/common/base/BasePageQuery.java b/src/main/java/com/datao/admin/common/base/BasePageQuery.java new file mode 100644 index 0000000..91153ea --- /dev/null +++ b/src/main/java/com/datao/admin/common/base/BasePageQuery.java @@ -0,0 +1,22 @@ +package com.datao.admin.common.base; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 基础分页请求对象 + * + * @author haoxr + * @date 2021/2/28 + */ +@Data +@Schema +public class BasePageQuery { + + @Schema(description = "页码", example = "1") + private int pageNum = 1; + + @Schema(description = "每页记录数", example = "10") + private int pageSize = 10; +} diff --git a/src/main/java/com/datao/admin/common/base/BaseVO.java b/src/main/java/com/datao/admin/common/base/BaseVO.java new file mode 100644 index 0000000..1b0ed68 --- /dev/null +++ b/src/main/java/com/datao/admin/common/base/BaseVO.java @@ -0,0 +1,19 @@ +package com.datao.admin.common.base; + +import lombok.Data; +import lombok.ToString; + +import java.io.Serializable; + +/** + * 视图对象基类 + * + * @author haoxr + * @date 2022/10/22 + */ +@Data +@ToString +public class BaseVO implements Serializable { + + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/datao/admin/common/base/IBaseEnum.java b/src/main/java/com/datao/admin/common/base/IBaseEnum.java new file mode 100644 index 0000000..1c79fe3 --- /dev/null +++ b/src/main/java/com/datao/admin/common/base/IBaseEnum.java @@ -0,0 +1,88 @@ +package com.datao.admin.common.base; + + +import cn.hutool.core.util.ObjectUtil; + +import java.util.EnumSet; +import java.util.Objects; + +/** + * 枚举通用接口 + * + * @author haoxr + * @date 2022/3/27 12:06 + */ +public interface IBaseEnum { + + T getValue(); + + String getLabel(); + + /** + * 根据值获取枚举 + * + * @param value + * @param clazz + * @param 枚举 + * @return + */ + static & IBaseEnum> E getEnumByValue(Object value, Class clazz) { + Objects.requireNonNull(value); + EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举 + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getValue(), value)) + .findFirst() + .orElse(null); + return matchEnum; + } + + /** + * 根据文本标签获取值 + * + * @param value + * @param clazz + * @param + * @return + */ + static & IBaseEnum> String getLabelByValue(Object value, Class clazz) { + Objects.requireNonNull(value); + EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举 + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getValue(), value)) + .findFirst() + .orElse(null); + + String label = null; + if (matchEnum != null) { + label = matchEnum.getLabel(); + } + return label; + } + + + /** + * 根据文本标签获取值 + * + * @param label + * @param clazz + * @param + * @return + */ + static & IBaseEnum> Object getValueByLabel(String label, Class clazz) { + Objects.requireNonNull(label); + EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举 + String finalLabel = label; + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel)) + .findFirst() + .orElse(null); + + Object value = null; + if (matchEnum != null) { + value = matchEnum.getValue(); + } + return value; + } + + +} diff --git a/src/main/java/com/datao/admin/common/constant/ExcelConstants.java b/src/main/java/com/datao/admin/common/constant/ExcelConstants.java new file mode 100644 index 0000000..9e7bf3c --- /dev/null +++ b/src/main/java/com/datao/admin/common/constant/ExcelConstants.java @@ -0,0 +1,16 @@ +package com.datao.admin.common.constant; + +/** + * Excel 常量 + * + * @author: haoxr + * @date: 2023/03/24 + */ +public interface ExcelConstants { + + /** + * Excel 模板目录 + */ + String EXCEL_TEMPLATE_DIR="excel-templates"; + +} diff --git a/src/main/java/com/datao/admin/common/constant/SecurityConstants.java b/src/main/java/com/datao/admin/common/constant/SecurityConstants.java new file mode 100644 index 0000000..46f0234 --- /dev/null +++ b/src/main/java/com/datao/admin/common/constant/SecurityConstants.java @@ -0,0 +1,45 @@ +package com.datao.admin.common.constant; + +/** + * Security 常量 + * + * @author: haoxr + * @date: 2023/03/24 + */ +public interface SecurityConstants { + + /** + * 登录接口路径 + */ + String LOGIN_PATH = "/api/v1/auth/login"; + + /** + * Token 前缀 + */ + String TOKEN_PREFIX = "Bearer "; + + /** + * 请求头Token的Key + */ + String TOKEN_KEY = "Authorization"; + + /** + * 验证码缓存前缀 + */ + String VERIFY_CODE_CACHE_PREFIX = "AUTH:VERIFY_CODE:"; + + /** + * 用户权限集合缓存前缀 + */ + String USER_PERMS_CACHE_PREFIX = "AUTH:USER_PERMS:"; + + /** + * 黑名单Token缓存前缀 + */ + String BLACK_TOKEN_CACHE_PREFIX = "AUTH:BLACK_TOKEN:"; + + /** + * 用户Token缓存前缀 + */ + String USER_TOKEN_CACHE_PREFIX = "AUTH:USER_TOKEN:"; +} diff --git a/src/main/java/com/datao/admin/common/constant/SystemConstants.java b/src/main/java/com/datao/admin/common/constant/SystemConstants.java new file mode 100644 index 0000000..b32268d --- /dev/null +++ b/src/main/java/com/datao/admin/common/constant/SystemConstants.java @@ -0,0 +1,28 @@ +package com.datao.admin.common.constant; + +/** + * 系统常量 + * + * @author haoxr + * @date 2022/10/22 + */ +public interface SystemConstants { + + /** + * 根节点ID + */ + Long ROOT_NODE_ID = 0l; + + + /** + * 系统默认密码 + */ + String DEFAULT_PASSWORD = "123456"; + + /** + * 超级管理员角色编码 + */ + String ROOT_ROLE_CODE = "ROOT"; + + +} diff --git a/src/main/java/com/datao/admin/common/enums/DataScopeEnum.java b/src/main/java/com/datao/admin/common/enums/DataScopeEnum.java new file mode 100644 index 0000000..1102ad9 --- /dev/null +++ b/src/main/java/com/datao/admin/common/enums/DataScopeEnum.java @@ -0,0 +1,32 @@ +package com.datao.admin.common.enums; + +import com.datao.admin.common.base.IBaseEnum; +import lombok.Getter; + +/** + * 数据权限枚举 + * + * @author haoxr + * @date 2022/10/14 + */ +public enum DataScopeEnum implements IBaseEnum { + + /** + * value 越小,数据权限范围越大 + */ + ALL(0, "所有数据"), + DEPT_AND_SUB(1, "部门及子部门数据"), + DEPT(2, "本部门数据"), + SELF(3, "本人数据"); + + @Getter + private Integer value; + + @Getter + private String label; + + DataScopeEnum(Integer value, String label) { + this.value = value; + this.label = label; + } +} diff --git a/src/main/java/com/datao/admin/common/enums/GenderEnum.java b/src/main/java/com/datao/admin/common/enums/GenderEnum.java new file mode 100644 index 0000000..33469d7 --- /dev/null +++ b/src/main/java/com/datao/admin/common/enums/GenderEnum.java @@ -0,0 +1,29 @@ +package com.datao.admin.common.enums; + +import com.datao.admin.common.base.IBaseEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +/** + * 性别枚举 + * + * @author haoxr + * @date 2022/10/14 + */ +@Schema(enumAsRef = true) +public enum GenderEnum implements IBaseEnum { + + MALE(1, "男"), + FEMALE (2, "女"); + + @Getter + private Integer value; + + @Getter + private String label; + + GenderEnum(Integer value, String label) { + this.value = value; + this.label = label; + } +} diff --git a/src/main/java/com/datao/admin/common/enums/MenuTypeEnum.java b/src/main/java/com/datao/admin/common/enums/MenuTypeEnum.java new file mode 100644 index 0000000..45d0ef7 --- /dev/null +++ b/src/main/java/com/datao/admin/common/enums/MenuTypeEnum.java @@ -0,0 +1,35 @@ +package com.datao.admin.common.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.datao.admin.common.base.IBaseEnum; +import lombok.Getter; + +/** + * 菜单类型枚举 + * + * @author haoxr + * @date 2022/4/23 9:36 + */ + +public enum MenuTypeEnum implements IBaseEnum { + + NULL(0, null), + MENU(1, "菜单"), + CATALOG(2, "目录"), + EXTLINK(3, "外链"), + BUTTON(4, "按钮"); + + @Getter + @EnumValue // Mybatis-Plus 提供注解表示插入数据库时插入该值 + private Integer value; + + @Getter + // @JsonValue // 表示对枚举序列化时返回此字段 + private String label; + + MenuTypeEnum(Integer value, String label) { + this.value = value; + this.label = label; + } + +} diff --git a/src/main/java/com/datao/admin/common/enums/StatusEnum.java b/src/main/java/com/datao/admin/common/enums/StatusEnum.java new file mode 100644 index 0000000..f133c1d --- /dev/null +++ b/src/main/java/com/datao/admin/common/enums/StatusEnum.java @@ -0,0 +1,27 @@ +package com.datao.admin.common.enums; + +import com.datao.admin.common.base.IBaseEnum; +import lombok.Getter; + +/** + * 状态枚举 + * + * @author haoxr + * @date 2022/10/14 + */ +public enum StatusEnum implements IBaseEnum { + + ENABLE(1, "启用"), + DISABLE (0, "禁用"); + + @Getter + private Integer value; + + @Getter + private String label; + + StatusEnum(Integer value, String label) { + this.value = value; + this.label = label; + } +} diff --git a/src/main/java/com/datao/admin/common/exception/BusinessException.java b/src/main/java/com/datao/admin/common/exception/BusinessException.java new file mode 100644 index 0000000..e691e51 --- /dev/null +++ b/src/main/java/com/datao/admin/common/exception/BusinessException.java @@ -0,0 +1,35 @@ +package com.datao.admin.common.exception; + +import com.datao.admin.common.result.IResultCode; +import lombok.Getter; + +/** + * 自定义业务异常 + * + * @author haoxr + * @date 2022/7/31 + */ +@Getter +public class BusinessException extends RuntimeException { + + public IResultCode resultCode; + + public BusinessException(IResultCode errorCode) { + super(errorCode.getMsg()); + this.resultCode = errorCode; + } + + public BusinessException(String message){ + super(message); + } + + public BusinessException(String message, Throwable cause){ + super(message, cause); + } + + public BusinessException(Throwable cause){ + super(cause); + } + + +} diff --git a/src/main/java/com/datao/admin/common/exception/GlobalExceptionHandler.java b/src/main/java/com/datao/admin/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..dd84a7e --- /dev/null +++ b/src/main/java/com/datao/admin/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,211 @@ +package com.datao.admin.common.exception; + +import cn.hutool.core.util.StrUtil; +import com.datao.admin.common.result.Result; +import com.datao.admin.common.result.ResultCode; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.TypeMismatchException; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; + +import jakarta.servlet.ServletException; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; + +import java.sql.SQLSyntaxErrorException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 全局系统异常处理器 + *

+ * 调整异常处理的HTTP状态码,丰富异常处理类型 + * + * @author Gadfly + * @since 2020-02-25 13:54 + **/ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(BindException e) { + log.error("BindException:{}", e.getMessage()); + String msg = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";")); + return Result.failed(ResultCode.PARAM_ERROR, msg); + } + + /** + * RequestParam参数的校验 + * + * @param e + * @param + * @return + */ + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(ConstraintViolationException e) { + log.error("ConstraintViolationException:{}", e.getMessage()); + String msg = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";")); + return Result.failed(ResultCode.PARAM_ERROR, msg); + } + + /** + * RequestBody参数的校验 + * + * @param e + * @param + * @return + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(MethodArgumentNotValidException e) { + log.error("MethodArgumentNotValidException:{}", e.getMessage()); + String msg = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";")); + return Result.failed(ResultCode.PARAM_ERROR, msg); + } + + @ExceptionHandler(NoHandlerFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public Result processException(NoHandlerFoundException e) { + log.error(e.getMessage(), e); + return Result.failed(ResultCode.RESOURCE_NOT_FOUND); + } + + /** + * MissingServletRequestParameterException + */ + @ExceptionHandler(MissingServletRequestParameterException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(MissingServletRequestParameterException e) { + log.error(e.getMessage(), e); + return Result.failed(ResultCode.PARAM_IS_NULL); + } + + /** + * MethodArgumentTypeMismatchException + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(MethodArgumentTypeMismatchException e) { + log.error(e.getMessage(), e); + return Result.failed(ResultCode.PARAM_ERROR, "类型错误"); + } + + /** + * ServletException + */ + @ExceptionHandler(ServletException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(ServletException e) { + log.error(e.getMessage(), e); + return Result.failed(e.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleIllegalArgumentException(IllegalArgumentException e) { + log.error("非法参数异常,异常原因:{}", e.getMessage(), e); + return Result.failed(e.getMessage()); + } + + @ExceptionHandler(JsonProcessingException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleJsonProcessingException(JsonProcessingException e) { + log.error("Json转换异常,异常原因:{}", e.getMessage(), e); + return Result.failed(e.getMessage()); + } + + /** + * HttpMessageNotReadableException + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(HttpMessageNotReadableException e) { + log.error(e.getMessage(), e); + String errorMessage = "请求体不可为空"; + Throwable cause = e.getCause(); + if (cause != null) { + errorMessage = convertMessage(cause); + } + return Result.failed(errorMessage); + } + + @ExceptionHandler(TypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result processException(TypeMismatchException e) { + log.error(e.getMessage(), e); + return Result.failed(e.getMessage()); + } + + @ExceptionHandler(BadSqlGrammarException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public Result handleBadSqlGrammarException(BadSqlGrammarException e) { + log.error(e.getMessage(), e); + String errorMsg = e.getMessage(); + if (StrUtil.isNotBlank(errorMsg) && errorMsg.contains("denied to user")) { + return Result.failed(ResultCode.FORBIDDEN_OPERATION); + } else { + return Result.failed(e.getMessage()); + } + } + + @ExceptionHandler(SQLSyntaxErrorException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public Result processSQLSyntaxErrorException(SQLSyntaxErrorException e) { + log.error(e.getMessage(), e); + return Result.failed(e.getMessage()); + } + + + @ExceptionHandler(BusinessException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleBizException(BusinessException e) { + log.error("biz exception: {}", e.getMessage()); + if (e.getResultCode() != null) { + return Result.failed(e.getResultCode()); + } + return Result.failed(e.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleException(Exception e) { + log.error("unknown exception: {}", e.getMessage()); + return Result.failed(e.getLocalizedMessage()); + } + + /** + * 传参类型错误时,用于消息转换 + * + * @param throwable 异常 + * @return 错误信息 + */ + private String convertMessage(Throwable throwable) { + String error = throwable.toString(); + String regulation = "\\[\"(.*?)\"]+"; + Pattern pattern = Pattern.compile(regulation); + Matcher matcher = pattern.matcher(error); + String group = ""; + if (matcher.find()) { + String matchString = matcher.group(); + matchString = matchString.replace("[", "").replace("]", ""); + matchString = "%s字段类型错误".formatted(matchString.replaceAll("\\\"", "")); + group += matchString; + } + return group; + } +} diff --git a/src/main/java/com/datao/admin/common/result/IResultCode.java b/src/main/java/com/datao/admin/common/result/IResultCode.java new file mode 100644 index 0000000..8982bd4 --- /dev/null +++ b/src/main/java/com/datao/admin/common/result/IResultCode.java @@ -0,0 +1,12 @@ +package com.datao.admin.common.result; + +/** + * @author haoxr + **/ +public interface IResultCode { + + String getCode(); + + String getMsg(); + +} diff --git a/src/main/java/com/datao/admin/common/result/PageResult.java b/src/main/java/com/datao/admin/common/result/PageResult.java new file mode 100644 index 0000000..73a4f4b --- /dev/null +++ b/src/main/java/com/datao/admin/common/result/PageResult.java @@ -0,0 +1,46 @@ +package com.datao.admin.common.result; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页响应结构体 + * + * @author haoxr + * @date 2022/2/18 23:29 + */ +@Data +public class PageResult implements Serializable { + + private String code; + + private Data data; + + private String msg; + + public static PageResult success(IPage page) { + PageResult result = new PageResult<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + + Data data = new Data(); + data.setList(page.getRecords()); + data.setTotal(page.getTotal()); + + result.setData(data); + result.setMsg(ResultCode.SUCCESS.getMsg()); + return result; + } + + @lombok.Data + public static class Data { + + private List list; + + private long total; + + } + +} diff --git a/src/main/java/com/datao/admin/common/result/Result.java b/src/main/java/com/datao/admin/common/result/Result.java new file mode 100644 index 0000000..a3878e0 --- /dev/null +++ b/src/main/java/com/datao/admin/common/result/Result.java @@ -0,0 +1,73 @@ +package com.datao.admin.common.result; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 统一响应结构体 + * + * @author haoxr + * @date 2022/1/30 + **/ +@Data +public class Result implements Serializable { + + private String code; + + private T data; + + private String msg; + + public static Result success() { + return success(null); + } + + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMsg(ResultCode.SUCCESS.getMsg()); + result.setData(data); + return result; + } + + public static Result failed() { + return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), ResultCode.SYSTEM_EXECUTION_ERROR.getMsg(), null); + } + + public static Result failed(String msg) { + return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), msg, null); + } + + public static Result judge(boolean status) { + if (status) { + return success(); + } else { + return failed(); + } + } + + public static Result failed(IResultCode resultCode) { + return result(resultCode.getCode(), resultCode.getMsg(), null); + } + + public static Result failed(IResultCode resultCode, String msg) { + return result(resultCode.getCode(), msg, null); + } + + private static Result result(IResultCode resultCode, T data) { + return result(resultCode.getCode(), resultCode.getMsg(), data); + } + + private static Result result(String code, String msg, T data) { + Result result = new Result<>(); + result.setCode(code); + result.setData(data); + result.setMsg(msg); + return result; + } + + public static boolean isSuccess(Result result) { + return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode()); + } +} diff --git a/src/main/java/com/datao/admin/common/result/ResultCode.java b/src/main/java/com/datao/admin/common/result/ResultCode.java new file mode 100644 index 0000000..b14cff5 --- /dev/null +++ b/src/main/java/com/datao/admin/common/result/ResultCode.java @@ -0,0 +1,114 @@ +package com.datao.admin.common.result; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 响应码枚举 + * + * @author haoxr + * @date 2020-06-23 + **/ +@AllArgsConstructor +@NoArgsConstructor +public enum ResultCode implements IResultCode, Serializable { + + SUCCESS("00000", "一切ok"), + + USER_ERROR("A0001", "用户端错误"), + REPEAT_SUBMIT_ERROR("A0002", "您的请求已提交,请不要重复提交或等待片刻再尝试。"), + + USER_LOGIN_ERROR("A0200", "用户登录异常"), + + USER_NOT_EXIST("A0201", "用户不存在"), + USER_ACCOUNT_LOCKED("A0202", "用户账户被冻结"), + USER_ACCOUNT_INVALID("A0203", "用户账户已作废"), + + USERNAME_OR_PASSWORD_ERROR("A0210", "用户名或密码错误"), + PASSWORD_ENTER_EXCEED_LIMIT("A0211", "用户输入密码次数超限"), + CLIENT_AUTHENTICATION_FAILED("A0212", "客户端认证失败"), + + VERIFY_CODE_TIMEOUT("A0213", "验证码已过期"), + VERIFY_CODE_ERROR("A0214", "验证码错误"), + + TOKEN_INVALID("A0230", "token无效或已过期"), + TOKEN_ACCESS_FORBIDDEN("A0231", "token已被禁止访问"), + + AUTHORIZED_ERROR("A0300", "访问权限异常"), + ACCESS_UNAUTHORIZED("A0301", "访问未授权"), + FORBIDDEN_OPERATION("A0302", "演示环境禁止新增、修改和删除数据,请本地部署后测试"), + + + PARAM_ERROR("A0400", "用户请求参数错误"), + RESOURCE_NOT_FOUND("A0401", "请求资源不存在"), + PARAM_IS_NULL("A0410", "请求必填参数为空"), + + USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"), + USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"), + USER_UPLOAD_FILE_SIZE_EXCEEDS("A0702", "用户上传文件太大"), + USER_UPLOAD_IMAGE_SIZE_EXCEEDS("A0703", "用户上传图片太大"), + + SYSTEM_EXECUTION_ERROR("B0001", "系统执行出错"), + SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"), + SYSTEM_ORDER_PROCESSING_TIMEOUT("B0100", "系统订单处理超时"), + + SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被出发"), + FLOW_LIMITING("B0210", "系统限流"), + DEGRADATION("B0220", "系统功能降级"), + + SYSTEM_RESOURCE_ERROR("B0300", "系统资源异常"), + SYSTEM_RESOURCE_EXHAUSTION("B0310", "系统资源耗尽"), + SYSTEM_RESOURCE_ACCESS_ERROR("B0320", "系统资源访问异常"), + SYSTEM_READ_DISK_FILE_ERROR("B0321", "系统读取磁盘文件失败"), + + CALL_THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"), + MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错"), + INTERFACE_NOT_EXIST("C0113", "接口不存在"), + + MESSAGE_SERVICE_ERROR("C0120", "消息服务出错"), + MESSAGE_DELIVERY_ERROR("C0121", "消息投递出错"), + MESSAGE_CONSUMPTION_ERROR("C0122", "消息消费出错"), + MESSAGE_SUBSCRIPTION_ERROR("C0123", "消息订阅出错"), + MESSAGE_GROUP_NOT_FOUND("C0124", "消息分组未查到"), + + DATABASE_ERROR("C0300", "数据库服务出错"), + DATABASE_TABLE_NOT_EXIST("C0311", "表不存在"), + DATABASE_COLUMN_NOT_EXIST("C0312", "列不存在"), + DATABASE_DUPLICATE_COLUMN_NAME("C0321", "多表关联中存在多个相同名称的列"), + DATABASE_DEADLOCK("C0331", "数据库死锁"), + DATABASE_PRIMARY_KEY_CONFLICT("C0341", "主键冲突"); + + @Override + public String getCode() { + return code; + } + + @Override + public String getMsg() { + return msg; + } + + private String code; + + private String msg; + + @Override + public String toString() { + return "{" + + "\"code\":\"" + code + '\"' + + ", \"msg\":\"" + msg + '\"' + + '}'; + } + + + public static ResultCode getValue(String code){ + for (ResultCode value : values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return SYSTEM_EXECUTION_ERROR; // 默认系统执行错误 + } +} diff --git a/src/main/java/com/datao/admin/common/util/ExcelUtils.java b/src/main/java/com/datao/admin/common/util/ExcelUtils.java new file mode 100644 index 0000000..01e3a87 --- /dev/null +++ b/src/main/java/com/datao/admin/common/util/ExcelUtils.java @@ -0,0 +1,21 @@ +package com.datao.admin.common.util; + +import com.alibaba.excel.EasyExcel; +import com.datao.admin.framework.easyexcel.MyAnalysisEventListener; + +import java.io.InputStream; + +/** + * Excel 工具类 + * + * @author: haoxr + * @date: 2023/03/01 + */ +public class ExcelUtils { + + public static String importExcel(InputStream is, Class clazz, MyAnalysisEventListener listener) { + EasyExcel.read(is, clazz, listener).sheet().doRead(); + String msg = listener.getMsg(); + return msg; + } +} diff --git a/src/main/java/com/datao/admin/common/util/RequestUtils.java b/src/main/java/com/datao/admin/common/util/RequestUtils.java new file mode 100644 index 0000000..62ef493 --- /dev/null +++ b/src/main/java/com/datao/admin/common/util/RequestUtils.java @@ -0,0 +1,24 @@ +package com.datao.admin.common.util; + +import cn.hutool.core.util.StrUtil; +import com.datao.admin.common.constant.SecurityConstants; +import jakarta.servlet.http.HttpServletRequest; + +/** + * 请求工具类 + * + * @author haoxr + */ +public class RequestUtils { + + /** + * 请求头解析获取 Token + */ + public static String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(SecurityConstants.TOKEN_KEY); + if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(SecurityConstants.TOKEN_PREFIX)) { + return bearerToken.substring(SecurityConstants.TOKEN_PREFIX.length()); + } + return null; + } +} diff --git a/src/main/java/com/datao/admin/common/util/ResponseUtils.java b/src/main/java/com/datao/admin/common/util/ResponseUtils.java new file mode 100644 index 0000000..fd8507a --- /dev/null +++ b/src/main/java/com/datao/admin/common/util/ResponseUtils.java @@ -0,0 +1,46 @@ +package com.datao.admin.common.util; + +import cn.hutool.json.JSONUtil; +import com.datao.admin.common.result.Result; +import com.datao.admin.common.result.ResultCode; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 响应工具类 + * + * @author haoxr + * @date 2022/10/18 + */ +public class ResponseUtils { + + /** + * 异常消息返回(适用过滤器中处理异常响应) + * + * @param response + * @param resultCode + */ + public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode) throws IOException { + switch (resultCode) { + case ACCESS_UNAUTHORIZED: + case TOKEN_INVALID: + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + break; + case TOKEN_ACCESS_FORBIDDEN: + response.setStatus(HttpStatus.FORBIDDEN.value()); + break; + default: + response.setStatus(HttpStatus.BAD_REQUEST.value()); + break; + } + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().print(JSONUtil.toJsonStr(Result.failed(resultCode))); + } + + +} diff --git a/src/main/java/com/datao/admin/config/CorsConfig.java b/src/main/java/com/datao/admin/config/CorsConfig.java new file mode 100644 index 0000000..3addbbc --- /dev/null +++ b/src/main/java/com/datao/admin/config/CorsConfig.java @@ -0,0 +1,42 @@ +package com.datao.admin.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import java.util.Collections; + +/** + * CORS资源共享配置 + * + * @author haoxr + * @date 2023/4/17 + */ +@Configuration +public class CorsConfig { + + @Bean + public FilterRegistrationBean filterRegistrationBean() { + CorsConfiguration corsConfiguration = new CorsConfiguration(); + //1.允许任何来源 + corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*")); + //2.允许任何请求头 + corsConfiguration.addAllowedHeader(CorsConfiguration.ALL); + //3.允许任何方法 + corsConfiguration.addAllowedMethod(CorsConfiguration.ALL); + //4.允许凭证 + corsConfiguration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", corsConfiguration); + CorsFilter corsFilter = new CorsFilter(source); + + FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean<>(corsFilter); + filterRegistrationBean.setOrder(-101); // 小于 SpringSecurity Filter的 Order(-100) 即可 + + return filterRegistrationBean; + } +} diff --git a/src/main/java/com/datao/admin/config/MybatisPlusConfig.java b/src/main/java/com/datao/admin/config/MybatisPlusConfig.java new file mode 100644 index 0000000..6c25cdf --- /dev/null +++ b/src/main/java/com/datao/admin/config/MybatisPlusConfig.java @@ -0,0 +1,55 @@ +package com.datao.admin.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.datao.admin.framework.mybatisplus.MyDataPermissionHandler; +import com.datao.admin.framework.mybatisplus.MyMetaObjectHandler; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * mybatis-plus 配置类 + * + * @author haoxr + * @since 2022/7/2 + */ +@Configuration +@EnableTransactionManagement +public class MybatisPlusConfig { + + + @Value("${system-config.data-permission.enabled}") + private Boolean dataPermissionEnabled; + + /** + * 分页插件和数据权限插件 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + //数据权限 + if (dataPermissionEnabled) { + interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataPermissionHandler())); + } + //分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + + return interceptor; + } + + /** + * 自动填充数据库创建人、创建时间、更新人、更新时间 + */ + @Bean + public GlobalConfig globalConfig() { + GlobalConfig globalConfig = new GlobalConfig(); + globalConfig.setMetaObjectHandler(new MyMetaObjectHandler()); + return globalConfig; + } + +} diff --git a/src/main/java/com/datao/admin/config/RedisConfig.java b/src/main/java/com/datao/admin/config/RedisConfig.java new file mode 100644 index 0000000..03ce3a1 --- /dev/null +++ b/src/main/java/com/datao/admin/config/RedisConfig.java @@ -0,0 +1,42 @@ +package com.datao.admin.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * Redis 配置 + * + * @author haoxr + * @since 2023/5/15 + */ +@Configuration +public class RedisConfig { + + /** + * 自定义 RedisTemplate + *

+ * 修改 Redis 序列化方式,默认 JdkSerializationRedisSerializer + * + * @param redisConnectionFactory {@link RedisConnectionFactory} + * @return {@link RedisTemplate} + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + + redisTemplate.setKeySerializer(RedisSerializer.string()); + redisTemplate.setValueSerializer(RedisSerializer.json()); + + redisTemplate.setHashKeySerializer(RedisSerializer.string()); + redisTemplate.setHashValueSerializer(RedisSerializer.json()); + + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + +} diff --git a/src/main/java/com/datao/admin/config/RestTemplateConfig.java b/src/main/java/com/datao/admin/config/RestTemplateConfig.java new file mode 100644 index 0000000..8f3811f --- /dev/null +++ b/src/main/java/com/datao/admin/config/RestTemplateConfig.java @@ -0,0 +1,21 @@ +package com.datao.admin.config; + +//import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import jakarta.validation.constraints.NotNull; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +import java.time.Duration; + +//@Configuration +public class RestTemplateConfig { +// @Bean + public RestTemplate restTemplate(@NotNull RestTemplateBuilder builder){ + return builder.setConnectTimeout(Duration.ofMillis(5000)) + .setReadTimeout(Duration.ofMillis(10000)) +// .additionalMessageConverters(new FastJsonHttpMessageConverter()) + .build(); + } +} diff --git a/src/main/java/com/datao/admin/config/SwaggerConfig.java b/src/main/java/com/datao/admin/config/SwaggerConfig.java new file mode 100644 index 0000000..4427a71 --- /dev/null +++ b/src/main/java/com/datao/admin/config/SwaggerConfig.java @@ -0,0 +1,71 @@ +package com.datao.admin.config; + +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Swagger 配置 + *

+ * Spring Doc FAQ: https://springdoc.org/#faq + * + * @author haoxr + * @date 2023/2/17 + */ +@Configuration +public class SwaggerConfig { + + /** + * 接口信息 + */ + @Bean + public OpenAPI apiInfo() { + return new OpenAPI() + .components(new Components() + .addSecuritySchemes("Authorization", + new SecurityScheme().type(SecurityScheme.Type.HTTP) + .scheme("bearer").bearerFormat("JWT") + ) + ) + .info(new Info() + .title("datao-admin 接口文档") + .version("2.0.0") + .description("接口文档") + .license(new License().name("Apache 2.0") + .url("https://www.youlai.tech")) + ); + } + + /** + * 接口分组-系统接口 + * + * @return + */ + @Bean + public GroupedOpenApi groupedOpenApi() { + String paths[] = {"/api/**"}; + + String packagesToScan[] = {"com.datao.admin.controller"}; + return GroupedOpenApi.builder().group("系统接口") + .packagesToScan(packagesToScan) + .pathsToMatch(paths) + .build(); + } + + + + + + + +} diff --git a/src/main/java/com/datao/admin/config/WebMvcConfig.java b/src/main/java/com/datao/admin/config/WebMvcConfig.java new file mode 100644 index 0000000..18923b2 --- /dev/null +++ b/src/main/java/com/datao/admin/config/WebMvcConfig.java @@ -0,0 +1,60 @@ +package com.datao.admin.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.validator.HibernateValidator; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.math.BigInteger; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.TimeZone; + +@Configuration +@Slf4j +public class WebMvcConfig implements WebMvcConfigurer { + + @Override + public void configureMessageConverters(List> converters) { + converters.add(new StringHttpMessageConverter()); + + MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); + ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); + objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); + + // 后台Long值传递给前端精度丢失问题(JS最大精度整数是Math.pow(2,53)) + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Long.class, ToStringSerializer.instance); + simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance); + objectMapper.registerModule(simpleModule); + + jackson2HttpMessageConverter.setObjectMapper(objectMapper); + converters.add(jackson2HttpMessageConverter); + } + + @Bean + public Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) { + ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) + .configure() + .failFast(true) // failFast=true 不校验所有参数,只要出现校验失败情况直接返回,不再进行后续参数校验 + .constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory)) + .buildValidatorFactory(); + + return validatorFactory.getValidator(); + } +} diff --git a/src/main/java/com/datao/admin/controller/AuthController.java b/src/main/java/com/datao/admin/controller/AuthController.java new file mode 100644 index 0000000..f3d781e --- /dev/null +++ b/src/main/java/com/datao/admin/controller/AuthController.java @@ -0,0 +1,93 @@ +package com.datao.admin.controller; + +import cn.hutool.core.util.StrUtil; +import com.datao.admin.common.constant.SecurityConstants; +import com.datao.admin.common.result.Result; +import com.datao.admin.common.util.RequestUtils; +import com.datao.admin.framework.easycaptcha.service.EasyCaptchaService; +import com.datao.admin.framework.security.JwtTokenManager; +import com.datao.admin.pojo.dto.CaptchaResult; +import com.datao.admin.pojo.dto.LoginResult; +import io.jsonwebtoken.Claims; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@Tag(name = "01.认证中心") +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +@Slf4j +public class AuthController { + private final AuthenticationManager authenticationManager; + private final JwtTokenManager jwtTokenManager; + private final EasyCaptchaService easyCaptchaService; + private final RedisTemplate redisTemplate; + + @Operation(summary = "登录") + @PostMapping("/login") + public Result login( + @Parameter(description = "用户名", example = "admin") @RequestParam String username, + @Parameter(description = "密码", example = "123456") @RequestParam String password + ) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + username.toLowerCase().trim(), + password + ); + Authentication authentication = authenticationManager.authenticate(authenticationToken); + // 生成token + String accessToken = jwtTokenManager.createToken(authentication); + LoginResult loginResult = LoginResult.builder() + .tokenType("Bearer") + .accessToken(accessToken) + .build(); + return Result.success(loginResult); + } + + @Operation(summary = "注销", security = {@SecurityRequirement(name = SecurityConstants.TOKEN_KEY)}) + @DeleteMapping("/logout") + public Result logout(HttpServletRequest request) { + String token = RequestUtils.resolveToken(request); + if (StrUtil.isNotBlank(token)) { + jwtTokenManager.deleteToken(token); +// Claims claims = jwtTokenManager.getTokenClaims(token); +//// String jti = claims.get("jti", String.class); +// Long userId = (Long) claims.get("userId"); +// redisTemplate.delete(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId); +// redisTemplate.delete(SecurityConstants.USER_TOKEN_CACHE_PREFIX + userId); + +// Date expiration = claims.getExpiration(); +// if (expiration != null) { +// // 有过期时间,在token有效时间内存入黑名单,超出时间移除黑名单节省内存占用 +// long ttl = (expiration.getTime() - System.currentTimeMillis()); +// redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null, ttl, TimeUnit.MILLISECONDS); +// } else { +// // 无过期时间,永久加入黑名单 +// redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null); +// } + } +// SecurityContextHolder.clearContext(); + return Result.success("注销成功"); + } + + @Operation(summary = "获取验证码") + @GetMapping("/captcha") + public Result getCaptcha() { + CaptchaResult captcha = easyCaptchaService.getCaptcha(); + return Result.success(captcha); + } + +} diff --git a/src/main/java/com/datao/admin/controller/DataServiceController.java b/src/main/java/com/datao/admin/controller/DataServiceController.java new file mode 100644 index 0000000..2e3b6b3 --- /dev/null +++ b/src/main/java/com/datao/admin/controller/DataServiceController.java @@ -0,0 +1,37 @@ +package com.datao.admin.controller; + +import com.datao.admin.common.result.Result; +import com.datao.admin.service.ProxyService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/data") +@RequiredArgsConstructor +//@Slf4j +public class DataServiceController { +// private final static String targetAddr = "http://localhost:10081"; +// public final static String DELEGATE_PREFIX = "/api/v1/data"; + + @Autowired + private ProxyService proxyService; + @Autowired + private Environment env; + + @RequestMapping(value = "/**", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "转发数据服务请求", security = {@SecurityRequirement(name = "Authorization")}) + public Result catchAll(HttpServletRequest request, HttpServletResponse response) { + String targetAddr = env.getProperty("proxy.target_url"); + String delegateApi = env.getProperty("proxy.delegate_api"); + return proxyService.redirect(request, response, targetAddr, delegateApi); + } +} diff --git a/src/main/java/com/datao/admin/controller/FileController.java b/src/main/java/com/datao/admin/controller/FileController.java new file mode 100644 index 0000000..25f9e7f --- /dev/null +++ b/src/main/java/com/datao/admin/controller/FileController.java @@ -0,0 +1,41 @@ +package com.datao.admin.controller; + +import com.datao.admin.common.result.Result; +import com.datao.admin.pojo.vo.FileInfoVO; +import com.datao.admin.service.FileService; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "07.文件接口") +@RestController +@RequestMapping("/api/v1/files") +@RequiredArgsConstructor +public class FileController { + + private final FileService fileService; + + @PostMapping + @Operation(summary = "文件上传", security = {@SecurityRequirement(name = "Authorization")}) + public Result uploadFile( + @Parameter(description ="表单文件对象") @RequestParam(value = "file") MultipartFile file + ) { + FileInfoVO fileInfoVO = fileService.uploadFile(file); + return Result.success(fileInfoVO); + } + + @DeleteMapping + @Operation(summary = "文件删除", security = {@SecurityRequirement(name = "Authorization")}) + @SneakyThrows + public Result deleteFile( + @Parameter(description ="文件路径") @RequestParam String filePath + ) { + boolean result = fileService.deleteFile(filePath); + return Result.judge(result); + } +} diff --git a/src/main/java/com/datao/admin/controller/SysDeptController.java b/src/main/java/com/datao/admin/controller/SysDeptController.java new file mode 100644 index 0000000..c8bc350 --- /dev/null +++ b/src/main/java/com/datao/admin/controller/SysDeptController.java @@ -0,0 +1,85 @@ +package com.datao.admin.controller; + +import com.datao.admin.common.result.Result; +import com.datao.admin.pojo.form.DeptForm; +import com.datao.admin.pojo.query.DeptQuery; +import com.datao.admin.pojo.vo.DeptVO; +import com.datao.admin.pojo.vo.Option; +import com.datao.admin.service.SysDeptService; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 部门控制器 + * + * @author haoxr + * @date 2020/11/6 + */ +@Tag(name = "05.部门接口") +@RestController +@RequestMapping("/api/v1/dept") +@RequiredArgsConstructor +public class SysDeptController { + private final SysDeptService deptService; + @Operation(summary = "获取部门列表", security = {@SecurityRequirement(name = "Authorization")}) + @GetMapping + public Result> listDepartments( + @ParameterObject DeptQuery queryParams + ) { + List list = deptService.listDepartments(queryParams); + return Result.success(list); + } + + @Operation(summary = "获取部门下拉选项", security = {@SecurityRequirement(name = "Authorization")}) + @GetMapping("/options") + public Result> listDeptOptions() { + List

+ * https://gitee.com/baomidou/mybatis-plus/issues/I37I90 + * + * @author zc + * @date 2021-12-10 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface DataPermission { + + /** + * 数据权限 {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor} + */ + String deptAlias() default ""; + + String deptIdColumnName() default "dept_id"; + + String userAlias() default ""; + + String userIdColumnName() default "create_by"; + +} + diff --git a/src/main/java/com/datao/admin/framework/mybatisplus/MyDataPermissionHandler.java b/src/main/java/com/datao/admin/framework/mybatisplus/MyDataPermissionHandler.java new file mode 100644 index 0000000..f85a7b0 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/mybatisplus/MyDataPermissionHandler.java @@ -0,0 +1,100 @@ +package com.datao.admin.framework.mybatisplus; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler; +import com.datao.admin.framework.security.util.SecurityUtils; +import com.datao.admin.common.base.IBaseEnum; +import com.datao.admin.common.enums.DataScopeEnum; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; + +import java.lang.reflect.Method; + +/** + * 数据权限控制器 + * + * @author zc + * @date 2021-12-10 13:28 + */ +@Slf4j +public class MyDataPermissionHandler implements DataPermissionHandler { + + @Override + @SneakyThrows + public Expression getSqlSegment(Expression where, String mappedStatementId) { + // 超级管理员不受数据权限控制 + if (SecurityUtils.isRoot()) { + return where; + } + Class clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringPool.DOT))); + String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + 1); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + DataPermission annotation = method.getAnnotation(DataPermission.class); + if (ObjectUtils.isNotEmpty(annotation) + && (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) { + return dataScopeFilter(annotation.deptAlias(), annotation.deptIdColumnName(), annotation.userAlias(), annotation.userIdColumnName(), where); + } + } + return where; + } + + /** + * 构建过滤条件 + * + * @param where 当前查询条件 + * @return 构建后查询条件 + */ + @SneakyThrows + public static Expression dataScopeFilter(String deptAlias, String deptIdColumnName, String userAlias, String userIdColumnName, Expression where) { + + + String deptColumnName = StrUtil.isNotBlank(deptAlias) ? (deptAlias + StringPool.DOT + deptIdColumnName) : deptIdColumnName; + String userColumnName = StrUtil.isNotBlank(userAlias) ? (userAlias + StringPool.DOT + userIdColumnName) : userIdColumnName; + + // 获取当前用户的数据权限 + Integer dataScope = SecurityUtils.getDataScope(); + + DataScopeEnum dataScopeEnum = IBaseEnum.getEnumByValue(dataScope, DataScopeEnum.class); + + Long deptId, userId; + String appendSqlStr; + switch (dataScopeEnum) { + case ALL: + return where; + case DEPT: + deptId = SecurityUtils.getDeptId(); + appendSqlStr = deptColumnName + StringPool.EQUALS + deptId; + break; + case SELF: + userId = SecurityUtils.getUserId(); + appendSqlStr = userColumnName + StringPool.EQUALS + userId; + break; + // 默认部门及子部门数据权限 + default: + deptId = SecurityUtils.getDeptId(); + appendSqlStr = deptColumnName + " IN ( SELECT id FROM sys_dept WHERE id = " + deptId + " OR FIND_IN_SET( " + deptId + " , tree_path ) )"; + break; + } + + if (StrUtil.isBlank(appendSqlStr)) { + return where; + } + + Expression appendExpression = CCJSqlParserUtil.parseCondExpression(appendSqlStr); + + if (where == null) { + return appendExpression; + } + + return new AndExpression(where, appendExpression); + } + + +} + diff --git a/src/main/java/com/datao/admin/framework/mybatisplus/MyMetaObjectHandler.java b/src/main/java/com/datao/admin/framework/mybatisplus/MyMetaObjectHandler.java new file mode 100644 index 0000000..7c2a43a --- /dev/null +++ b/src/main/java/com/datao/admin/framework/mybatisplus/MyMetaObjectHandler.java @@ -0,0 +1,40 @@ +package com.datao.admin.framework.mybatisplus; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * mybatis-plus 字段自动填充 + * + * @author haoxr + * @date 2022/10/14 + * @link https://mp.baomidou.com/guide/auto-fill-metainfo.html + */ +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + /** + * 新增填充创建时间 + * + * @param metaObject + */ + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); + this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); + } + + /** + * 更新填充更新时间 + * + * @param metaObject + */ + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); + } + +} diff --git a/src/main/java/com/datao/admin/framework/resubmit/Resubmit.java b/src/main/java/com/datao/admin/framework/resubmit/Resubmit.java new file mode 100644 index 0000000..0bd8c8f --- /dev/null +++ b/src/main/java/com/datao/admin/framework/resubmit/Resubmit.java @@ -0,0 +1,25 @@ +package com.datao.admin.framework.resubmit; + + +import java.lang.annotation.*; + +/** + * 防重复提交注解 + * + * @author haoxr + * @since 2023/5/9 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface Resubmit { + + /** + * 防重提交锁过期时间(秒) + *

+ * 默认5秒内不允许重复提交 + */ + int expire() default 5; + +} diff --git a/src/main/java/com/datao/admin/framework/resubmit/ResubmitAspect.java b/src/main/java/com/datao/admin/framework/resubmit/ResubmitAspect.java new file mode 100644 index 0000000..4e508de --- /dev/null +++ b/src/main/java/com/datao/admin/framework/resubmit/ResubmitAspect.java @@ -0,0 +1,80 @@ +package com.datao.admin.framework.resubmit; + +import cn.hutool.core.util.StrUtil; +import com.datao.admin.common.result.ResultCode; +import com.datao.admin.common.util.RequestUtils; +import com.datao.admin.framework.security.JwtTokenManager; +import com.datao.admin.common.exception.BusinessException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.concurrent.TimeUnit; + +/** + * 防重复提交切面 + * + * @author : haoxr + * @since : 2023/05/09 + */ +@Aspect +@Component +@Slf4j +@RequiredArgsConstructor +public class ResubmitAspect { + + private final RedissonClient redissonClient; + + private final JwtTokenManager jwtTokenManager; + + private static final String RESUBMIT_LOCK_PREFIX = "LOCK:RESUBMIT:"; + + /** + * 防重复提交切点 + */ + @Pointcut("@annotation(resubmit)") + public void preventDuplicateSubmitPointCut(Resubmit resubmit) { + log.info("定义防重复提交切点"); + } + + @Around("preventDuplicateSubmitPointCut(resubmit)") + public Object doAround(ProceedingJoinPoint pjp, Resubmit resubmit) throws Throwable { + + String resubmitLockKey = generateResubmitLockKey(); + if (resubmitLockKey != null) { + int expire = resubmit.expire(); // 防重提交锁过期时间 + RLock lock = redissonClient.getLock(resubmitLockKey); + boolean lockResult = lock.tryLock(0, expire, TimeUnit.SECONDS); // 获取锁失败,直接返回 false + if (!lockResult) { + throw new BusinessException(ResultCode.REPEAT_SUBMIT_ERROR); // 抛出重复提交提示信息 + } + } + Object result = pjp.proceed(); + return result; + } + + + /** + * 获取防重提交锁的 key + */ + private String generateResubmitLockKey() { + String resubmitLockKey = null; + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String jwt = RequestUtils.resolveToken(request); + if (StrUtil.isNotBlank(jwt)) { + String jti = (String) jwtTokenManager.getTokenClaims(jwt).get("jti"); + resubmitLockKey = RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI() + "-"; + } + return resubmitLockKey; + } + +} diff --git a/src/main/java/com/datao/admin/framework/security/JwtTokenManager.java b/src/main/java/com/datao/admin/framework/security/JwtTokenManager.java new file mode 100644 index 0000000..34bd0fa --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/JwtTokenManager.java @@ -0,0 +1,179 @@ +package com.datao.admin.framework.security; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.IdUtil; +import com.datao.admin.common.constant.SecurityConstants; +import com.datao.admin.framework.security.userdetails.SysUserDetails; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.io.DecodingException; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; +import jakarta.annotation.Resource; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * JWT token manager + * + * @author haoxr + * @date 2022/10/22 + */ +@Component +public class JwtTokenManager { + + /** + * token加密密钥 + */ + @Value("${auth.token.secret_key}") + private String secretKey; + + /** + * token有效期(单位:秒) + */ + @Value("${auth.token.ttl}") + private Long tokenTtl; + + /** + * secret key byte array. + */ + private byte[] secretKeyBytes; + + private JwtParser jwtParser; + + @Resource + private RedisTemplate redisTemplate; + + /** + * Create token. + * + * @param authentication auth info + * @return token + */ + public String createToken(Authentication authentication) { + Claims claims = Jwts.claims().setSubject(authentication.getName()); + SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); + + Long userId = userDetails.getUserId(); + String uuid = IdUtil.fastSimpleUUID(); + claims.put("jti", uuid); + claims.put("userId", userId); + claims.put("username", claims.getSubject()); + claims.put("deptId", userDetails.getDeptId()); + claims.put("dataScope", userDetails.getDataScope()); + + // 角色放入JWT的claims + Set roles = userDetails.getAuthorities().stream() + .map(item -> item.getAuthority()).collect(Collectors.toSet()); + claims.put("authorities", roles); + + // 权限数据多放入Redis + Set perms = userDetails.getPerms(); + redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId, perms); + + // 缓存用户token + redisTemplate.opsForValue().set(SecurityConstants.USER_TOKEN_CACHE_PREFIX + userId, uuid, tokenTtl, TimeUnit.SECONDS); + + // 过期时间 +// Date expirationTime = new Date(System.currentTimeMillis() + tokenTtl * 1000L); + return Jwts.builder() + //.setId(IdUtil.fastSimpleUUID()) TODO 设置jti无效 + .setClaims(claims) +// .setExpiration(expirationTime) + .signWith(Keys.hmacShaKeyFor(this.getSecretKeyBytes()), SignatureAlgorithm.HS256).compact(); + } + + /** + * 获取认证信息 + */ + public Authentication getAuthentication( Claims claims) { + SysUserDetails principal = new SysUserDetails(); + principal.setUserId(Convert.toLong(claims.get("userId"))); // 用户ID + principal.setUsername(Convert.toStr(claims.get("username"))); // 用户名 + principal.setDeptId(Convert.toLong(claims.get("deptId"))); // 部门ID + principal.setDataScope(Convert.toInt(claims.get("dataScope"))); // 数据权限 + + List authorities = ((ArrayList) claims.get("authorities")) + .stream() + .map(role -> new SimpleGrantedAuthority(role)) + .collect(Collectors.toList()); + + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + /** + * 解析 & 验证 token + */ + public Claims parseAndValidateToken(String token) { + // 解析成功说明JWT有效 + Claims claims = this.getTokenClaims(token); + Integer userId = (Integer) claims.get("userId"); + if(redisTemplate.hasKey(SecurityConstants.USER_TOKEN_CACHE_PREFIX + userId)){ + String uuidCache = (String) redisTemplate.opsForValue().get(SecurityConstants.USER_TOKEN_CACHE_PREFIX + userId); + String uuidToken = (String) claims.get("jti"); + if(Objects.equals(uuidToken, uuidCache)) { + redisTemplate.expire(SecurityConstants.USER_TOKEN_CACHE_PREFIX + userId, tokenTtl, TimeUnit.SECONDS); + } + else{ + throw new RuntimeException("登录已失效"); + } + } + else{ + throw new RuntimeException("登录已失效"); + } +// if(claims.getExpiration().getTime() < System.currentTimeMillis()){ +// throw new RuntimeException("token 已过期"); +// } +// // 验证JWT 是否在黑名单(注销场景会存入黑名单) +// Boolean isBlack = redisTemplate.hasKey(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + claims.get("jti")); +// +// if (isBlack) { +// throw new RuntimeException("token 已被禁用"); +// } + return claims; + } + + /** + * 删除缓存 + * @return + */ + public void deleteToken(String token){ + Claims claims = this.getTokenClaims(token); + if(claims!=null) { + Integer userId = (Integer) claims.get("userId"); + redisTemplate.delete(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId); + redisTemplate.delete(SecurityConstants.USER_TOKEN_CACHE_PREFIX + userId); + } + } + + public byte[] getSecretKeyBytes() { + if (secretKeyBytes == null) { + try { + secretKeyBytes = Decoders.BASE64.decode(secretKey); + } catch (DecodingException e) { + secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8); + } + } + return secretKeyBytes; + } + + + /** + * get token claims + */ + public Claims getTokenClaims(String token) { + if (jwtParser == null) { + jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build(); + } + Claims claims = jwtParser.parseClaimsJws(token).getBody(); + return claims; + } +} diff --git a/src/main/java/com/datao/admin/framework/security/config/SecurityConfig.java b/src/main/java/com/datao/admin/framework/security/config/SecurityConfig.java new file mode 100644 index 0000000..902f90b --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/config/SecurityConfig.java @@ -0,0 +1,101 @@ +package com.datao.admin.framework.security.config; + +import com.datao.admin.framework.security.JwtTokenManager; +import com.datao.admin.framework.security.filter.JwtAuthenticationFilter; +import com.datao.admin.framework.security.filter.VerifyCodeFilter; +import com.datao.admin.common.constant.SecurityConstants; +import com.datao.admin.framework.security.exception.MyAccessDeniedHandler; +import com.datao.admin.framework.security.exception.MyAuthenticationEntryPoint; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +/** + * Spring Security 权限配置 + * + * @author haoxr + * @date 2023/2/17 + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +@RequiredArgsConstructor +public class SecurityConfig { + + private final MyAuthenticationEntryPoint myAuthenticationEntryPoint; + private final MyAccessDeniedHandler myAccessDeniedHandler; + private final JwtTokenManager jwtTokenManager; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeHttpRequests() + .requestMatchers(SecurityConstants.LOGIN_PATH).permitAll() // 登录接口放行但会走过滤器链-验证码校验 + .anyRequest().authenticated() + .and() + .exceptionHandling() + .authenticationEntryPoint(myAuthenticationEntryPoint) + .accessDeniedHandler(myAccessDeniedHandler) + ; + + // 验证码校验过滤器 + http.addFilterBefore(new VerifyCodeFilter(),UsernamePasswordAuthenticationFilter.class); + // JWT 校验过滤器 + http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenManager), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + /** + * 不走过滤器链的放行配置 + * + * @return + */ + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring() + .requestMatchers( + "/api/v1/auth/captcha", + "/webjars/**", + "/doc.html", + "/swagger-resources/**", + "/v3/api-docs/**", + "/swagger-ui/**" + ); + } + + /** + * 密码编码器 + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * 无法直接注入 AuthenticationManager + * + * @param authenticationConfiguration + * @return + * @throws Exception + */ + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + +} diff --git a/src/main/java/com/datao/admin/framework/security/exception/MyAccessDeniedHandler.java b/src/main/java/com/datao/admin/framework/security/exception/MyAccessDeniedHandler.java new file mode 100644 index 0000000..057f60f --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/exception/MyAccessDeniedHandler.java @@ -0,0 +1,25 @@ +package com.datao.admin.framework.security.exception; + +import com.datao.admin.common.result.ResultCode; +import com.datao.admin.common.util.ResponseUtils; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Spring Security访问异常处理器 + * + * @author haoxr + * @date 2022/10/18 + */ +@Component +public class MyAccessDeniedHandler implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_ACCESS_FORBIDDEN); + } +} diff --git a/src/main/java/com/datao/admin/framework/security/exception/MyAuthenticationEntryPoint.java b/src/main/java/com/datao/admin/framework/security/exception/MyAuthenticationEntryPoint.java new file mode 100644 index 0000000..5129807 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/exception/MyAuthenticationEntryPoint.java @@ -0,0 +1,26 @@ +package com.datao.admin.framework.security.exception; + +import com.datao.admin.common.result.ResultCode; +import com.datao.admin.common.util.ResponseUtils; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 认证异常处理 + * + * @author haoxr + * @date 2022/10/18 + */ +@Component +public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID); + } +} diff --git a/src/main/java/com/datao/admin/framework/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/datao/admin/framework/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..5245845 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,78 @@ +package com.datao.admin.framework.security.filter; + +import cn.hutool.core.util.StrUtil; +import com.datao.admin.framework.security.JwtTokenManager; +import com.datao.admin.common.constant.SecurityConstants; +import com.datao.admin.common.result.ResultCode; +import com.datao.admin.common.util.RequestUtils; +import com.datao.admin.common.util.ResponseUtils; +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * JWT 校验过滤器 + * + * @author haoxr + * @date 2022/10/1 + */ +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST"); + + private final JwtTokenManager tokenManager; + + public JwtAuthenticationFilter(JwtTokenManager tokenManager) { + this.tokenManager = tokenManager; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + // 登录接口放行是走过滤器链的方式(验证码校验过滤器),这里拦截到登录接口需要手动放行 + if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) { + // 手动放行登录接口 + chain.doFilter(request, response); + }else{ + String jwt = RequestUtils.resolveToken(request); + if (StrUtil.isNotBlank(jwt)){ + try { + // 解析&验证 JWT + Claims claims = this.tokenManager.parseAndValidateToken(jwt); + + // JWT验证有效获取Authentication存入Security上下文 + Authentication authentication = this.tokenManager.getAuthentication(claims); + SecurityContextHolder.getContext().setAuthentication(authentication); + + chain.doFilter(request, response); + + SecurityContextHolder.clearContext(); + }catch (Exception e){ + ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID); + } +// if (StrUtil.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) { +// try { +// // 解析&验证 JWT +// Claims claims = this.tokenManager.parseAndValidateToken(jwt); +// +// // JWT验证有效获取Authentication存入Security上下文 +// Authentication authentication = this.tokenManager.getAuthentication(claims); +// SecurityContextHolder.getContext().setAuthentication(authentication); +// +// chain.doFilter(request, response); +// }catch (Exception e){ +// ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID); +// } + }else{ + ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID); + } + } + } +} diff --git a/src/main/java/com/datao/admin/framework/security/filter/VerifyCodeFilter.java b/src/main/java/com/datao/admin/framework/security/filter/VerifyCodeFilter.java new file mode 100644 index 0000000..2752066 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/filter/VerifyCodeFilter.java @@ -0,0 +1,66 @@ +package com.datao.admin.framework.security.filter; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.datao.admin.common.constant.SecurityConstants; +import com.datao.admin.common.result.ResultCode; +import com.datao.admin.common.util.ResponseUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + + +/** + * 验证码校验过滤器 + * + * @author haoxr + * @date 2022/10/1 + */ +public class VerifyCodeFilter extends OncePerRequestFilter { + + private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST"); + + public static final String VERIFY_CODE_PARAM_KEY = "verifyCode"; + public static final String VERIFY_CODE_KEY_PARAM_KEY = "verifyCodeKey"; + + @Override + public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { + // 检验登录接口的验证码 + if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) { + // 请求中的验证码 + String requestVerifyCode = request.getParameter(VERIFY_CODE_PARAM_KEY); + + // TODO 兼容 2.0.0 无验证码版本,后续移除 + if (StrUtil.isBlank(requestVerifyCode)) { + // 非登录接口放行 + chain.doFilter(request, response); + return; + } + // 缓存中的验证码 + RedisTemplate redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class); + String verifyCodeKey = request.getParameter(VERIFY_CODE_KEY_PARAM_KEY); + Object cacheVerifyCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_CACHE_PREFIX + verifyCodeKey); + if (cacheVerifyCode == null) { + ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT); + } else { + // 验证码比对 + if (StrUtil.equals(requestVerifyCode, Convert.toStr(cacheVerifyCode))) { + chain.doFilter(request, response); + } else { + ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_ERROR); + } + } + } else { + // 非登录接口放行 + chain.doFilter(request, response); + } + } + +} diff --git a/src/main/java/com/datao/admin/framework/security/service/PermissionService.java b/src/main/java/com/datao/admin/framework/security/service/PermissionService.java new file mode 100644 index 0000000..4229423 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/service/PermissionService.java @@ -0,0 +1,55 @@ +package com.datao.admin.framework.security.service; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.datao.admin.common.constant.SecurityConstants; +import com.datao.admin.framework.security.util.SecurityUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.PatternMatchUtils; + +import java.util.Set; + +/** + * SpringSecurity权限校验 + * + * @author haoxr + * @date 2022/2/22 + */ +@Service("ss") +@RequiredArgsConstructor +public class PermissionService { + + private final RedisTemplate redisTemplate; + + /** + * 判断当前登录用户是否拥有操作权限 + * + * @param perm 权限标识(eg: sys:user:add) + * @return + */ + public boolean hasPerm(String perm) { + + if (StrUtil.isBlank(perm)) { + return false; + } + // 超级管理员放行 + if (SecurityUtils.isRoot()) { + return true; + } + + Long userId = SecurityUtils.getUserId(); + + Set perms = (Set) redisTemplate.opsForValue().get(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId); // 权限数据用户登录成功节点存入redis,详见 JwtTokenManager#createToken() + + if (CollectionUtil.isEmpty(perms)) { + return false; + } + boolean hasPermission = perms.stream() + .anyMatch(item -> PatternMatchUtils.simpleMatch(perm, item)); // *号匹配任意字符 + return hasPermission; + } + + +} diff --git a/src/main/java/com/datao/admin/framework/security/userdetails/SysUserDetails.java b/src/main/java/com/datao/admin/framework/security/userdetails/SysUserDetails.java new file mode 100644 index 0000000..3663720 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/userdetails/SysUserDetails.java @@ -0,0 +1,103 @@ +package com.datao.admin.framework.security.userdetails; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.datao.admin.pojo.bo.UserAuthInfo; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Spring Security + * + * @author haoxr + */ +@Data +public class SysUserDetails implements UserDetails { + + private Long userId; + + private String username; + + private String password; + + private Boolean enabled; + + private Collection authorities; + + private Set perms; + + private Long deptId; + + private Integer dataScope; + + public SysUserDetails() { + + } + + public SysUserDetails(UserAuthInfo user) { + this.userId = user.getUserId(); + Set roles = user.getRoles(); + Set authorities; + if (CollectionUtil.isNotEmpty(roles)) { + authorities = roles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) // 标识角色 + .collect(Collectors.toSet()); + } else { + authorities = Collections.EMPTY_SET; + } + this.authorities = authorities; + this.username = user.getUsername(); + this.password = user.getPassword(); + this.enabled = ObjectUtil.equal(user.getStatus(), 1); + this.perms = user.getPerms(); + this.deptId = user.getDeptId(); + this.dataScope = user.getDataScope(); + } + + public Long getUserId() { + return this.userId; + } + + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } +} diff --git a/src/main/java/com/datao/admin/framework/security/userdetails/SysUserDetailsService.java b/src/main/java/com/datao/admin/framework/security/userdetails/SysUserDetailsService.java new file mode 100644 index 0000000..5cc4217 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/userdetails/SysUserDetailsService.java @@ -0,0 +1,31 @@ +package com.datao.admin.framework.security.userdetails; + +import com.datao.admin.pojo.bo.UserAuthInfo; +import com.datao.admin.service.SysUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * 用户认证信息实现类 + * + * @author haoxr + */ +@Service +@RequiredArgsConstructor +public class SysUserDetailsService implements UserDetailsService { + + private final SysUserService sysUserService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + UserAuthInfo userAuthInfo = sysUserService.getUserAuthInfo(username); + if (userAuthInfo == null) { + throw new UsernameNotFoundException(username); + } + return new SysUserDetails(userAuthInfo); + } +} diff --git a/src/main/java/com/datao/admin/framework/security/util/SecurityUtils.java b/src/main/java/com/datao/admin/framework/security/util/SecurityUtils.java new file mode 100644 index 0000000..81db32f --- /dev/null +++ b/src/main/java/com/datao/admin/framework/security/util/SecurityUtils.java @@ -0,0 +1,141 @@ +package com.datao.admin.framework.security.util; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.datao.admin.common.constant.SystemConstants; +import com.datao.admin.framework.security.userdetails.SysUserDetails; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.PatternMatchUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +public class SecurityUtils { + + /** + * 获取当前登录人信息 + * + * @return + */ + public static SysUserDetails getUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Object principal = authentication.getPrincipal(); + if (principal instanceof SysUserDetails) { + return (SysUserDetails) authentication.getPrincipal(); + } + } + return null; + } + + /** + * 获取用户ID + * + * @return + */ + public static Long getUserId() { + Long userId = Convert.toLong(getUser().getUserId()); + return userId; + } + + /** + * 获取部门ID + * + * @return + */ + public static Long getDeptId() { + Long userId = Convert.toLong(getUser().getDeptId()); + return userId; + } + + /** + * 获取数据权限范围 + * + * @return DataScope + */ + public static Integer getDataScope() { + Integer dataScope = Convert.toInt(getUser().getDataScope()); + return dataScope; + } + + + /** + * 获取用户角色集合 + * + * @return + */ + public static Set getRoles() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Collection authorities = authentication.getAuthorities(); + if (CollectionUtil.isNotEmpty(authorities)) { + Set roles = authorities.stream().filter(item -> item.getAuthority().startsWith("ROLE_")) + .map(item -> StrUtil.removePrefix(item.getAuthority(), "ROLE_")) + .collect(Collectors.toSet()); + return roles; + } + } + return Collections.EMPTY_SET; + } + + /** + * 获取用户权限集合 + * + * @return + */ + public static Set getPerms() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Collection authorities = authentication.getAuthorities(); + if (CollectionUtil.isNotEmpty(authorities)) { + Set perms = authorities.stream().filter(item -> !item.getAuthority().startsWith("ROLE_")) + .map(item -> item.getAuthority()) + .collect(Collectors.toSet()); + return perms; + } + } + return Collections.EMPTY_SET; + } + + /** + * 是否超级管理员 + *

+ * 超级管理员忽视任何权限判断 + * + * @return + */ + public static boolean isRoot() { + Set roles = getRoles(); + + if (roles.contains(SystemConstants.ROOT_ROLE_CODE)) { + return true; + } + return false; + } + + + /** + * 是否拥有权限判断 + *

+ * 适用业务判断(接口权限判断适用Spring Security 自带注解 PreAuthorize 判断即可 ) + * + * @return + */ + public static boolean hasPerm(String perm) { + + if (isRoot()) { + return true; + } + + Set perms = getPerms(); + + boolean hasPerm = perms.stream().anyMatch(item -> PatternMatchUtils.simpleMatch(perm, item)); + return hasPerm; + } + +} diff --git a/src/main/java/com/datao/admin/framework/xxljob/XxlJobConfig.java b/src/main/java/com/datao/admin/framework/xxljob/XxlJobConfig.java new file mode 100644 index 0000000..8439fa1 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/xxljob/XxlJobConfig.java @@ -0,0 +1,61 @@ +package com.datao.admin.framework.xxljob; + +//import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * xxl-job config + * + * @author xuxueli 2017-04-28 + */ +@Configuration +@ConditionalOnProperty(name = "xxl.job.enabled")// xxl.job.enabled = true 才会自动配置 +@Slf4j +public class XxlJobConfig { + + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + @Value("${xxl.job.accessToken}") + private String accessToken; + + @Value("${xxl.job.executor.appname}") + private String appname; + + @Value("${xxl.job.executor.address}") + private String address; + + @Value("${xxl.job.executor.ip}") + private String ip; + + @Value("${xxl.job.executor.port}") + private int port; + + @Value("${xxl.job.executor.logpath}") + private String logPath; + + @Value("${xxl.job.executor.logretentiondays}") + private int logRetentionDays; + + +// @Bean +// public XxlJobSpringExecutor xxlJobExecutor() { +// log.info(">>>>>>>>>>> xxl-job config init."); +// XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); +// xxlJobSpringExecutor.setAdminAddresses(adminAddresses); +// xxlJobSpringExecutor.setAppname(appname); +// xxlJobSpringExecutor.setAddress(address); +// xxlJobSpringExecutor.setIp(ip); +// xxlJobSpringExecutor.setPort(port); +// xxlJobSpringExecutor.setAccessToken(accessToken); +// xxlJobSpringExecutor.setLogPath(logPath); +// xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); +// +// return xxlJobSpringExecutor; +// } + +} diff --git a/src/main/java/com/datao/admin/framework/xxljob/XxlJobSampleHandler.java b/src/main/java/com/datao/admin/framework/xxljob/XxlJobSampleHandler.java new file mode 100644 index 0000000..80fa298 --- /dev/null +++ b/src/main/java/com/datao/admin/framework/xxljob/XxlJobSampleHandler.java @@ -0,0 +1,21 @@ +package com.datao.admin.framework.xxljob; + +//import com.xxl.job.core.context.XxlJobHelper; +//import com.xxl.job.core.handler.annotation.XxlJob; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +//@Component +//public class XxlJobSampleHandler { +// @XxlJob("demoJobHandler") +// public void demoJobHandler() throws Exception { +// XxlJobHelper.log("XXL-JOB, Hello World."); +// +// for (int i = 0; i < 5; i++) { +// XxlJobHelper.log("beat at:" + i); +// TimeUnit.SECONDS.sleep(2); +// } +// } +// +//} diff --git a/src/main/java/com/datao/admin/listener/UserImportListener.java b/src/main/java/com/datao/admin/listener/UserImportListener.java new file mode 100644 index 0000000..868a77f --- /dev/null +++ b/src/main/java/com/datao/admin/listener/UserImportListener.java @@ -0,0 +1,176 @@ +package com.datao.admin.listener; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.excel.context.AnalysisContext; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.datao.admin.common.base.IBaseEnum; +import com.datao.admin.common.enums.GenderEnum; +import com.datao.admin.common.enums.StatusEnum; +import com.datao.admin.converter.UserConverter; +import com.datao.admin.framework.easyexcel.MyAnalysisEventListener; +import com.datao.admin.common.constant.SystemConstants; +import com.datao.admin.pojo.entity.SysRole; +import com.datao.admin.pojo.entity.SysUser; +import com.datao.admin.pojo.entity.SysUserRole; +import com.datao.admin.pojo.vo.UserImportVO; +import com.datao.admin.service.SysRoleService; +import com.datao.admin.service.SysUserRoleService; +import com.datao.admin.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户导入监听器 + *

+ * 最简单的读监听器:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read + * + * @author haoxr + * @since 2022/4/10 20:49 + */ +@Slf4j +public class UserImportListener extends MyAnalysisEventListener { + + + // 有效条数 + private int validCount; + + // 无效条数 + private int invalidCount; + + // 导入返回信息 + StringBuilder msg = new StringBuilder(); + + // 部门ID + private final Long deptId; + + private final SysUserService userService; + + private final PasswordEncoder passwordEncoder; + + private final UserConverter userConverter; + + private final SysRoleService roleService; + + private final SysUserRoleService userRoleService; + + public UserImportListener(Long deptId) { + this.deptId = deptId; + this.userService = SpringUtil.getBean(SysUserService.class); + this.passwordEncoder = SpringUtil.getBean(PasswordEncoder.class); + this.roleService = SpringUtil.getBean(SysRoleService.class); + this.userRoleService = SpringUtil.getBean(SysUserRoleService.class); + this.userConverter = SpringUtil.getBean(UserConverter.class); + } + + /** + * 每一条数据解析都会来调用 + *

+ * 1. 数据校验;全字段校验 + * 2. 数据持久化; + * + * @param userImportVO 一行数据,类似于 {@link AnalysisContext#readRowHolder()} + * @param analysisContext + */ + @Override + public void invoke(UserImportVO userImportVO, AnalysisContext analysisContext) { + log.info("解析到一条用户数据:{}", JSONUtil.toJsonStr(userImportVO)); + // 校验数据 + StringBuilder validationMsg = new StringBuilder(); + + String username = userImportVO.getUsername(); + if (StrUtil.isBlank(username)) { + validationMsg.append("用户名为空;"); + } else { + long count = userService.count(new LambdaQueryWrapper().eq(SysUser::getUsername, username)); + if (count > 0) { + validationMsg.append("用户名已存在;"); + } + } + + String nickname = userImportVO.getNickname(); + if (StrUtil.isBlank(nickname)) { + validationMsg.append("用户昵称为空;"); + } + + String mobile = userImportVO.getMobile(); + if (StrUtil.isBlank(mobile)) { + validationMsg.append("手机号码为空;"); + } else { + if (!Validator.isMobile(mobile)) { + validationMsg.append("手机号码不正确;"); + } + } + + if (validationMsg.length() == 0) { + // 校验通过,持久化至数据库 + SysUser entity = userConverter.importVo2Entity(userImportVO); + entity.setDeptId(deptId); // 部门 + entity.setPassword(passwordEncoder.encode(SystemConstants.DEFAULT_PASSWORD)); // 默认密码 + // 性别翻译 + String genderLabel = userImportVO.getGender(); + if (StrUtil.isNotBlank(genderLabel)) { + Integer genderValue = (Integer) IBaseEnum.getValueByLabel(genderLabel, GenderEnum.class); + entity.setGender(genderValue); + } + + // 角色解析 + String roleCodes = userImportVO.getRoleCodes(); + List roleIds = null; + if (StrUtil.isNotBlank(roleCodes)) { + roleIds = roleService.list( + new LambdaQueryWrapper() + .in(SysRole::getCode, (Object) roleCodes.split(",")) + .eq(SysRole::getStatus, StatusEnum.ENABLE.getValue()) + .select(SysRole::getId) + ).stream() + .map(SysRole::getId) + .collect(Collectors.toList()); + } + + + boolean saveResult = userService.save(entity); + if (saveResult) { + validCount++; + // 保存用户角色关联 + if (CollectionUtil.isNotEmpty(roleIds)) { + List userRoles = roleIds.stream() + .map(roleId -> new SysUserRole(entity.getId(), roleId)) + .collect(Collectors.toList()); + userRoleService.saveBatch(userRoles); + } + } else { + invalidCount++; + msg.append("第" + (validCount + invalidCount) + "行数据保存失败;
"); + } + } else { + invalidCount++; + msg.append("第" + (validCount + invalidCount) + "行数据校验失败:").append(validationMsg + "
"); + } + } + + + /** + * 所有数据解析完成会来调用 + * + * @param analysisContext + */ + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + + } + + + @Override + public String getMsg() { + // 总结信息 + String summaryMsg = StrUtil.format("导入用户结束:成功{}条,失败{}条;
{}", validCount, invalidCount, msg); + return summaryMsg; + } +} diff --git a/src/main/java/com/datao/admin/mapper/SysDeptMapper.java b/src/main/java/com/datao/admin/mapper/SysDeptMapper.java new file mode 100644 index 0000000..072b55f --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysDeptMapper.java @@ -0,0 +1,20 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.datao.admin.framework.mybatisplus.DataPermission; +import com.datao.admin.pojo.entity.SysDept; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +@Mapper +public interface SysDeptMapper extends BaseMapper { + + @DataPermission(deptIdColumnName = "id") + @Override + List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); +} diff --git a/src/main/java/com/datao/admin/mapper/SysDictMapper.java b/src/main/java/com/datao/admin/mapper/SysDictMapper.java new file mode 100644 index 0000000..ac938ec --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysDictMapper.java @@ -0,0 +1,14 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datao.admin.pojo.entity.SysDict; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysDictMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/datao/admin/mapper/SysDictTypeMapper.java b/src/main/java/com/datao/admin/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..8f6c2c7 --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysDictTypeMapper.java @@ -0,0 +1,14 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datao.admin.pojo.entity.SysDictType; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysDictTypeMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/datao/admin/mapper/SysMenuMapper.java b/src/main/java/com/datao/admin/mapper/SysMenuMapper.java new file mode 100644 index 0000000..bccac04 --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysMenuMapper.java @@ -0,0 +1,30 @@ +package com.datao.admin.mapper; + +/** + * 菜单持久接口层 + * + * @author haoxr + * @date 2022/1/24 + */ + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datao.admin.pojo.entity.SysMenu; +import com.datao.admin.pojo.bo.RouteBO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Set; + +@Mapper +public interface SysMenuMapper extends BaseMapper { + + List listRoutes(); + + /** + * 获取角色权限集合 + * + * @param roles + * @return + */ + Set listRolePerms(Set roles); +} diff --git a/src/main/java/com/datao/admin/mapper/SysRoleMapper.java b/src/main/java/com/datao/admin/mapper/SysRoleMapper.java new file mode 100644 index 0000000..a0d329e --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysRoleMapper.java @@ -0,0 +1,20 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datao.admin.pojo.entity.SysRole; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Set; + +@Mapper +public interface SysRoleMapper extends BaseMapper { + + + /** + * 获取最大范围的数据权限 + * + * @param roles + * @return + */ + Integer getMaximumDataScope(Set roles); +} diff --git a/src/main/java/com/datao/admin/mapper/SysRoleMenuMapper.java b/src/main/java/com/datao/admin/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..509d56d --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysRoleMenuMapper.java @@ -0,0 +1,25 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datao.admin.pojo.entity.SysRoleMenu; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 角色菜单持久层 + * + * @author haoxr + * @date 2022/6/4 + */ +@Mapper +public interface SysRoleMenuMapper extends BaseMapper { + + /** + * 获取角色拥有的菜单ID集合 + * + * @param roleId + * @return + */ + List listMenuIdsByRoleId(Long roleId); +} diff --git a/src/main/java/com/datao/admin/mapper/SysUserMapper.java b/src/main/java/com/datao/admin/mapper/SysUserMapper.java new file mode 100644 index 0000000..78447f8 --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysUserMapper.java @@ -0,0 +1,59 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datao.admin.framework.mybatisplus.DataPermission; +import com.datao.admin.pojo.bo.UserBO; +import com.datao.admin.pojo.entity.SysUser; +import com.datao.admin.pojo.bo.UserAuthInfo; +import com.datao.admin.pojo.bo.UserFormBO; +import com.datao.admin.pojo.query.UserPageQuery; +import com.datao.admin.pojo.vo.UserExportVO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 用户持久层 + * + * @author haoxr + * @date 2022/1/14 + */ +@Mapper +public interface SysUserMapper extends BaseMapper { + + /** + * 获取用户分页列表 + * + * @param page + * @param queryParams 查询参数 + * @return + */ + @DataPermission(deptAlias = "u") + Page getUserPage(Page page, UserPageQuery queryParams); + + /** + * 获取用户表单详情 + * + * @param userId 用户ID + * @return + */ + UserFormBO getUserDetail(Long userId); + + /** + * 根据用户名获取认证信息 + * + * @param username + * @return + */ + UserAuthInfo getUserAuthInfo(String username); + + /** + * 获取导出用户列表 + * + * @param queryParams + * @return + */ + @DataPermission(deptAlias = "u") + List listExportUsers(UserPageQuery queryParams); +} diff --git a/src/main/java/com/datao/admin/mapper/SysUserRoleMapper.java b/src/main/java/com/datao/admin/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..88e5c1a --- /dev/null +++ b/src/main/java/com/datao/admin/mapper/SysUserRoleMapper.java @@ -0,0 +1,16 @@ +package com.datao.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datao.admin.pojo.entity.SysUserRole; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户角色持久层 + * + * @author haoxr + * @date 2022/1/15 + */ +@Mapper +public interface SysUserRoleMapper extends BaseMapper { + +} diff --git a/src/main/java/com/datao/admin/pojo/bo/RouteBO.java b/src/main/java/com/datao/admin/pojo/bo/RouteBO.java new file mode 100644 index 0000000..51a9823 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/bo/RouteBO.java @@ -0,0 +1,76 @@ +package com.datao.admin.pojo.bo; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.datao.admin.common.enums.MenuTypeEnum; +import lombok.Data; + +import java.util.List; + +/** + * 路由 + */ +@Data +public class RouteBO { + /** + * + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 父菜单ID + */ + private Long parentId; + + /** + * 菜单名称 + */ + private String name; + + /** + * 菜单类型(1-菜单;2-目录;3-外链;4-按钮权限) + */ + private MenuTypeEnum type; + + /** + * 路由路径(浏览器地址栏路径) + */ + private String path; + + /** + * 组件路径(vue页面完整路径,省略.vue后缀) + */ + private String component; + + /** + * 权限标识 + */ + private String perm; + + /** + * 显示状态(1:显示;0:隐藏) + */ + private Integer visible; + + /** + * 排序 + */ + private Integer sort; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 外链路径 + */ + private String redirectUrl; + + /** + * 拥有路由的权限 + */ + private List roles; + +} diff --git a/src/main/java/com/datao/admin/pojo/bo/UserAuthInfo.java b/src/main/java/com/datao/admin/pojo/bo/UserAuthInfo.java new file mode 100644 index 0000000..0322a0c --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/bo/UserAuthInfo.java @@ -0,0 +1,35 @@ +package com.datao.admin.pojo.bo; + +import lombok.Data; + +import java.util.Set; + +/** + * 用户认证信息 + * + * @author haoxr + * @date 2022/10/22 + * + */ +@Data +public class UserAuthInfo { + + private Long userId; + + private String username; + + private String nickname; + + private Long deptId; + + private String password; + + private Integer status; + + private Set roles; + + private Set perms; + + private Integer dataScope; + +} diff --git a/src/main/java/com/datao/admin/pojo/bo/UserBO.java b/src/main/java/com/datao/admin/pojo/bo/UserBO.java new file mode 100644 index 0000000..f6e80ee --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/bo/UserBO.java @@ -0,0 +1,72 @@ +package com.datao.admin.pojo.bo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 用户持久化对象 + * + * @author haoxr + * @date 2022/6/10 + */ +@Data +public class UserBO { + + /** + * 用户ID + */ + private Long id; + + /** + * 账户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 性别(1->男;2->女) + */ + private Integer gender; + + /** + * 头像URL + */ + private String avatar; + + /** + * 邮箱 + */ + private String email; + + /** + * 状态: 1->启用;0->禁用 + */ + private Integer status; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 角色名称,多个使用英文逗号(,)分割 + */ + private String roleNames; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date createTime; +} diff --git a/src/main/java/com/datao/admin/pojo/bo/UserFormBO.java b/src/main/java/com/datao/admin/pojo/bo/UserFormBO.java new file mode 100644 index 0000000..9e55492 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/bo/UserFormBO.java @@ -0,0 +1,66 @@ +package com.datao.admin.pojo.bo; + +import lombok.Data; + +import java.util.List; + +/** + * user表单持久化对象 + * + * @author haoxr + * @date 2022/6/10 + */ +@Data +public class UserFormBO { + + /** + * 用户ID + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 用户昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 性别(1:男;2:女) + */ + private Integer gender; + + /** + * 用户头像 + */ + private String avatar; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 状态(1:启用;0:禁用) + */ + private Integer status; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 角色ID集合 + */ + private List roleIds; + +} diff --git a/src/main/java/com/datao/admin/pojo/dto/CaptchaResult.java b/src/main/java/com/datao/admin/pojo/dto/CaptchaResult.java new file mode 100644 index 0000000..ec3d05f --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/dto/CaptchaResult.java @@ -0,0 +1,24 @@ +package com.datao.admin.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +/** + * 验证码响应对象 + * + * @author: haoxr + * @date: 2023/03/24 + */ +@Schema(description ="验证码响应对象") +@Builder +@Data +public class CaptchaResult { + + @Schema(description = "验证码缓存key") + private String verifyCodeKey; + + @Schema(description = "验证码图片Base64字符串") + private String verifyCodeBase64; + +} diff --git a/src/main/java/com/datao/admin/pojo/dto/LoginResult.java b/src/main/java/com/datao/admin/pojo/dto/LoginResult.java new file mode 100644 index 0000000..6b6b8bf --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/dto/LoginResult.java @@ -0,0 +1,24 @@ +package com.datao.admin.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +@Schema(description ="登录响应对象") +@Data +@Builder +public class LoginResult { + + @Schema(description = "访问token") + private String accessToken; + + @Schema(description = "token 类型",example = "Bearer") + private String tokenType; + + @Schema(description = "刷新token") + private String refreshToken; + + @Schema(description = "过期时间(单位:毫秒)") + private Long expires; + +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysDept.java b/src/main/java/com/datao/admin/pojo/entity/SysDept.java new file mode 100644 index 0000000..d8521d4 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysDept.java @@ -0,0 +1,54 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.datao.admin.common.base.BaseEntity; +import lombok.Data; + +/** + * 部门表 + */ +@Data +public class SysDept extends BaseEntity { + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 部门名称 + */ + private String name; + + /** + * 父节点id + */ + private Long parentId; + + /** + * 父节点id路径 + */ + private String treePath; + + /** + * 显示顺序 + */ + private Integer sort; + + /** + * 状态(1:正常;0:禁用) + */ + private Integer status; + + /** + * 逻辑删除标识(1:已删除;0:未删除) + */ + private Integer deleted; + + private Long createBy; + + private Long updateBy; + +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysDict.java b/src/main/java/com/datao/admin/pojo/entity/SysDict.java new file mode 100644 index 0000000..5b16db7 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysDict.java @@ -0,0 +1,63 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; + +import lombok.Data; + +/** + * 字典数据表 + * + * @author haoxr + * @date 2022/12/17 + */ +@Data +public class SysDict implements Serializable { + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 字典类型编码 + */ + private String typeCode; + + /** + * 字典项名称 + */ + private String name; + + /** + * 字典项值 + */ + private String value; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态(1:正常;0:禁用) + */ + private Integer status; + + /** + * 是否默认(1:是;0:否) + */ + private Integer defaulted; + + /** + * 备注 + */ + private String remark; + + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysDictType.java b/src/main/java/com/datao/admin/pojo/entity/SysDictType.java new file mode 100644 index 0000000..8adbda7 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysDictType.java @@ -0,0 +1,41 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.datao.admin.common.base.BaseEntity; +import lombok.Data; + +/** + * 字典类型实体 + * + * @author haoxr + * @date 2022/12/17 + */ +@Data +public class SysDictType extends BaseEntity { + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 类型名称 + */ + private String name; + + /** + * 类型编码 + */ + private String code; + + /** + * 状态(0:正常;1:禁用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysMenu.java b/src/main/java/com/datao/admin/pojo/entity/SysMenu.java new file mode 100644 index 0000000..1dd84a5 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysMenu.java @@ -0,0 +1,76 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import com.datao.admin.common.enums.MenuTypeEnum; +import com.datao.admin.common.base.BaseEntity; +import lombok.Data; + +/** + * 菜单实体对象 + * + * @author haoxr + * @date 2023/3/6 + */ +@TableName(value ="sys_menu") +@Data +public class SysMenu extends BaseEntity { + /** + * 菜单ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 父菜单ID + */ + private Long parentId; + + /** + * 菜单名称 + */ + private String name; + + /** + * 菜单类型(1-菜单;2-目录;3-外链;4-按钮权限) + */ + private MenuTypeEnum type; + + /** + * 路由路径(浏览器地址栏路径) + */ + private String path; + + /** + * 组件路径(vue页面完整路径,省略.vue后缀) + */ + private String component; + + /** + * 权限标识 + */ + private String perm; + + /** + * 显示状态(1:显示;0:隐藏) + */ + private Integer visible; + + /** + * 排序 + */ + private Integer sort; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 跳转路径 + */ + private String redirect; + +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysRole.java b/src/main/java/com/datao/admin/pojo/entity/SysRole.java new file mode 100644 index 0000000..9bc48a7 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysRole.java @@ -0,0 +1,50 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.datao.admin.common.base.BaseEntity; +import lombok.Data; + +/** + * 角色表 + */ +@TableName(value ="sys_role") +@Data +public class SysRole extends BaseEntity { + /** + * + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色编码 + */ + private String code; + + /** + * 显示顺序 + */ + private Integer sort; + + /** + * 角色状态(1-正常;0-停用) + */ + private Integer status; + + /** + * 逻辑删除标识(0-未删除;1-已删除) + */ + private Integer deleted; + + /** + * 数据权限 + */ + private Integer dataScope; +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysRoleMenu.java b/src/main/java/com/datao/admin/pojo/entity/SysRoleMenu.java new file mode 100644 index 0000000..f04d8bf --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysRoleMenu.java @@ -0,0 +1,28 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 角色和菜单关联表 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SysRoleMenu { + /** + * 角色ID + */ + private Long roleId; + + /** + * 菜单ID + */ + private Long menuId; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysUser.java b/src/main/java/com/datao/admin/pojo/entity/SysUser.java new file mode 100644 index 0000000..c99bfb0 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysUser.java @@ -0,0 +1,69 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.datao.admin.common.base.BaseEntity; +import lombok.Data; + +/** + * 用户表 + */ +@Data +public class SysUser extends BaseEntity { + /** + * + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 性别((1:男;2:女)) + */ + private Integer gender; + + /** + * 密码 + */ + private String password; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户头像 + */ + private String avatar; + + /** + * 联系方式 + */ + private String mobile; + + /** + * 用户状态((1:正常;0:禁用)) + */ + private Integer status; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 逻辑删除标识(0:未删除;1:已删除) + */ + private Integer deleted; + +} diff --git a/src/main/java/com/datao/admin/pojo/entity/SysUserRole.java b/src/main/java/com/datao/admin/pojo/entity/SysUserRole.java new file mode 100644 index 0000000..9107e6f --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/entity/SysUserRole.java @@ -0,0 +1,31 @@ +package com.datao.admin.pojo.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 用户和角色关联表 + * + * @author haoxr + * @date 2022/12/17 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SysUserRole { + /** + * 用户ID + */ + private Long userId; + + /** + * 角色ID + */ + private Long roleId; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/datao/admin/pojo/form/DeptForm.java b/src/main/java/com/datao/admin/pojo/form/DeptForm.java new file mode 100644 index 0000000..0ee9b35 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/form/DeptForm.java @@ -0,0 +1,27 @@ +package com.datao.admin.pojo.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "部门表单对象") +@Data +public class DeptForm { + + @Schema(description="部门ID") + private Long id; + + @Schema(description="部门名称") + private String name; + + @Schema(description="父部门ID") + @NotNull(message = "父部门ID不能为空") + private Long parentId; + + @Schema(description="状态(1:启用;0:禁用)") + private Integer status; + + @Schema(description="排序(数字越小排名越靠前)") + private Integer sort; + +} diff --git a/src/main/java/com/datao/admin/pojo/form/DictForm.java b/src/main/java/com/datao/admin/pojo/form/DictForm.java new file mode 100644 index 0000000..26006ba --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/form/DictForm.java @@ -0,0 +1,32 @@ +package com.datao.admin.pojo.form; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "字典表单对象") +@Data +public class DictForm { + + @Schema(description="字典ID") + private Long id; + + @Schema(description="类型编码") + private String typeCode; + + @Schema(description="字典名称") + private String name; + + @Schema(description="字典值") + private String value; + + @Schema(description="状态(1:启用;0:禁用)") + private Integer status; + + @Schema(description="排序") + private Integer sort; + + @Schema(description = "字典备注") + private String remark; + +} diff --git a/src/main/java/com/datao/admin/pojo/form/DictTypeForm.java b/src/main/java/com/datao/admin/pojo/form/DictTypeForm.java new file mode 100644 index 0000000..416c6c3 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/form/DictTypeForm.java @@ -0,0 +1,26 @@ +package com.datao.admin.pojo.form; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "字典类型") +@Data +public class DictTypeForm { + + @Schema(description="字典类型ID") + private Long id; + + @Schema(description="类型名称") + private String name; + + @Schema(description="类型编码") + private String code; + + @Schema(description="类型状态(1:启用;0:禁用)") + private Integer status; + + @Schema(description = "备注") + private String remark; + +} diff --git a/src/main/java/com/datao/admin/pojo/form/MenuForm.java b/src/main/java/com/datao/admin/pojo/form/MenuForm.java new file mode 100644 index 0000000..d00c23c --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/form/MenuForm.java @@ -0,0 +1,44 @@ +package com.datao.admin.pojo.form; + +import com.datao.admin.common.enums.MenuTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "菜单表单对象") +@Data +public class MenuForm { + + @Schema(description = "菜单ID") + private Long id; + + @Schema(description = "父菜单ID") + private Long parentId; + + @Schema(description = "菜单名称") + private String name; + + @Schema(description = "菜单类型(1-菜单;2-目录;3-外链;4-按钮权限)") + private MenuTypeEnum type; + + @Schema(description = "路由路径") + private String path; + + @Schema(description = "组件路径(vue页面完整路径,省略.vue后缀)") + private String component; + + @Schema(description = "权限标识") + private String perm; + + @Schema(description = "显示状态(1:显示;0:隐藏)") + private Integer visible; + + @Schema(description = "排序(数字越小排名越靠前)") + private Integer sort; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "跳转路径") + private String redirect; + +} diff --git a/src/main/java/com/datao/admin/pojo/form/RoleForm.java b/src/main/java/com/datao/admin/pojo/form/RoleForm.java new file mode 100644 index 0000000..f2c165c --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/form/RoleForm.java @@ -0,0 +1,33 @@ +package com.datao.admin.pojo.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +// import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "角色表单对象") +@Data +public class RoleForm { + + @Schema(description="角色ID") + private Long id; + + @Schema(description="角色名称") + @NotBlank(message = "角色名称不能为空") + private String name; + + @Schema(description="角色编码") + @NotBlank(message = "角色编码不能为空") + private String code; + + @Schema(description="排序") + private Integer sort; + + @Schema(description="角色状态(1-正常;0-停用)") + private Integer status; + + @Schema(description="数据权限") + private Integer dataScope; + +} diff --git a/src/main/java/com/datao/admin/pojo/form/UserForm.java b/src/main/java/com/datao/admin/pojo/form/UserForm.java new file mode 100644 index 0000000..2c6b47f --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/form/UserForm.java @@ -0,0 +1,54 @@ +package com.datao.admin.pojo.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import java.util.List; + +/** + * 用户表单对象 + * + * @author haoxr + * @date 2022/4/12 11:04 + */ +@Schema(description = "用户表单对象") +@Data +public class UserForm { + + @Schema(description="用户ID") + private Long id; + + @Schema(description="用户名") + @NotBlank(message = "用户名不能为空") + private String username; + + @Schema(description="昵称") + @NotBlank(message = "昵称不能为空") + private String nickname; + + @Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号码格式不正确") + private String mobile; + + @Schema(description="性别") + private Integer gender; + + @Schema(description="用户头像") + private String avatar; + + @Schema(description="邮箱") + private String email; + + @Schema(description="用户状态(1:正常;0:禁用)") + private Integer status; + + @Schema(description="部门ID") + private Long deptId; + + @Schema(description="角色ID集合") + @NotEmpty(message = "用户角色不能为空") + private List roleIds; + +} diff --git a/src/main/java/com/datao/admin/pojo/query/DeptQuery.java b/src/main/java/com/datao/admin/pojo/query/DeptQuery.java new file mode 100644 index 0000000..6501ba4 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/DeptQuery.java @@ -0,0 +1,23 @@ +package com.datao.admin.pojo.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 部门查询对象 + * + * @author haoxr + * @date 2022/6/11 + */ +@Schema(description ="部门分页查询对象") +@Data +public class DeptQuery { + + @Schema(description="关键字(部门名称)") + private String keywords; + + @Schema(description="状态(1->正常;0->禁用)") + private Integer status; + +} diff --git a/src/main/java/com/datao/admin/pojo/query/DictPageQuery.java b/src/main/java/com/datao/admin/pojo/query/DictPageQuery.java new file mode 100644 index 0000000..1d73e38 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/DictPageQuery.java @@ -0,0 +1,17 @@ +package com.datao.admin.pojo.query; + + +import com.datao.admin.common.base.BasePageQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description ="字典数据项分页查询对象") +@Data +public class DictPageQuery extends BasePageQuery { + + @Schema(description="关键字(字典项名称)") + private String keywords; + + @Schema(description="字典类型编码") + private String typeCode; +} diff --git a/src/main/java/com/datao/admin/pojo/query/DictTypePageQuery.java b/src/main/java/com/datao/admin/pojo/query/DictTypePageQuery.java new file mode 100644 index 0000000..8b0d39e --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/DictTypePageQuery.java @@ -0,0 +1,15 @@ +package com.datao.admin.pojo.query; + + +import com.datao.admin.common.base.BasePageQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description ="字典类型分页查询对象") +@Data +public class DictTypePageQuery extends BasePageQuery { + + @Schema(description="关键字(类型名称/类型编码)") + private String keywords; + +} diff --git a/src/main/java/com/datao/admin/pojo/query/MenuQuery.java b/src/main/java/com/datao/admin/pojo/query/MenuQuery.java new file mode 100644 index 0000000..835424e --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/MenuQuery.java @@ -0,0 +1,23 @@ +package com.datao.admin.pojo.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 菜单查询对象 + * + * @author haoxr + * @date 2022/10/28 + */ +@Schema(description ="部门分页查询对象") +@Data +public class MenuQuery { + + @Schema(description="关键字(菜单名称)") + private String keywords; + + @Schema(description="状态(1->显示;0->隐藏)") + private Integer status; + +} diff --git a/src/main/java/com/datao/admin/pojo/query/PermPageQuery.java b/src/main/java/com/datao/admin/pojo/query/PermPageQuery.java new file mode 100644 index 0000000..92fa37a --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/PermPageQuery.java @@ -0,0 +1,23 @@ +package com.datao.admin.pojo.query; + +import com.datao.admin.common.base.BasePageQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 权限分页查询对象 + * + * @author haoxr + * @date 2022/1/14 22:22 + */ +@Data +@Schema +public class PermPageQuery extends BasePageQuery { + + @Schema(description="权限名称") + private String name; + + @Schema(description="菜单ID") + private Long menuId; + +} diff --git a/src/main/java/com/datao/admin/pojo/query/RolePageQuery.java b/src/main/java/com/datao/admin/pojo/query/RolePageQuery.java new file mode 100644 index 0000000..9d2c989 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/RolePageQuery.java @@ -0,0 +1,19 @@ +package com.datao.admin.pojo.query; + +import com.datao.admin.common.base.BasePageQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 角色分页查询实体 + * + * @author haoxr + * @date 2022/6/3 + * + */ +@Data +public class RolePageQuery extends BasePageQuery { + + @Schema(description="关键字(角色名称/角色编码)") + private String keywords; +} diff --git a/src/main/java/com/datao/admin/pojo/query/UserPageQuery.java b/src/main/java/com/datao/admin/pojo/query/UserPageQuery.java new file mode 100644 index 0000000..24f50f9 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/query/UserPageQuery.java @@ -0,0 +1,26 @@ +package com.datao.admin.pojo.query; + +import com.datao.admin.common.base.BasePageQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 用户分页查询对象 + * + * @author haoxr + * @date 2022/1/14 + */ +@Schema +@Data +public class UserPageQuery extends BasePageQuery { + + @Schema(description="关键字(用户名/昵称/手机号)") + private String keywords; + + @Schema(description="用户状态") + private Integer status; + + @Schema(description="部门ID") + private Long deptId; + +} diff --git a/src/main/java/com/datao/admin/pojo/vo/DeptVO.java b/src/main/java/com/datao/admin/pojo/vo/DeptVO.java new file mode 100644 index 0000000..565e1d6 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/vo/DeptVO.java @@ -0,0 +1,39 @@ +package com.datao.admin.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "部门视图对象") +@Data +public class DeptVO { + + @Schema(description = "部门ID") + private Long id; + + @Schema(description = "父部门ID") + private Long parentId; + + @Schema(description = "部门名称") + private String name; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "状态(1:启用;0:禁用)") + private Integer status; + + @Schema(description = "子部门") + private List children; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime createTime; + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/datao/admin/pojo/vo/DictPageVO.java b/src/main/java/com/datao/admin/pojo/vo/DictPageVO.java new file mode 100644 index 0000000..4fbafa8 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/vo/DictPageVO.java @@ -0,0 +1,23 @@ +package com.datao.admin.pojo.vo; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description ="字典分页对象") +@Data +public class DictPageVO { + + @Schema(description="字典ID") + private Long id; + + @Schema(description="字典名称") + private String name; + + @Schema(description="字典值") + private String value; + + @Schema(description="状态(1:启用;0:禁用)") + private Integer status; + +} diff --git a/src/main/java/com/datao/admin/pojo/vo/DictTypePageVO.java b/src/main/java/com/datao/admin/pojo/vo/DictTypePageVO.java new file mode 100644 index 0000000..8316a3a --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/vo/DictTypePageVO.java @@ -0,0 +1,24 @@ +package com.datao.admin.pojo.vo; + + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description ="字典类型分页对象") +@Data +public class DictTypePageVO { + + @Schema(description="字典类型ID") + private Long id; + + @Schema(description="类型名称") + private String name; + + @Schema(description="类型编码") + private String code; + + @Schema(description="状态:1:启用;0:禁用") + private Integer status; + +} diff --git a/src/main/java/com/datao/admin/pojo/vo/FileInfoVO.java b/src/main/java/com/datao/admin/pojo/vo/FileInfoVO.java new file mode 100644 index 0000000..f02aeb2 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/vo/FileInfoVO.java @@ -0,0 +1,16 @@ +package com.datao.admin.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "文件对象") +@Data +public class FileInfoVO { + + @Schema(description = "文件名称") + private String name; + + @Schema(description = "文件URL") + private String url; + +} diff --git a/src/main/java/com/datao/admin/pojo/vo/MenuVO.java b/src/main/java/com/datao/admin/pojo/vo/MenuVO.java new file mode 100644 index 0000000..a0182af --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/vo/MenuVO.java @@ -0,0 +1,54 @@ +package com.datao.admin.pojo.vo; + +import com.datao.admin.common.enums.MenuTypeEnum; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description ="菜单视图对象") +@Data +public class MenuVO { + + @Schema(description = "菜单ID") + private Long id; + + @Schema(description = "父菜单ID") + private Long parentId; + + @Schema(description = "菜单名称") + private String name; + + @Schema(description = "ICON") + private String icon; + + @Schema(description = "路由名称") + private String routeName; + + @Schema(description = "路由相对路径") + private String routePath; + + @Schema(description = "组件路径") + private String component; + + @Schema(description = "菜单排序(数字越小排名越靠前)") + private Integer sort; + + @Schema(description = "菜单是否可见(1:显示;0:隐藏)") + private Integer visible; + + @Schema(description = "跳转路径") + private String redirect; + + @Schema(description="菜单类型") + private MenuTypeEnum type; + + @Schema(description="按钮权限标识") + private String perm; + + @Schema(description = "子菜单") + @JsonInclude(value = JsonInclude.Include.NON_NULL) + private List children; + +} diff --git a/src/main/java/com/datao/admin/pojo/vo/Option.java b/src/main/java/com/datao/admin/pojo/vo/Option.java new file mode 100644 index 0000000..8b9dea2 --- /dev/null +++ b/src/main/java/com/datao/admin/pojo/vo/Option.java @@ -0,0 +1,42 @@ +package com.datao.admin.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 下拉选项对象 + * + * @author haoxr + * @date 2022/1/22 + */ +@Schema(description ="下拉选项对象") +@Data +@NoArgsConstructor +public class Option { + + public Option(T value, String label) { + this.value = value; + this.label = label; + } + + public Option(T value, String label, List

+ * 已实现 MinIO + * + * @author haoxr + * @date 2022/11/19 + */ +public interface FileService { + + /** + * 上传文件 + * @param file 表单文件对象 + * @return + */ + FileInfoVO uploadFile(MultipartFile file); + + /** + * 删除文件 + * + * @param filePath + * @return + */ + boolean deleteFile(String filePath); + + +} diff --git a/src/main/java/com/datao/admin/service/ProxyService.java b/src/main/java/com/datao/admin/service/ProxyService.java new file mode 100644 index 0000000..c3b205e --- /dev/null +++ b/src/main/java/com/datao/admin/service/ProxyService.java @@ -0,0 +1,72 @@ +package com.datao.admin.service; + +import com.datao.admin.common.result.Result; +import io.swagger.v3.core.util.Json; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.List; + +import static com.datao.admin.common.result.ResultCode.CALL_THIRD_PARTY_SERVICE_ERROR; + +@Service +public class ProxyService { + public Result redirect(HttpServletRequest request, HttpServletResponse response, String routeUrl, String prefix) { + try { + String redirectUrl = createRedictUrl(request,routeUrl, prefix); + RequestEntity requestEntity = createRequestEntity(request, redirectUrl); + return route(requestEntity); + } catch (Exception e) { + return Result.failed(e.getMessage()); //new ResponseEntity("REDIRECT ERROR", HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + private String createRedictUrl(HttpServletRequest request, String routeUrl, String prefix) { + String queryString = request.getQueryString(); + return routeUrl + request.getRequestURI().replace(prefix, "") + + (queryString != null ? "?" + queryString : ""); + } + + + private RequestEntity createRequestEntity(HttpServletRequest request, String url) throws URISyntaxException, IOException { + String method = request.getMethod(); + HttpMethod httpMethod = HttpMethod.valueOf(method); + MultiValueMap headers = parseRequestHeader(request); + byte[] body = parseRequestBody(request); + return new RequestEntity<>(body, headers, httpMethod, new URI(url)); + } + + private Result route(RequestEntity requestEntity) { + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange(requestEntity, Result.class); + return response.getBody(); + } + + + private byte[] parseRequestBody(HttpServletRequest request) throws IOException { + InputStream inputStream = request.getInputStream(); + return StreamUtils.copyToByteArray(inputStream); + } + + private MultiValueMap parseRequestHeader(HttpServletRequest request) { + HttpHeaders headers = new HttpHeaders(); + List headerNames = Collections.list(request.getHeaderNames()); + for (String headerName : headerNames) { + List headerValues = Collections.list(request.getHeaders(headerName)); + for (String headerValue : headerValues) { + headers.add(headerName, headerValue); + } + } + return headers; + } +} diff --git a/src/main/java/com/datao/admin/service/SysDeptService.java b/src/main/java/com/datao/admin/service/SysDeptService.java new file mode 100644 index 0000000..ba54792 --- /dev/null +++ b/src/main/java/com/datao/admin/service/SysDeptService.java @@ -0,0 +1,65 @@ +package com.datao.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.datao.admin.pojo.vo.Option; +import com.datao.admin.pojo.entity.SysDept; +import com.datao.admin.pojo.form.DeptForm; +import com.datao.admin.pojo.query.DeptQuery; +import com.datao.admin.pojo.vo.DeptVO; + +import java.util.List; + +/** + * 部门业务接口 + * + * @author haoxr + * @date 2021/8/22 + */ +public interface SysDeptService extends IService { + /** + * 部门列表 + * + * @return + */ + List listDepartments(DeptQuery queryParams); + + /** + * 部门树形下拉选项 + * + * @return + */ + List