diff --git a/.gitignore b/.gitignore index 9845fd9ce..9acb04aef 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .idea/* *.iml */target/* -*/*.iml \ No newline at end of file +*/*.iml +/.gradle/ +/application.pid \ No newline at end of file diff --git a/LICENSE b/LICENSE index 91e3c252c..9f53f735f 100644 --- a/LICENSE +++ b/LICENSE @@ -176,7 +176,7 @@ 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 2018 Elune + Copyright 2019-2023 Zheng Jie Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index bcb758ea1..4b8224787 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

el-admin 后台管理系统

+

ELADMIN 后台管理系统

[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/elunez/eladmin/blob/master/LICENSE) @@ -9,75 +9,119 @@
#### 项目简介 -eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统, 权限控制的方式为RBAC,项目支持数据字典与数据权限管理,支持一键生成前后端代码,支持前端菜单动态路由 +一个基于 Spring Boot 2.7.18 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统 -**开发文档** [https://docs.auauz.net/](https://docs.auauz.net) +现已发布基于 mybatis-plus 版本,项目地址:[https://github.com/elunez/eladmin-mp](https://github.com/elunez/eladmin-mp)、[https://gitee.com/elunez/eladmin-mp](https://gitee.com/elunez/eladmin-mp)。 -**体验地址** [https://auauz.net/](https://auauz.net/) +**开发文档:** [https://eladmin.vip](https://eladmin.vip) -**账号密码** ```admin/123456```(默认密码都是123456) +**体验地址:** [https://eladmin.vip/demo](https://eladmin.vip/demo) + +**账号密码:** `admin / 123456` #### 项目源码 | | 后端源码 | 前端源码 | |--- |--- | --- | -| github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-qd | -| 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-qt | +| github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-web | +| 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-web | + +#### VPS推荐 + + + + +使用优惠码: `BWHCGLUKKB`,可获得 6.81% 的折扣 [查看介绍](https://bwhstock.in/) + +#### 主要特性 +- 使用最新技术栈,社区资源丰富。 +- 高效率开发,代码生成器可一键生成前后端代码 +- 支持数据字典,可方便地对一些状态进行管理 +- 支持接口限流,避免恶意请求导致服务层压力过大 +- 支持接口级别的功能权限与数据权限,可自定义操作 +- 自定义权限注解与匿名接口注解,可快速对接口拦截与放行 +- 对一些常用地前端组件封装:表格数据请求、数据字典等 +- 前后端统一异常拦截处理,统一输出异常,避免繁琐的判断 +- 支持在线用户管理与服务器性能监控,支持限制单用户登录 +- 支持运维管理,可方便地对远程服务器的应用进行部署与管理 #### 系统功能 - 用户管理:提供用户的相关配置,新增用户后,默认密码为123456 - 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限 -- 权限管理:权限细化到接口,可以理解成按钮权限 - 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单 - 部门管理:可配置系统组织架构,树形表格展示 - 岗位管理:配置各个部门的职位 -- 字典管理:应广大码友的要求加入字典管理,可维护常用一些固定的数据,如:状态,性别等 -- 操作日志:记录用户操作的日志 -- 异常日志:记录异常日志,方便开发人员定位错误 -- 系统缓存:使用jedis将缓存操作可视化,并提供对redis的基本操作,可根据需求自行扩展 +- 字典管理:可维护常用一些固定的数据,如:状态,性别等 +- 系统日志:记录用户操作日志与异常日志,方便开发人员定位排错 - SQL监控:采用druid 监控数据库访问性能,默认用户名admin,密码123456 - 定时任务:整合Quartz做定时任务,加入任务日志,任务运行情况一目了然 -- 代码生成:高灵活度一键生成前后端代码,减少百分之80左右的工作任务 +- 代码生成:高灵活度生成前后端代码,减少大量重复的工作任务 - 邮件工具:配合富文本,发送html格式的邮件 -- 免费图床:使用sm.ms图床,用作公共图片上传使用 -- 七牛云存储:可同步七牛云存储的数据到系统,无需登录七牛云直接操作云数据 +- 亚马逊S3云存储:支持市面上大多数对象存储,兼容亚马逊S3协议,如七牛云,阿里云等 - 支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试 +- 服务监控:监控服务器的负载情况 +- 运维管理:一键部署你的应用 #### 项目结构 -项目采用分模块开发方式,将通用的配置放在公共模块,```system```模块为系统核心模块也是项目入口模块,```logging``` 模块为系统的日志模块,```tools``` 为第三方工具模块,包含了图床、邮件、七牛云、支付宝,```generator``` 为系统的代码生成模块 +项目采用按功能分模块的开发方式,结构如下 + +- `eladmin-common` 为系统的公共模块,各种工具类,公共配置存在该模块 + +- `eladmin-system` 为系统核心模块也是项目入口模块,也是最终需要打包部署的模块 + +- `eladmin-logging` 为系统的日志模块,其他模块如果需要记录日志需要引入该模块 + +- `eladmin-tools` 为第三方工具模块,包含:邮件、亚马逊S3云存储、本地存储、支付宝 + +- `eladmin-generator` 为系统的代码生成模块,支持生成前后端CRUD代码 + +#### 详细结构 + +``` - eladmin-common 公共模块 + - annotation 为系统自定义注解 + - aspect 自定义注解的切面 + - base 提供了Entity、DTO基类和mapstruct的通用mapper + - config 项目通用配置 + - Web配置跨域与静态资源映射、Swagger配置,文件上传临时路径配置 + - Redis配置,Redission配置, 异步线程池配置 + - 权限拦截配置、AuthorityConfig、Druid 删除广告配置 - exception 项目统一异常的处理 - - mapper mapstruct的通用mapper - - redis redis缓存相关配置 - - swagger2 接口文档配置 - - utils 系统通用工具类 + - utils 系统通用工具类,列举一些常用的工具类 + - BigDecimaUtils 金额计算工具类 + - RequestHolder 请求工具类 + - SecurityUtils 安全工具类 + - StringUtils 字符串工具类 + - SpringBeanHolder Spring Bean工具类 + - RedisUtils Redis工具类 + - EncryptUtils 加密工具类 + - FileUtil 文件工具类 - eladmin-system 系统核心模块(系统启动入口) - - config 配置跨域与静态资源,与数据权限 - - modules 系统相关模块(登录授权、定时任务等) + - sysrunner 程序启动后处理数据 + - modules 系统相关模块(登录授权、系统监控、定时任务、系统模块、运维模块) - eladmin-logging 系统日志模块 - eladmin-tools 系统第三方工具模块 + - email 邮件工具 + - amazon 亚马逊S3云存储工具 + - alipay 支付宝支付工具 + - local-storage 本地存储工具 - eladmin-generator 系统代码生成模块 +``` + +#### 特别鸣谢 + +- 感谢 [PanJiaChen](https://github.com/PanJiaChen/vue-element-admin) 大佬提供的前端模板 -#### 系统预览 - - - - - - - - - - - - - - - - -
+- 感谢 [Moxun](https://github.com/moxun1639) 大佬提供的前端 Curd 通用组件 + +- 感谢 [zhy6599](https://gitee.com/zhy6599) 大佬提供的后端运维管理相关功能 + +- 感谢 [j.yao.SUSE](https://github.com/everhopingandwaiting) 大佬提供的匿名接口与Redis限流等功能 + +- 感谢 [d15801543974](https://github.com/d15801543974) 大佬提供的基于注解的通用查询方式 #### 项目捐赠 -如果你用爽了,可以请作者喝杯咖啡表示支持 ☕️! [Donate](https://docs.auauz.net/#/jz) +项目的发展离不开你的支持,请作者喝杯咖啡吧☕ [Donate](https://eladmin.vip/pages/030101/) + #### 反馈交流 -- QQ交流群:891137268 +- QQ交流群:891137268 、947578238、659622532 \ No newline at end of file diff --git a/eladmin-common/pom.xml b/eladmin-common/pom.xml index 490184911..e1510a7a3 100644 --- a/eladmin-common/pom.xml +++ b/eladmin-common/pom.xml @@ -1,14 +1,24 @@ - + eladmin me.zhengjie - 2.1 + 2.7 4.0.0 + + 5.8.35 + eladmin-common + 公共模块 + + + + cn.hutool + hutool-all + ${hutool.version} + + \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java b/eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java new file mode 100644 index 000000000..7f9576048 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

+ * 用于判断是否过滤数据权限 + * 1、如果没有用到 @OneToOne 这种关联关系,只需要填写 fieldName [参考:DeptQueryCriteria.class] + * 2、如果用到了 @OneToOne ,fieldName 和 joinName 都需要填写,拿UserQueryCriteria.class举例: + * 应该是 @DataPermission(joinName = "dept", fieldName = "id") + *

+ * @author Zheng Jie + * @website ... + * @date 2020-05-07 + **/ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DataPermission { + + /** + * Entity 中的字段名称 + */ + String fieldName() default ""; + + /** + * Entity 中与部门关联的字段名称 + */ + String joinName() default ""; +} diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java b/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java index a00008fca..d73878a10 100644 --- a/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java @@ -1,7 +1,21 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.annotation; import me.zhengjie.aspect.LimitType; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java b/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java index dc394b82f..18bc4c4a4 100644 --- a/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/Query.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.annotation; import java.lang.annotation.ElementType; @@ -13,46 +28,55 @@ @Retention(RetentionPolicy.RUNTIME) public @interface Query { - /** Dong ZhaoYang 2017/8/7 基本对象的属性名 */ + // Dong ZhaoYang 2017/8/7 基本对象的属性名 String propName() default ""; - /** Dong ZhaoYang 2017/8/7 查询方式 */ + // Dong ZhaoYang 2017/8/7 查询方式 Type type() default Type.EQUAL; /** * 连接查询的属性名,如User类中的dept - * @return */ String joinName() default ""; /** * 默认左连接 - * @return */ Join join() default Join.LEFT; /** * 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username") - * @return */ String blurry() default ""; enum Type { - /** jie 2019/6/4 相等 */ + // jie 2019/6/4 相等 EQUAL - /** Dong ZhaoYang 2017/8/7 大于等于 */ + // Dong ZhaoYang 2017/8/7 大于等于 , GREATER_THAN - /** Dong ZhaoYang 2017/8/7 小于等于 */ + // Dong ZhaoYang 2017/8/7 小于等于 , LESS_THAN - /** Dong ZhaoYang 2017/8/7 中模糊查询 */ + // Dong ZhaoYang 2017/8/7 中模糊查询 , INNER_LIKE - /** Dong ZhaoYang 2017/8/7 左模糊查询 */ + // Dong ZhaoYang 2017/8/7 左模糊查询 , LEFT_LIKE - /** Dong ZhaoYang 2017/8/7 右模糊查询 */ + // Dong ZhaoYang 2017/8/7 右模糊查询 , RIGHT_LIKE - /** Dong ZhaoYang 2017/8/7 小于 */ + // Dong ZhaoYang 2017/8/7 小于 , LESS_THAN_NQ - //** jie 2019/6/4 包含 */ + // jie 2019/6/4 包含 , IN + // 不包含 + , NOT_IN + // 不等于 + ,NOT_EQUAL + // between + ,BETWEEN + // 不为空 + ,NOT_NULL + // 为空 + ,IS_NULL, + // Aborn Jiang 2022/06/01, 对应SQL: SELECT * FROM table WHERE FIND_IN_SET('querytag', table.tags); + FIND_IN_SET } /** @@ -60,10 +84,8 @@ enum Type { * 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询 */ enum Join { - /** jie 2019-6-4 13:18:30 左连接 */ - LEFT - /** jie 2019-6-4 13:18:30 右连接 */ - , RIGHT + /** jie 2019-6-4 13:18:30 */ + LEFT, RIGHT, INNER } } diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java new file mode 100644 index 000000000..c66fcb7c1 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousAccess.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.annotation.rest; + +import java.lang.annotation.*; + +/** + * @author jacky + * 用于标记匿名访问方法 + */ +@Inherited +@Documented +@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java new file mode 100644 index 000000000..0617e3837 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +package me.zhengjie.annotation.rest; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code DELETE} requests onto specific handler + * methods. + * 支持匿名访问 DeleteMapping + * + * @author liaojinlong + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousPatchMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.DELETE) +public @interface AnonymousDeleteMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java new file mode 100644 index 000000000..55cbf63b6 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +package me.zhengjie.annotation.rest; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code GET} requests onto specific handler + * methods. + *

+ * 支持匿名访问 GetMapping + * + * @author liaojinlong + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.GET) +public @interface AnonymousGetMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + * + * @since 4.3.5 + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java new file mode 100644 index 000000000..67d941c0e --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +package me.zhengjie.annotation.rest; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code PATCH} requests onto specific handler + * methods. + * * 支持匿名访问 PatchMapping + * + * @author liaojinlong + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousDeleteMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.PATCH) +public @interface AnonymousPatchMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java new file mode 100644 index 000000000..a6fe644db --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +package me.zhengjie.annotation.rest; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code POST} requests onto specific handler + * methods. + * 支持匿名访问 PostMapping + * + * @author liaojinlong + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousDeleteMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.POST) +public @interface AnonymousPostMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java new file mode 100644 index 000000000..bb2d890eb --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +package me.zhengjie.annotation.rest; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code PUT} requests onto specific handler + * methods. + * * 支持匿名访问 PutMapping + * + * @author liaojinlong + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousDeleteMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.PUT) +public @interface AnonymousPutMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java index 555774c7d..5cc3c6dfa 100644 --- a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java +++ b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java @@ -1,5 +1,21 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.aspect; +import cn.hutool.core.util.ObjUtil; import com.google.common.collect.ImmutableList; import me.zhengjie.annotation.Limit; import me.zhengjie.exception.BadRequestException; @@ -12,7 +28,6 @@ import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; @@ -20,13 +35,19 @@ import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; +/** + * @author / + */ @Aspect @Component public class LimitAspect { - @Autowired - private RedisTemplate redisTemplate; + + private final RedisTemplate redisTemplate; private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class); + public LimitAspect(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } @Pointcut("@annotation(me.zhengjie.annotation.Limit)") public void pointcut() { @@ -41,21 +62,19 @@ public Object around(ProceedingJoinPoint joinPoint) throws Throwable { LimitType limitType = limit.limitType(); String key = limit.key(); if (StringUtils.isEmpty(key)) { - switch (limitType) { - case IP: - key = StringUtils.getIP(request); - break; - default: - key = signatureMethod.getName(); + if (limitType == LimitType.IP) { + key = StringUtils.getIp(request); + } else { + key = signatureMethod.getName(); } } - ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_"))); + ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replace("/","_"))); String luaScript = buildLuaScript(); - RedisScript redisScript = new DefaultRedisScript<>(luaScript, Number.class); - Number count = (Number) redisTemplate.execute(redisScript, keys, limit.count(), limit.period()); - if (null != count && count.intValue() <= limit.count()) { + RedisScript redisScript = new DefaultRedisScript<>(luaScript, Long.class); + Long count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period()); + if (ObjUtil.isNotNull(count) && count.intValue() <= limit.count()) { logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name()); return joinPoint.proceed(); } else { diff --git a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java index 6f3830473..42d041510 100644 --- a/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java +++ b/eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java @@ -1,7 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.aspect; +/** + * 限流枚举 + * @author / + */ public enum LimitType { + // 默认 CUSTOMER, -// by ip addr - IP; + // by ip addr + IP } diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java b/eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java new file mode 100644 index 000000000..84dad94d1 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java @@ -0,0 +1,49 @@ +package me.zhengjie.base; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.sql.Timestamp; + +/** + * @author Zheng Jie + * @date 2019年10月24日20:48:53 + */ +@Getter +@Setter +public class BaseDTO implements Serializable { + + @ApiModelProperty(value = "创建人") + private String createBy; + + @ApiModelProperty(value = "修改人") + private String updateBy; + + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private Timestamp createTime; + + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private Timestamp updateTime; + + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + Field[] fields = this.getClass().getDeclaredFields(); + try { + for (Field f : fields) { + f.setAccessible(true); + builder.append(f.getName(), f.get(this)).append("\n"); + } + } catch (Exception e) { + builder.append("toString builder encounter an error"); + } + return builder.toString(); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java b/eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java new file mode 100644 index 000000000..7f5a0d9f5 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.base; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.sql.Timestamp; + +/** + * 通用字段, is_del 根据需求自行添加 + * @author Zheng Jie + * @date 2019年10月24日20:46:32 + */ +@Getter +@Setter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity implements Serializable { + + @CreatedBy + @Column(name = "create_by", updatable = false) + @ApiModelProperty(value = "创建人", hidden = true) + private String createBy; + + @LastModifiedBy + @Column(name = "update_by") + @ApiModelProperty(value = "更新人", hidden = true) + private String updateBy; + + @CreationTimestamp + @Column(name = "create_time", updatable = false) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private Timestamp createTime; + + @UpdateTimestamp + @Column(name = "update_time") + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private Timestamp updateTime; + + /* 分组校验 */ + public @interface Create {} + + /* 分组校验 */ + public @interface Update {} + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + Field[] fields = this.getClass().getDeclaredFields(); + try { + for (Field f : fields) { + f.setAccessible(true); + builder.append(f.getName(), f.get(this)).append("\n"); + } + } catch (Exception e) { + builder.append("toString builder encounter an error"); + } + return builder.toString(); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java b/eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java new file mode 100644 index 000000000..6f69b1382 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.base; + +import java.util.List; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +public interface BaseMapper { + + /** + * DTO转Entity + * @param dto / + * @return / + */ + E toEntity(D dto); + + /** + * Entity转DTO + * @param entity / + * @return / + */ + D toDto(E entity); + + /** + * DTO集合转Entity集合 + * @param dtoList / + * @return / + */ + List toEntity(List dtoList); + + /** + * Entity集合转DTO集合 + * @param entityList / + * @return / + */ + List toDto(List entityList); +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java b/eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java new file mode 100644 index 000000000..ee0764062 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/AsyncExecutor.java @@ -0,0 +1,96 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 创建自定义的线程池 + * @author Zheng Jie + * @description + * @date 2023-06-08 + **/ +@EnableAsync +@Configuration +public class AsyncExecutor implements AsyncConfigurer { + + public static int corePoolSize; + + public static int maxPoolSize; + + public static int keepAliveSeconds; + + public static int queueCapacity; + + @Value("${task.pool.core-pool-size}") + public void setCorePoolSize(int corePoolSize) { + AsyncExecutor.corePoolSize = corePoolSize; + } + + @Value("${task.pool.max-pool-size}") + public void setMaxPoolSize(int maxPoolSize) { + AsyncExecutor.maxPoolSize = maxPoolSize; + } + + @Value("${task.pool.keep-alive-seconds}") + public void setKeepAliveSeconds(int keepAliveSeconds) { + AsyncExecutor.keepAliveSeconds = keepAliveSeconds; + } + + @Value("${task.pool.queue-capacity}") + public void setQueueCapacity(int queueCapacity) { + AsyncExecutor.queueCapacity = queueCapacity; + } + + /** + * 自定义线程池,用法 @Async + * @return Executor + */ + @Override + public Executor getAsyncExecutor() { + // 自定义工厂 + ThreadFactory factory = r -> new Thread(r, "el-async-" + new AtomicInteger(1).getAndIncrement()); + // 自定义线程池 + return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds, + TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueCapacity), factory, + new ThreadPoolExecutor.CallerRunsPolicy()); + } + + /** + * 自定义线程池,用法,注入到类中使用 + * private ThreadPoolTaskExecutor taskExecutor; + * @return ThreadPoolTaskExecutor + */ + @Bean("taskAsync") + public ThreadPoolTaskExecutor taskAsync() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(20); + executor.setKeepAliveSeconds(60); + executor.setThreadNamePrefix("el-task-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java new file mode 100644 index 000000000..d693c58a7 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config; + +import me.zhengjie.utils.SecurityUtils; +import org.springframework.data.domain.AuditorAware; +import org.springframework.stereotype.Component; +import java.util.Optional; + +/** + * @description : 设置审计 + * @author : Dong ZhaoYang + * @date : 2019/10/28 + */ +@Component("auditorAware") +public class AuditorConfig implements AuditorAware { + + /** + * 返回操作员标志信息 + * + * @return / + */ + @Override + public Optional getCurrentAuditor() { + try { + // 这里应根据实际业务情况获取具体信息 + return Optional.of(SecurityUtils.getCurrentUsername()); + }catch (Exception ignored){} + // 用户定时任务,或者无Token调用的情况 + return Optional.of("System"); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java new file mode 100644 index 000000000..b65385913 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/AuthorityConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config; + +import me.zhengjie.utils.SecurityUtils; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Service; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Zheng Jie + */ +@Service(value = "el") +public class AuthorityConfig { + + /** + * 判断接口是否有权限 + * @param permissions 权限 + * @return / + */ + public Boolean check(String ...permissions){ + // 获取当前用户的所有权限 + List elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); + // 判断当前用户的所有权限是否包含接口上定义的权限 + return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java b/eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java new file mode 100644 index 000000000..08ab5cca3 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/CustomP6SpyLogger.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config; + +import cn.hutool.core.util.StrUtil; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; +import lombok.extern.slf4j.Slf4j; + +/** + * @author Zheng Jie + * @description 自定义 p6spy sql输出格式 + * @date 2024-12-26 + **/ +@Slf4j +public class CustomP6SpyLogger implements MessageFormattingStrategy { + + // 重置颜色 + private static final String RESET = "\u001B[0m"; + // 红色 + private static final String RED = "\u001B[31m"; + // 绿色 + private static final String GREEN = "\u001B[32m"; + + /** + * 格式化 sql + * @param connectionId 连接id + * @param now 当前时间 + * @param elapsed 执行时长 + * @param category sql分类 + * @param prepared 预编译sql + * @param sql 执行sql + * @param url 数据库连接url + * @return 格式化后的sql + */ + @Override + public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { + // 去掉换行和多余空格 + if(StrUtil.isNotBlank(sql)){ + sql = sql.replaceAll("\\s+", " ").trim(); + } + + // 格式化并加上颜色 + return String.format( + "%s[Time: %dms]%s - %s%s%s;", + GREEN, elapsed, RESET, RED, sql, RESET + ); + } +} + diff --git a/eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java b/eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java new file mode 100644 index 000000000..399e51bdc --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/RedisConfiguration.java @@ -0,0 +1,191 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONFactory; +import com.alibaba.fastjson2.JSONWriter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.MurmurHash3; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.Cache; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.CacheErrorHandler; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.cache.interceptor.SimpleCacheErrorHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Zheng Jie + * @date 2025-01-13 + */ +@Slf4j +@Configuration +@EnableCaching +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedisConfiguration extends CachingConfigurerSupport { + + // 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + private static final String[] WHITELIST_STR = {"me.zhengjie" }; + + /** + * 设置 redis 数据默认过期时间,默认2小时 + * 设置@cacheable 序列化方式 + */ + @Bean + public RedisCacheConfiguration redisCacheConfiguration(){ + FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); + configuration = configuration.serializeValuesWith(RedisSerializationContext. + SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2)); + return configuration; + } + + @Bean(name = "redisTemplate") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + // 指定 key 和 value 的序列化方案 + FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + // value值的序列化采用fastJsonRedisSerializer + template.setValueSerializer(fastJsonRedisSerializer); + template.setHashValueSerializer(fastJsonRedisSerializer); + // 设置fastJson的序列化白名单 + for (String pack : WHITELIST_STR) { + JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack); + } + // key的序列化采用StringRedisSerializer + template.setKeySerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 缓存管理器 + * @param redisConnectionFactory / + * @return 缓存管理器 + */ + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration config = redisCacheConfiguration(); + return RedisCacheManager.builder(redisConnectionFactory) + .cacheDefaults(config) + .build(); + } + + /** + * 自定义缓存key生成策略 + */ + @Bean + public KeyGenerator keyGenerator() { + return (target, method, params) -> { + Map container = new HashMap<>(8); + Class targetClassClass = target.getClass(); + // 类地址 + container.put("class",targetClassClass.toGenericString()); + // 方法名称 + container.put("methodName",method.getName()); + // 包名称 + container.put("package",targetClassClass.getPackage()); + // 参数列表 + for (int i = 0; i < params.length; i++) { + container.put(String.valueOf(i),params[i]); + } + // 转为JSON字符串 + String jsonString = JSON.toJSONString(container); + // 使用 MurmurHash 生成 hash + return Integer.toHexString(MurmurHash3.hash32x86(jsonString.getBytes())); + }; + } + + @Bean + @SuppressWarnings({"unchecked","all"}) + public CacheErrorHandler errorHandler() { + return new SimpleCacheErrorHandler() { + @Override + public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { + // 处理缓存读取错误 + log.error("Cache Get Error: {}",exception.getMessage()); + } + @Override + public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { + // 处理缓存写入错误 + log.error("Cache Put Error: {}",exception.getMessage()); + } + @Override + public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { + // 处理缓存删除错误 + log.error("Cache Evict Error: {}",exception.getMessage()); + } + @Override + public void handleCacheClearError(RuntimeException exception, Cache cache) { + // 处理缓存清除错误 + log.error("Cache Clear Error: {}",exception.getMessage()); + } + }; + } + + /** + * Value 序列化 + * + * @param + * @author / + */ + static class FastJsonRedisSerializer implements RedisSerializer { + + private final Class clazz; + + FastJsonRedisSerializer(Class clazz) { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length == 0) { + return null; + } + String str = new String(bytes, StandardCharsets.UTF_8); + return JSON.parseObject(str, clazz); + } + + } +} \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java b/eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java new file mode 100644 index 000000000..d7e0c70e0 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/RedissonConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedissonConfiguration { + + @Value("${spring.redis.host}") + private String redisHost; + + @Value("${spring.redis.port}") + private int redisPort; + + @Value("${spring.redis.database}") + private int redisDatabase; + + @Value("${spring.redis.password:}") + private String redisPassword; + + @Value("${spring.redis.timeout:5000}") + private int timeout; + + @Value("${spring.redis.lettuce.pool.max-active:64}") + private int connectionPoolSize; + + @Value("${spring.redis.lettuce.pool.min-idle:16}") + private int connectionMinimumIdleSize; + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redisHost + ":" + redisPort) + .setDatabase(redisDatabase) + .setTimeout(timeout) + .setConnectionPoolSize(connectionPoolSize) + .setConnectionMinimumIdleSize(connectionMinimumIdleSize); + if(StrUtil.isNotBlank(redisPassword)){ + config.useSingleServer().setPassword(redisPassword); + } + return Redisson.create(config); + } +} + + diff --git a/eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java new file mode 100644 index 000000000..8757ead1a --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/RemoveDruidAdConfig.java @@ -0,0 +1,78 @@ +package me.zhengjie.config; + +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Zheng Jie + * @description + * @date 2025-01-11 + **/ +@Configuration +@SuppressWarnings({"unchecked","all"}) +@ConditionalOnWebApplication +@AutoConfigureAfter(DruidDataSourceAutoConfigure.class) +@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", + havingValue = "true", matchIfMissing = true) +public class RemoveDruidAdConfig { + + /** + * 方法名: removeDruidAdFilterRegistrationBean + * 方法描述 除去页面底部的广告 + * @param properties com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties + * @return org.springframework.boot.web.servlet.FilterRegistrationBean + */ + @Bean + public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) { + + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + + final String filePath = "support/http/resources/js/common.js"; + + //创建filter进行过滤 + Filter filter = new Filter() { + @Override + public void init(FilterConfig filterConfig) throws ServletException {} + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + if (httpRequest.getRequestURI().endsWith("js/common.js")) { + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered by.*?shrek.wang", ""); + httpResponse.setContentType("application/javascript"); + httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.getWriter().write(text); + } else { + chain.doFilter(request, response); + } + } + @Override + public void destroy() {} + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java b/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java new file mode 100644 index 000000000..6b7d2b6f9 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/properties/FileProperties.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.properties; + +import lombok.Data; +import me.zhengjie.utils.ElConstant; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author Zheng Jie + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "file") +public class FileProperties { + + /** 文件大小限制 */ + private Long maxSize; + + /** 头像大小限制 */ + private Long avatarMaxSize; + + private ElPath mac; + + private ElPath linux; + + private ElPath windows; + + public ElPath getPath(){ + String os = System.getProperty("os.name"); + if(os.toLowerCase().startsWith(ElConstant.WIN)) { + return windows; + } else if(os.toLowerCase().startsWith(ElConstant.MAC)){ + return mac; + } + return linux; + } + + @Data + public static class ElPath{ + + private String path; + + private String avatar; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.java b/eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.java new file mode 100644 index 000000000..9eb327c51 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/properties/RsaProperties.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.properties; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author Zheng Jie + * @website https://eladmin.vip + * @description + * @date 2020-05-18 + **/ +@Data +@Component +public class RsaProperties { + + public static String privateKey; + + @Value("${rsa.private_key}") + public void setPrivateKey(String privateKey) { + RsaProperties.privateKey = privateKey; + } +} \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java new file mode 100644 index 000000000..72c54bbe4 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/ConfigurerAdapter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.webConfig; + +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.support.config.FastJsonConfig; +import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter; +import me.zhengjie.config.properties.FileProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * WebMvcConfigurer + * + * @author Zheng Jie + * @date 2018-11-30 + */ +@Configuration +@EnableWebMvc +public class ConfigurerAdapter implements WebMvcConfigurer { + + /** 文件配置 */ + private final FileProperties properties; + + public ConfigurerAdapter(FileProperties properties) { + this.properties = properties; + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + FileProperties.ElPath path = properties.getPath(); + String avatarUtl = "file:" + path.getAvatar().replace("\\","/"); + String pathUtl = "file:" + path.getPath().replace("\\","/"); + registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0); + registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0); + registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); + } + + @Override + public void configureMessageConverters(List> converters) { + // 配置 FastJsonHttpMessageConverter + FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); + List supportMediaTypeList = new ArrayList<>(); + supportMediaTypeList.add(MediaType.APPLICATION_JSON); + FastJsonConfig config = new FastJsonConfig(); + config.setDateFormat("yyyy-MM-dd HH:mm:ss"); + // 开启引用检测 + config.setWriterFeatures(JSONWriter.Feature.ReferenceDetection); + converter.setFastJsonConfig(config); + converter.setSupportedMediaTypes(supportMediaTypeList); + converter.setDefaultCharset(StandardCharsets.UTF_8); + converters.add(converter); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java new file mode 100644 index 000000000..acd59601c --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/MultipartConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.webConfig; + +import org.springframework.boot.web.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import javax.servlet.MultipartConfigElement; +import java.io.File; + +/** + * @date 2018-12-28 + * @author ... + */ +@Configuration +public class MultipartConfig { + + /** + * 文件上传临时路径 + */ + @Bean + MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + String location = System.getProperty("user.home") + "/.eladmin/file/tmp"; + File tmpFile = new File(location); + if (!tmpFile.exists()) { + if (!tmpFile.mkdirs()) { + System.out.println("create was not successful."); + } + } + factory.setLocation(location); + return factory.createMultipartConfig(); + } +} \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java new file mode 100644 index 000000000..075439ff2 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/QueryCustomizer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2023 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.webConfig; + +import org.apache.catalina.connector.Connector; +import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; +import org.springframework.context.annotation.Configuration; + +/** + * @author bearBoy80 + */ +@Configuration(proxyBeanMethods = false) +public class QueryCustomizer implements TomcatConnectorCustomizer { + @Override + public void customize(Connector connector) { + connector.setProperty("relaxedQueryChars", "[]{}"); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java new file mode 100644 index 000000000..2b73c897d --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerConfig.java @@ -0,0 +1,169 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.webConfig; + +import lombok.RequiredArgsConstructor; +import me.zhengjie.utils.AnonTagUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; +import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * api页面 /doc.html + * @author Zheng Jie + * @date 2018-11-23 + */ +@Configuration +@EnableSwagger2 +@RequiredArgsConstructor +public class SwaggerConfig { + + @Value("${jwt.header}") + private String tokenHeader; + + @Value("${swagger.enabled}") + private Boolean enabled; + + @Value("${server.servlet.context-path:}") + private String apiPath; + + private final ApplicationContext applicationContext; + + @Bean + @SuppressWarnings({"unchecked","all"}) + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .enable(enabled) + .pathMapping("/") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.regex("^(?!/error).*")) + .paths(PathSelectors.any()) + .build() + //添加登陆认证 + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .description("一个简单且易上手的 Spring boot 后台管理框架") + .title("ELADMIN 接口文档") + .version("2.7") + .build(); + } + + private List securitySchemes() { + //设置请求头信息 + List securitySchemes = new ArrayList<>(); + ApiKey apiKey = new ApiKey(tokenHeader, tokenHeader, "header"); + securitySchemes.add(apiKey); + return securitySchemes; + } + + private List securityContexts() { + //设置需要登录认证的路径 + List securityContexts = new ArrayList<>(); + securityContexts.add(getContextByPath()); + return securityContexts; + } + + private SecurityContext getContextByPath() { + Set urls = AnonTagUtils.getAllAnonymousUrl(applicationContext); + urls = urls.stream().filter(url -> !url.equals("/")).collect(Collectors.toSet()); + String regExp = "^(?!" + apiPath + String.join("|" + apiPath, urls) + ").*$"; + return SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o->o.requestMappingPattern() + // 排除不需要认证的接口 + .matches(regExp)) + .build(); + } + + private List defaultAuth() { + List securityReferences = new ArrayList<>(); + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + securityReferences.add(new SecurityReference(tokenHeader, authorizationScopes)); + return securityReferences; + } + + /** + * 解决Springfox与SpringBoot集成后,WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider冲突问题 + * @return / + */ + @Bean + @SuppressWarnings({"all"}) + public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private void customizeSpringfoxHandlerMappings(List mappings) { + List filteredMappings = mappings.stream() + .filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(filteredMappings); + } + + private List getHandlerMappings(Object bean) { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + if (field != null) { + field.setAccessible(true); + try { + return (List) field.get(bean); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Failed to access handlerMappings field", e); + } + } + return Collections.emptyList(); + } + }; + } +} + diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java new file mode 100644 index 000000000..2fba84077 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/SwaggerDataConfig.java @@ -0,0 +1,52 @@ +package me.zhengjie.config.webConfig; + +import cn.hutool.core.collection.CollUtil; +import com.fasterxml.classmate.TypeResolver; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.data.domain.Pageable; +import springfox.documentation.schema.AlternateTypeRule; +import springfox.documentation.schema.AlternateTypeRuleConvention; + +import java.util.List; + +import static springfox.documentation.schema.AlternateTypeRules.newRule; + +/** + * 将Pageable转换展示在swagger中 + */ +@Configuration +public class SwaggerDataConfig { + + @Bean + public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) { + return new AlternateTypeRuleConvention() { + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public List rules() { + return CollUtil.newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class))); + } + }; + } + + @ApiModel + @Data + private static class Page { + @ApiModelProperty("页码 (0..N)") + private Integer page; + + @ApiModelProperty("每页显示的数目") + private Integer size; + + @ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc") + private List sort; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java new file mode 100644 index 000000000..4751bb1cd --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/config/webConfig/WebSocketConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.config.webConfig; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * @author ZhangHouYing + * @date 2019-08-24 15:44 + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java b/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java index adef657a5..1d6297b91 100644 --- a/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java +++ b/eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java @@ -1,8 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.exception; import lombok.Getter; import org.springframework.http.HttpStatus; - import static org.springframework.http.HttpStatus.BAD_REQUEST; /** diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java b/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java index 23417a726..32d0e38f7 100644 --- a/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java +++ b/eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java @@ -1,34 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.exception; import org.springframework.util.StringUtils; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.IntStream; - /** * @author Zheng Jie * @date 2018-11-23 */ public class EntityExistException extends RuntimeException { - public EntityExistException(Class clazz, Object... saveBodyParamsMap) { - super(EntityExistException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, saveBodyParamsMap))); - } - - private static String generateMessage(String entity, Map saveBodyParams) { - return StringUtils.capitalize(entity) + - " 已存在 " + - saveBodyParams; + public EntityExistException(Class clazz, String field, String val) { + super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val)); } - private static Map toMap( - Class keyType, Class valueType, Object... entries) { - if (entries.length % 2 == 1) - throw new IllegalArgumentException("Invalid entries"); - return IntStream.range(0, entries.length / 2).map(i -> i * 2) - .collect(HashMap::new, - (m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])), - Map::putAll); + private static String generateMessage(String entity, String field, String val) { + return StringUtils.capitalize(entity) + + " with " + field + " "+ val + " existed"; } } \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java b/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java index 31e1cbdfb..4d9f4d964 100644 --- a/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java +++ b/eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java @@ -1,35 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.exception; import org.springframework.util.StringUtils; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.IntStream; - /** * @author Zheng Jie * @date 2018-11-23 */ public class EntityNotFoundException extends RuntimeException { - public EntityNotFoundException(Class clazz, Object... searchParamsMap) { - super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, searchParamsMap))); + public EntityNotFoundException(Class clazz, String field, String val) { + super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val)); } - private static String generateMessage(String entity, Map searchParams) { - return StringUtils.capitalize(entity) + - " 不存在 " + - searchParams; + private static String generateMessage(String entity, String field, String val) { + return StringUtils.capitalize(entity) + + " with " + field + " "+ val + " does not exist"; } - - private static Map toMap( - Class keyType, Class valueType, Object... entries) { - if (entries.length % 2 == 1) - throw new IllegalArgumentException("Invalid entries"); - return IntStream.range(0, entries.length / 2).map(i -> i * 2) - .collect(HashMap::new, - (m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])), - Map::putAll); - } - } \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java b/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java index db43891e7..aa377c4f3 100644 --- a/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java +++ b/eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java @@ -1,30 +1,48 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.exception.handler; -import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; -import java.time.LocalDateTime; - /** * @author Zheng Jie * @date 2018-11-23 */ @Data -class ApiError { +public class ApiError { - private Integer status; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime timestamp; + private Integer status = 400; + private Long timestamp; private String message; private ApiError() { - timestamp = LocalDateTime.now(); + timestamp = System.currentTimeMillis(); + } + + public static ApiError error(String message){ + ApiError apiError = new ApiError(); + apiError.setMessage(message); + return apiError; } - public ApiError(Integer status,String message) { - this(); - this.status = status; - this.message = message; + public static ApiError error(Integer status, String message){ + ApiError apiError = new ApiError(); + apiError.setStatus(status); + apiError.setMessage(message); + return apiError; } } diff --git a/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java b/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java index 5e58c7844..4e4e7165a 100644 --- a/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java +++ b/eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.exception.handler; import lombok.extern.slf4j.Slf4j; @@ -7,7 +22,9 @@ import me.zhengjie.utils.ThrowableUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -23,91 +40,74 @@ public class GlobalExceptionHandler { /** * 处理所有不可知的异常 - * @param e - * @return */ @ExceptionHandler(Throwable.class) - public ResponseEntity handleException(Throwable e){ + public ResponseEntity handleException(Throwable e){ // 打印堆栈信息 log.error(ThrowableUtil.getStackTrace(e)); - ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage()); - return buildResponseEntity(apiError); + return buildResponseEntity(ApiError.error(e.getMessage())); } /** - * 处理 接口无权访问异常AccessDeniedException - * @param e - * @return + * BadCredentialsException */ - @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException(AccessDeniedException e){ + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity badCredentialsException(BadCredentialsException e){ // 打印堆栈信息 - log.error(ThrowableUtil.getStackTrace(e)); - ApiError apiError = new ApiError(FORBIDDEN.value(),e.getMessage()); - return buildResponseEntity(apiError); + String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage(); + log.error(message); + return buildResponseEntity(ApiError.error(message)); } /** * 处理自定义异常 - * @param e - * @return */ @ExceptionHandler(value = BadRequestException.class) public ResponseEntity badRequestException(BadRequestException e) { // 打印堆栈信息 log.error(ThrowableUtil.getStackTrace(e)); - ApiError apiError = new ApiError(e.getStatus(),e.getMessage()); - return buildResponseEntity(apiError); + return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage())); } /** * 处理 EntityExist - * @param e - * @return */ @ExceptionHandler(value = EntityExistException.class) public ResponseEntity entityExistException(EntityExistException e) { // 打印堆栈信息 log.error(ThrowableUtil.getStackTrace(e)); - ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage()); - return buildResponseEntity(apiError); + return buildResponseEntity(ApiError.error(e.getMessage())); } /** * 处理 EntityNotFound - * @param e - * @return */ @ExceptionHandler(value = EntityNotFoundException.class) public ResponseEntity entityNotFoundException(EntityNotFoundException e) { // 打印堆栈信息 log.error(ThrowableUtil.getStackTrace(e)); - ApiError apiError = new ApiError(NOT_FOUND.value(),e.getMessage()); - return buildResponseEntity(apiError); + return buildResponseEntity(ApiError.error(NOT_FOUND.value(),e.getMessage())); } /** * 处理所有接口数据验证异常 - * @param e - * @returns */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e){ // 打印堆栈信息 log.error(ThrowableUtil.getStackTrace(e)); - String[] str = e.getBindingResult().getAllErrors().get(0).getCodes()[1].split("\\."); - StringBuffer msg = new StringBuffer(str[1]+":"); - msg.append(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); - ApiError apiError = new ApiError(BAD_REQUEST.value(),msg.toString()); - return buildResponseEntity(apiError); + ObjectError objectError = e.getBindingResult().getAllErrors().get(0); + String message = objectError.getDefaultMessage(); + if (objectError instanceof FieldError) { + message = ((FieldError) objectError).getField() + ": " + message; + } + return buildResponseEntity(ApiError.error(message)); } /** * 统一返回 - * @param apiError - * @return */ private ResponseEntity buildResponseEntity(ApiError apiError) { - return new ResponseEntity(apiError, HttpStatus.valueOf(apiError.getStatus())); + return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus())); } } diff --git a/eladmin-common/src/main/java/me/zhengjie/mapper/EntityMapper.java b/eladmin-common/src/main/java/me/zhengjie/mapper/EntityMapper.java deleted file mode 100644 index 98bc55db2..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/mapper/EntityMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.zhengjie.mapper; - -import java.util.List; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -public interface EntityMapper { - - /** - * DTO转Entity - * @param dto - * @return - */ - E toEntity(D dto); - - /** - * Entity转DTO - * @param entity - * @return - */ - D toDto(E entity); - - /** - * DTO集合转Entity集合 - * @param dtoList - * @return - */ - List toEntity(List dtoList); - - /** - * Entity集合转DTO集合 - * @param entityList - * @return - */ - List toDto(List entityList); -} diff --git a/eladmin-common/src/main/java/me/zhengjie/redis/FastJsonRedisSerializer.java b/eladmin-common/src/main/java/me/zhengjie/redis/FastJsonRedisSerializer.java deleted file mode 100644 index d2e224c87..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/redis/FastJsonRedisSerializer.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.zhengjie.redis; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.serializer.SerializerFeature; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.SerializationException; - -import java.nio.charset.Charset; - -/** - * Value 序列化 - * - * @author / - * @param - */ -public class FastJsonRedisSerializer implements RedisSerializer { - - public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); - - private Class clazz; - - public FastJsonRedisSerializer(Class clazz) { - super(); - this.clazz = clazz; - } - - @Override - public byte[] serialize(T t) throws SerializationException { - if (t == null) { - return new byte[0]; - } - return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); - } - - @Override - public T deserialize(byte[] bytes) throws SerializationException { - if (bytes == null || bytes.length <= 0) { - return null; - } - String str = new String(bytes, DEFAULT_CHARSET); - return (T) JSON.parseObject(str, clazz); - } - -} diff --git a/eladmin-common/src/main/java/me/zhengjie/redis/RedisConfig.java b/eladmin-common/src/main/java/me/zhengjie/redis/RedisConfig.java deleted file mode 100644 index a94861650..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/redis/RedisConfig.java +++ /dev/null @@ -1,93 +0,0 @@ -package me.zhengjie.redis; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.parser.ParserConfig; -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.utils.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cache.annotation.CachingConfigurerSupport; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.interceptor.KeyGenerator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import java.time.Duration; - -/** - * @author Zheng Jie - * @date 2018-11-24 - */ -@Slf4j -@Configuration -@EnableCaching -// 自动配置 -@ConditionalOnClass(RedisOperations.class) -@EnableConfigurationProperties(RedisProperties.class) -public class RedisConfig extends CachingConfigurerSupport { - - /** - * 设置 redis 数据默认过期时间,默认1天 - * 设置@cacheable 序列化方式 - * @return - */ - @Bean - public RedisCacheConfiguration redisCacheConfiguration(){ - FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); - RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); - configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(1)); - return configuration; - } - - @Bean(name = "redisTemplate") - @ConditionalOnMissingBean(name = "redisTemplate") - public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - //序列化 - FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); - // value值的序列化采用fastJsonRedisSerializer - template.setValueSerializer(fastJsonRedisSerializer); - template.setHashValueSerializer(fastJsonRedisSerializer); - - // 全局开启AutoType,不建议使用 - // ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - // 建议使用这种方式,小范围指定白名单 - ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain"); - ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.service.dto"); - ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.domain"); - ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.quartz.domain"); - ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.monitor.domain"); - ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.security.security"); - // key的序列化采用StringRedisSerializer - template.setKeySerializer(new StringRedisSerializer()); - template.setHashKeySerializer(new StringRedisSerializer()); - template.setConnectionFactory(redisConnectionFactory); - return template; - } - - /** - * 自定义缓存key生成策略 - * 使用方法 @Cacheable(keyGenerator="keyGenerator") - * @return - */ - @Bean - @Override - public KeyGenerator keyGenerator() { - return (target, method, params) -> { - StringBuilder sb = new StringBuilder(); - sb.append(target.getClass().getName()); - sb.append(method.getName()); - for (Object obj : params) { - sb.append(JSON.toJSONString(obj).hashCode()); - } - return sb.toString(); - }; - } -} diff --git a/eladmin-common/src/main/java/me/zhengjie/redis/StringRedisSerializer.java b/eladmin-common/src/main/java/me/zhengjie/redis/StringRedisSerializer.java deleted file mode 100644 index e0f54f10d..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/redis/StringRedisSerializer.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.zhengjie.redis; - -import cn.hutool.core.lang.Assert; -import com.alibaba.fastjson.JSON; -import org.springframework.data.redis.serializer.RedisSerializer; - -import java.nio.charset.Charset; - -/** - * 重写序列化器 - * - * @author / - */ -public class StringRedisSerializer implements RedisSerializer { - - private final Charset charset; - - private final String target = "\""; - - private final String replacement = ""; - - public StringRedisSerializer() { - this(Charset.forName("UTF8")); - } - - public StringRedisSerializer(Charset charset) { - Assert.notNull(charset, "Charset must not be null!"); - this.charset = charset; - } - - @Override - public String deserialize(byte[] bytes) { - return (bytes == null ? null : new String(bytes, charset)); - } - - @Override - public byte[] serialize(Object object) { - String string = JSON.toJSONString(object); - if (string == null) { - return null; - } - string = string.replace(target, replacement); - return string.getBytes(charset); - } -} \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerConfig.java b/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerConfig.java deleted file mode 100644 index 207873cd6..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerConfig.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.zhengjie.swagger2; - -import com.google.common.base.Predicates; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.ParameterBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.schema.ModelRef; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.Parameter; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; -import java.util.ArrayList; -import java.util.List; - -/** - * api页面 /swagger-ui.html - * @author Zheng Jie - * @date 2018-11-23 - */ - -@Configuration -@EnableSwagger2 -public class SwaggerConfig { - - @Value("${jwt.header}") - private String tokenHeader; - - @Value("${swagger.enabled}") - private Boolean enabled; - - @Bean - public Docket createRestApi() { - ParameterBuilder ticketPar = new ParameterBuilder(); - List pars = new ArrayList(); - ticketPar.name(tokenHeader).description("token") - .modelRef(new ModelRef("string")) - .parameterType("header") - .defaultValue("Bearer ") - .required(true) - .build(); - pars.add(ticketPar.build()); - return new Docket(DocumentationType.SWAGGER_2) - .enable(enabled) - .apiInfo(apiInfo()) - .select() - .paths(Predicates.not(PathSelectors.regex("/error.*"))) - .build() - .globalOperationParameters(pars); - } - - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - .title("eladmin 接口文档") - .version("2.1") - .build(); - } - -} diff --git a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerDataConfig.java b/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerDataConfig.java deleted file mode 100644 index 35f0edf1a..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/swagger2/SwaggerDataConfig.java +++ /dev/null @@ -1 +0,0 @@ -package me.zhengjie.swagger2; import com.fasterxml.classmate.TypeResolver; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.data.domain.Pageable; import springfox.documentation.schema.AlternateTypeRule; import springfox.documentation.schema.AlternateTypeRuleConvention; import java.util.List; import static com.google.common.collect.Lists.newArrayList; import static springfox.documentation.schema.AlternateTypeRules.newRule; /** * 将Pageable转换展示在swagger中 */ @Configuration public class SwaggerDataConfig { @Bean public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) { return new AlternateTypeRuleConvention() { @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } @Override public List rules() { return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class))); } }; } @ApiModel static class Page { @ApiModelProperty("页码 (0..N)") private Integer page; @ApiModelProperty("每页显示的数目") private Integer size; @ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc") private List sort; public Integer getPage() { return page; } public void setPage(Integer page) { this.page = page; } public Integer getSize() { return size; } public void setSize(Integer size) { this.size = size; } public List getSort() { return sort; } public void setSort(List sort) { this.sort = sort; } } } \ No newline at end of file diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java new file mode 100644 index 000000000..9a2832ded --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/AnonTagUtils.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ +package me.zhengjie.utils; + +import me.zhengjie.annotation.rest.AnonymousAccess; +import me.zhengjie.utils.enums.RequestMethodEnum; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.*; + +/** + * @author Zheng Jie + * @description 匿名标记工具 + * @date 2025-01-13 + **/ +public class AnonTagUtils { + + /** + * 获取匿名标记的URL + * @param applicationContext / + * @return / + */ + public static Map> getAnonymousUrl(ApplicationContext applicationContext){ + RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping"); + Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods(); + Map> anonymousUrls = new HashMap<>(8); + // 获取匿名标记 + Set get = new HashSet<>(); + Set post = new HashSet<>(); + Set put = new HashSet<>(); + Set patch = new HashSet<>(); + Set delete = new HashSet<>(); + Set all = new HashSet<>(); + for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { + HandlerMethod handlerMethod = infoEntry.getValue(); + AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class); + if (null != anonymousAccess) { + List requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods()); + RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name()); + if (infoEntry.getKey().getPatternsCondition()!=null) { + switch (Objects.requireNonNull(request)) { + case GET: + get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + break; + case POST: + post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + break; + case PUT: + put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + break; + case PATCH: + patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + break; + case DELETE: + delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + break; + default: + all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + break; + } + } + } + } + anonymousUrls.put(RequestMethodEnum.GET.getType(), get); + anonymousUrls.put(RequestMethodEnum.POST.getType(), post); + anonymousUrls.put(RequestMethodEnum.PUT.getType(), put); + anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch); + anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete); + anonymousUrls.put(RequestMethodEnum.ALL.getType(), all); + return anonymousUrls; + } + + /** + * 获取所有匿名标记的URL + * @param applicationContext / + * @return / + */ + public static Set getAllAnonymousUrl(ApplicationContext applicationContext){ + Set allUrl = new HashSet<>(); + Map> anonymousUrls = getAnonymousUrl(applicationContext); + for (String key : anonymousUrls.keySet()) { + allUrl.addAll(anonymousUrls.get(key)); + } + return allUrl; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java new file mode 100644 index 000000000..ff3d81fa2 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/BigDecimalUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ +package me.zhengjie.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * @author Zheng Jie + * @description 计算类 + * @date 2024-12-27 + **/ +public class BigDecimalUtils { + + /** + * 将对象转换为 BigDecimal + * @param obj 输入对象 + * @return 转换后的 BigDecimal + */ + private static BigDecimal toBigDecimal(Object obj) { + if (obj instanceof BigDecimal) { + return (BigDecimal) obj; + } else if (obj instanceof Long) { + return BigDecimal.valueOf((Long) obj); + } else if (obj instanceof Integer) { + return BigDecimal.valueOf((Integer) obj); + } else if (obj instanceof Double) { + return new BigDecimal(String.valueOf(obj)); + } else { + throw new IllegalArgumentException("Unsupported type"); + } + } + + /** + * 加法 + * @param a 加数 + * @param b 加数 + * @return 两个加数的和,保留两位小数 + */ + public static BigDecimal add(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.add(bdB).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 减法 + * @param a 被减数 + * @param b 减数 + * @return 两数的差,保留两位小数 + */ + public static BigDecimal subtract(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.subtract(bdB).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 乘法 + * @param a 乘数 + * @param b 乘数 + * @return 两个乘数的积,保留两位小数 + */ + public static BigDecimal multiply(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.multiply(bdB).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 除法 + * @param a 被除数 + * @param b 除数 + * @return 两数的商,保留两位小数 + */ + public static BigDecimal divide(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.divide(bdB, 2, RoundingMode.HALF_UP); + } + + /** + * 除法 + * @param a 被除数 + * @param b 除数 + * @param scale 保留小数位数 + * @return 两数的商,保留两位小数 + */ + public static BigDecimal divide(Object a, Object b, int scale) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.divide(bdB, scale, RoundingMode.HALF_UP); + } + + /** + * 分转元 + * @param obj 分的金额 + * @return 转换后的元,保留两位小数 + */ + public static BigDecimal centsToYuan(Object obj) { + BigDecimal cents = toBigDecimal(obj); + return cents.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); + } + + /** + * 元转分 + * @param obj 元的金额 + * @return 转换后的分 + */ + public static Long yuanToCents(Object obj) { + BigDecimal yuan = toBigDecimal(obj); + return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue(); + } + + public static void main(String[] args) { + BigDecimal num1 = new BigDecimal("10.123"); + BigDecimal num2 = new BigDecimal("2.456"); + + System.out.println("加法结果: " + add(num1, num2)); + System.out.println("减法结果: " + subtract(num1, num2)); + System.out.println("乘法结果: " + multiply(num1, num2)); + System.out.println("除法结果: " + divide(num1, num2)); + + Long cents = 12345L; + System.out.println("分转元结果: " + centsToYuan(cents)); + + BigDecimal yuan = new BigDecimal("123.45"); + System.out.println("元转分结果: " + yuanToCents(yuan)); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java b/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java new file mode 100644 index 000000000..e8fb08609 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ +package me.zhengjie.utils; + +/** + * @author liaojinlong + * @date 2020/6/11 15:49 + * @description 关于缓存的Key集合 + */ +public interface CacheKey { + + /** + * 用户 + */ + String USER_ID = "user::id:"; + + /** + * 数据 + */ + String DATA_USER = "data::user:"; + + /** + * 菜单 + */ + String MENU_ID = "menu::id:"; + String MENU_USER = "menu::user:"; + + /** + * 角色授权 + */ + String ROLE_AUTH = "role::auth:"; + String ROLE_USER = "role::user:"; + + /** + * 角色信息 + */ + String ROLE_ID = "role::id:"; + + /** + * 部门 + */ + String DEPT_ID = "dept::id:"; + + /** + * 岗位 + */ + String JOB_ID = "job::id:"; + + /** + * 数据字典 + */ + String DICT_NAME = "dict::name:"; +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java new file mode 100644 index 000000000..b095ec63b --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils; + +import java.io.Closeable; + +/** + * @author Zheng Jie + * @website https://eladmin.vip + * @description 用于关闭各种连接,缺啥补啥 + * @date 2021-03-05 + **/ +public class CloseUtil { + + public static void close(Closeable closeable) { + if (null != closeable) { + try { + closeable.close(); + } catch (Exception e) { + // 静默关闭 + } + } + } + + public static void close(AutoCloseable closeable) { + if (null != closeable) { + try { + closeable.close(); + } catch (Exception e) { + // 静默关闭 + } + } + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java new file mode 100644 index 000000000..0b0bf6370 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java @@ -0,0 +1,160 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +package me.zhengjie.utils; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * @author: liaojinlong + * @date: 2020/6/11 16:28 + * @apiNote: JDK 8 新日期类 格式化与字符串转换 工具类 + */ +public class DateUtil { + + public static final DateTimeFormatter DFY_MD_HMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + public static final DateTimeFormatter DFY_MD = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + /** + * LocalDateTime 转时间戳 + * + * @param localDateTime / + * @return / + */ + public static Long getTimeStamp(LocalDateTime localDateTime) { + return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond(); + } + + /** + * 时间戳转LocalDateTime + * + * @param timeStamp / + * @return / + */ + public static LocalDateTime fromTimeStamp(Long timeStamp) { + return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset()); + } + + /** + * LocalDateTime 转 Date + * Jdk8 后 不推荐使用 {@link Date} Date + * + * @param localDateTime / + * @return / + */ + public static Date toDate(LocalDateTime localDateTime) { + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * LocalDate 转 Date + * Jdk8 后 不推荐使用 {@link Date} Date + * + * @param localDate / + * @return / + */ + public static Date toDate(LocalDate localDate) { + return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault()))); + } + + + /** + * Date转 LocalDateTime + * Jdk8 后 不推荐使用 {@link Date} Date + * + * @param date / + * @return / + */ + public static LocalDateTime toLocalDateTime(Date date) { + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + } + + /** + * 日期 格式化 + * + * @param localDateTime / + * @param patten / + * @return / + */ + public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) { + DateTimeFormatter df = DateTimeFormatter.ofPattern(patten); + return df.format(localDateTime); + } + + /** + * 日期 格式化 + * + * @param localDateTime / + * @param df / + * @return / + */ + public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) { + return df.format(localDateTime); + } + + /** + * 日期格式化 yyyy-MM-dd HH:mm:ss + * + * @param localDateTime / + * @return / + */ + public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) { + return DFY_MD_HMS.format(localDateTime); + } + + /** + * 日期格式化 yyyy-MM-dd + * + * @param localDateTime / + * @return / + */ + public String localDateTimeFormatyMd(LocalDateTime localDateTime) { + return DFY_MD.format(localDateTime); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd + * + * @param localDateTime / + * @return / + */ + public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.from(dateTimeFormatter.parse(localDateTime)); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd + * + * @param localDateTime / + * @return / + */ + public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) { + return LocalDateTime.from(dateTimeFormatter.parse(localDateTime)); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd HH:mm:ss + * + * @param localDateTime / + * @return / + */ + public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) { + return LocalDateTime.from(DFY_MD_HMS.parse(localDateTime)); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java b/eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java deleted file mode 100644 index 89bfeaae9..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.zhengjie.utils; - -/** - * 常用静态常量 - * @author Zheng Jie - * @date 2018-12-26 - */ -public class ElAdminConstant { - - public static final String RESET_PASS = "重置密码"; - - public static final String RESET_MAIL = "重置邮箱"; - - /** - * 用于IP定位转换 - */ - public static final String REGION = "内网IP|内网IP"; - - /** - * 常用接口 - */ - public static class Url{ - public static final String SM_MS_URL = "https://sm.ms/api/upload"; - } -} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.java b/eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.java new file mode 100644 index 000000000..fa58845e0 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/ElConstant.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils; + +/** + * 常用静态常量 + * + * @author Zheng Jie + * @date 2018-12-26 + */ +public class ElConstant { + /** + * win 系统 + */ + public static final String WIN = "win"; + + /** + * mac 系统 + */ + public static final String MAC = "mac"; +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java index 5bddf3a51..83bf918f3 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java @@ -1,11 +1,26 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; -import org.springframework.util.DigestUtils; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.IvParameterSpec; +import java.nio.charset.StandardCharsets; /** * 加密 @@ -14,36 +29,50 @@ */ public class EncryptUtils { - private static String strKey = "Passw0rd", strParam = "Passw0rd"; + private static final String STR_PARAM = "Passw0rd"; + private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8)); + + private static DESKeySpec getDesKeySpec(String source) throws Exception { + if (source == null || source.isEmpty()) { + return null; + } + String strKey = "Passw0rd"; + return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8)); + } /** * 对称加密 - * @param source - * @return - * @throws Exception */ public static String desEncrypt(String source) throws Exception { - if (source == null || source.length() == 0){ - return null; - } Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); - DESKeySpec desKeySpec = new DESKeySpec(strKey.getBytes("UTF-8")); + DESKeySpec desKeySpec = getDesKeySpec(source); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey secretKey = keyFactory.generateSecret(desKeySpec); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV); + return byte2hex(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase(); + } + + /** + * 对称解密 + */ + public static String desDecrypt(String source) throws Exception { + Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8)); + DESKeySpec desKeySpec = getDesKeySpec(source); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); - IvParameterSpec iv = new IvParameterSpec(strParam.getBytes("UTF-8")); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); - return byte2hex( - cipher.doFinal(source.getBytes("UTF-8"))).toUpperCase(); + cipher.init(Cipher.DECRYPT_MODE, secretKey, IV); + byte[] retByte = cipher.doFinal(src); + return new String(retByte); } - public static String byte2hex(byte[] inStr) { + private static String byte2hex(byte[] inStr) { String stmp; - StringBuffer out = new StringBuffer(inStr.length * 2); - for (int n = 0; n < inStr.length; n++) { - stmp = Integer.toHexString(inStr[n] & 0xFF); + StringBuilder out = new StringBuilder(inStr.length * 2); + for (byte b : inStr) { + stmp = Integer.toHexString(b & 0xFF); if (stmp.length() == 1) { - // 如果是0至F的单位字符串,则添加0 - out.append("0" + stmp); + out.append("0").append(stmp); } else { out.append(stmp); } @@ -51,46 +80,16 @@ public static String byte2hex(byte[] inStr) { return out.toString(); } - - public static byte[] hex2byte(byte[] b) { - if ((b.length % 2) != 0){ + private static byte[] hex2byte(byte[] b) { + int size = 2; + if ((b.length % size) != 0) { throw new IllegalArgumentException("长度不是偶数"); } byte[] b2 = new byte[b.length / 2]; - for (int n = 0; n < b.length; n += 2) { + for (int n = 0; n < b.length; n += size) { String item = new String(b, n, 2); b2[n / 2] = (byte) Integer.parseInt(item, 16); } return b2; } - - /** - * 对称解密 - * @param source - * @return - * @throws Exception - */ - public static String desDecrypt(String source) throws Exception { - if (source == null || source.length() == 0){ - return null; - } - byte[] src = hex2byte(source.getBytes()); - Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); - DESKeySpec desKeySpec = new DESKeySpec(strKey.getBytes("UTF-8")); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - SecretKey secretKey = keyFactory.generateSecret(desKeySpec); - IvParameterSpec iv = new IvParameterSpec(strParam.getBytes("UTF-8")); - cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); - byte[] retByte = cipher.doFinal(src); - return new String(retByte); - } - - /** - * 密码加密 - * @param password - * @return - */ - public static String encryptPassword(String password){ - return DigestUtils.md5DigestAsHex(password.getBytes()); - } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java index 5549b8149..590f76b68 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java @@ -1,18 +1,62 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.poi.excel.BigExcelWriter; +import cn.hutool.poi.excel.ExcelUtil; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.exception.BadRequestException; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFSheet; import org.springframework.web.multipart.MultipartFile; - +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.*; +import java.nio.file.Files; +import java.security.MessageDigest; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; /** * File工具类,扩展 hutool 工具包 + * * @author Zheng Jie * @date 2018-12-27 */ +@Slf4j public class FileUtil extends cn.hutool.core.io.FileUtil { + /** + * 系统临时目录 + *
+ * windows 包含路径分割符,但Linux 不包含, + * 在windows \\==\ 前提下, + * 为安全起见 同意拼装 路径分割符, + *
+     *       java.io.tmpdir
+     *       windows : C:\Users/xxx\AppData\Local\Temp\
+     *       linux: /temp
+     * 
+ */ + public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator; /** * 定义GB的计算常量 */ @@ -31,49 +75,40 @@ public class FileUtil extends cn.hutool.core.io.FileUtil { */ private static final DecimalFormat DF = new DecimalFormat("0.00"); + public static final String IMAGE = "图片"; + public static final String TXT = "文档"; + public static final String MUSIC = "音乐"; + public static final String VIDEO = "视频"; + public static final String OTHER = "其他"; + + /** * MultipartFile转File - * @param multipartFile - * @return */ - public static File toFile(MultipartFile multipartFile){ + public static File toFile(MultipartFile multipartFile) { // 获取文件名 String fileName = multipartFile.getOriginalFilename(); // 获取文件后缀 - String prefix="."+getExtensionName(fileName); + String prefix = "." + getExtensionName(fileName); File file = null; try { // 用uuid作为文件名,防止生成的临时文件重复 - file = File.createTempFile(IdUtil.simpleUUID(), prefix); + file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix); // MultipartFile to File multipartFile.transferTo(file); } catch (IOException e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return file; } /** - * 删除 - * @param files - */ - public static void deleteFile(File... files) { - for (File file : files) { - if (file.exists()) { - file.delete(); - } - } - } - - /** - * 获取文件扩展名 - * @param filename - * @return + * 获取文件扩展名,不带 . */ public static String getExtensionName(String filename) { - if ((filename != null) && (filename.length() > 0)) { + if ((filename != null) && (!filename.isEmpty())) { int dot = filename.lastIndexOf('.'); - if ((dot >-1) && (dot < (filename.length() - 1))) { + if ((dot > -1) && (dot < (filename.length() - 1))) { return filename.substring(dot + 1); } } @@ -82,13 +117,11 @@ public static String getExtensionName(String filename) { /** * Java文件操作 获取不带扩展名的文件名 - * @param filename - * @return */ public static String getFileNameNoEx(String filename) { - if ((filename != null) && (filename.length() > 0)) { + if ((filename != null) && (!filename.isEmpty())) { int dot = filename.lastIndexOf('.'); - if ((dot >-1) && (dot < (filename.length()))) { + if (dot > -1) { return filename.substring(0, dot); } } @@ -97,46 +130,280 @@ public static String getFileNameNoEx(String filename) { /** * 文件大小转换 - * @param size - * @return */ - public static String getSize(int size){ - String resultSize = ""; + public static String getSize(long size) { + String resultSize; if (size / GB >= 1) { //如果当前Byte的值大于等于1GB - resultSize = DF.format(size / (float) GB) + "GB "; + resultSize = DF.format(size / (float) GB) + "GB"; } else if (size / MB >= 1) { //如果当前Byte的值大于等于1MB - resultSize = DF.format(size / (float) MB) + "MB "; + resultSize = DF.format(size / (float) MB) + "MB"; } else if (size / KB >= 1) { //如果当前Byte的值大于等于1KB - resultSize = DF.format(size / (float) KB) + "KB "; + resultSize = DF.format(size / (float) KB) + "KB"; } else { - resultSize = size + "B "; + resultSize = size + "B"; } return resultSize; } /** * inputStream 转 File - * @param ins - * @param name - * @return - * @throws Exception */ - public static File inputStreamToFile(InputStream ins, String name) throws Exception{ - File file = new File(System.getProperty("java.io.tmpdir") + name); + static File inputStreamToFile(InputStream ins, String name){ + File file = new File(SYS_TEM_DIR + name); if (file.exists()) { return file; } - OutputStream os = new FileOutputStream(file); - int bytesRead = 0; - byte[] buffer = new byte[8192]; - while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { - os.write(buffer, 0, bytesRead); + OutputStream os = null; + try { + os = Files.newOutputStream(file.toPath()); + int bytesRead; + int len = 8192; + byte[] buffer = new byte[len]; + while ((bytesRead = ins.read(buffer, 0, len)) != -1) { + os.write(buffer, 0, bytesRead); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + CloseUtil.close(os); + CloseUtil.close(ins); } - os.close(); - ins.close(); return file; } + + /** + * 将文件名解析成文件的上传路径 + */ + public static File upload(MultipartFile file, String filePath) { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS"); + // 过滤非法文件名 + String name = getFileNameNoEx(verifyFilename(file.getOriginalFilename())); + String suffix = getExtensionName(file.getOriginalFilename()); + String nowStr = "-" + format.format(date); + try { + String fileName = name + nowStr + "." + suffix; + String path = filePath + fileName; + // getCanonicalFile 可解析正确各种路径 + File dest = new File(path).getCanonicalFile(); + // 检测是否存在目录 + if (!dest.getParentFile().exists()) { + if (!dest.getParentFile().mkdirs()) { + System.out.println("was not successful."); + } + } + // 文件写入 + file.transferTo(dest); + return dest; + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 导出excel + */ + public static void downloadExcel(List> list, HttpServletResponse response) throws IOException { + String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx"; + File file = new File(tempPath); + BigExcelWriter writer = ExcelUtil.getBigWriter(file); + // 处理数据以防止CSV注入 + List> sanitizedList = list.parallelStream().map(map -> { + Map sanitizedMap = new LinkedHashMap<>(); + map.forEach((key, value) -> { + if (value instanceof String) { + String strValue = (String) value; + // 检查并处理以特殊字符开头的值 + if (strValue.startsWith("=") || strValue.startsWith("+") || strValue.startsWith("-") || strValue.startsWith("@")) { + strValue = "'" + strValue; // 添加单引号前缀 + } + sanitizedMap.put(key, strValue); + } else { + sanitizedMap.put(key, value); + } + }); + return sanitizedMap; + }).collect(Collectors.toList()); + // 一次性写出内容,使用默认样式,强制输出标题 + writer.write(sanitizedList, true); + SXSSFSheet sheet = (SXSSFSheet)writer.getSheet(); + //上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法 + sheet.trackAllColumnsForAutoSizing(); + //列宽自适应 + writer.autoSizeColumnAll(); + //response为HttpServletResponse对象 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"); + //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码 + response.setHeader("Content-Disposition", "attachment;filename=file.xlsx"); + ServletOutputStream out = response.getOutputStream(); + // 终止后删除临时文件 + file.deleteOnExit(); + writer.flush(out, true); + //此处记得关闭输出Servlet流 + IoUtil.close(out); + } + + public static String getFileType(String type) { + String documents = "txt doc pdf ppt pps xlsx xls docx"; + String music = "mp3 wav wma mpa ram ra aac aif m4a"; + String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg"; + String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg"; + if (image.contains(type)) { + return IMAGE; + } else if (documents.contains(type)) { + return TXT; + } else if (music.contains(type)) { + return MUSIC; + } else if (video.contains(type)) { + return VIDEO; + } else { + return OTHER; + } + } + + public static void checkSize(long maxSize, long size) { + // 1M + int len = 1024 * 1024; + if (size > (maxSize * len)) { + throw new BadRequestException("文件超出规定大小:" + maxSize + "MB"); + } + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(File file1, File file2) { + String img1Md5 = getMd5(file1); + String img2Md5 = getMd5(file2); + if(img1Md5 != null){ + return img1Md5.equals(img2Md5); + } + return false; + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(String file1Md5, String file2Md5) { + return file1Md5.equals(file2Md5); + } + + private static byte[] getByte(File file) { + // 得到文件长度 + byte[] b = new byte[(int) file.length()]; + InputStream in = null; + try { + in = Files.newInputStream(file.toPath()); + try { + System.out.println(in.read(b)); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } finally { + CloseUtil.close(in); + } + return b; + } + + private static String getMd5(byte[] bytes) { + // 16进制字符 + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(bytes); + byte[] md = mdTemp.digest(); + int j = md.length; + char[] str = new char[j * 2]; + int k = 0; + // 移位 输出字符串 + for (byte byte0 : md) { + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 下载文件 + * + * @param request / + * @param response / + * @param file / + */ + public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) { + response.setCharacterEncoding(request.getCharacterEncoding()); + response.setContentType("application/octet-stream"); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + response.setHeader("Content-Disposition", "attachment; filename=" + file.getName()); + IOUtils.copy(fis, response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + if (fis != null) { + try { + fis.close(); + if (deleteOnExit) { + file.deleteOnExit(); + } + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + } + } + + /** + * 验证并过滤非法的文件名 + * @param fileName 文件名 + * @return 文件名 + */ + public static String verifyFilename(String fileName) { + // 过滤掉特殊字符 + fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", ""); + + // 去掉文件名开头和结尾的空格和点 + fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", ""); + + // 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符 + int maxFileNameLength = 255; + if (System.getProperty("os.name").startsWith("Windows")) { + maxFileNameLength = 260; + } + if (fileName.length() > maxFileNameLength) { + fileName = fileName.substring(0, maxFileNameLength); + } + + // 过滤掉控制字符 + fileName = fileName.replaceAll("[\\p{Cntrl}]", ""); + + // 过滤掉 ".." 路径 + fileName = fileName.replaceAll("\\.{2,}", ""); + + // 去掉文件名开头的 ".." + fileName = fileName.replaceAll("^\\.+/", ""); + + // 保留文件名中最后一个 "." 字符,过滤掉其他 "." + fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") + + fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2"); + + return fileName; + } + + public static String getMd5(File file) { + return getMd5(getByte(file)); + } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java b/eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java new file mode 100644 index 000000000..f6019cd8e --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/PageResult.java @@ -0,0 +1,24 @@ +package me.zhengjie.utils; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页结果封装类 + * @author Zheng Jie + * @date 2018-11-23 + * @param + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageResult implements Serializable { + + private List content; + + private long totalElements; +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java index 7c4c7e703..cbb99e544 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java @@ -1,10 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; import org.springframework.data.domain.Page; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * 分页工具 @@ -15,17 +27,12 @@ public class PageUtil extends cn.hutool.core.util.PageUtil { /** * List 分页 - * @param page - * @param size - * @param list - * @return */ - public static List toPage(int page, int size , List list) { + public static List paging(int page, int size , List list) { int fromIndex = page * size; int toIndex = page * size + size; - if(fromIndex > list.size()){ - return new ArrayList(); + return Collections.emptyList(); } else if(toIndex >= list.size()) { return list.subList(fromIndex,list.size()); } else { @@ -35,30 +42,22 @@ public static List toPage(int page, int size , List list) { /** * Page 数据处理,预防redis反序列化报错 - * @param page - * @return */ - public static Map toPage(Page page) { - Map map = new HashMap(); - - map.put("content",page.getContent()); - map.put("totalElements",page.getTotalElements()); - - return map; + public static PageResult toPage(Page page) { + return new PageResult<>(page.getContent(), page.getTotalElements()); } /** - * @param object - * @param totalElements - * @return + * 自定义分页 */ - public static Map toPage(Object object, Object totalElements) { - Map map = new HashMap(); - - map.put("content",object); - map.put("totalElements",totalElements); - - return map; + public static PageResult toPage(List list, long totalElements) { + return new PageResult<>(list, totalElements); } + /** + * 返回空数据 + */ + public static PageResult noData () { + return new PageResult<>(null, 0); + } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java b/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java index c1ad8ab8d..cbb8b65f8 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java @@ -1,8 +1,25 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.DataPermission; import me.zhengjie.annotation.Query; import javax.persistence.criteria.*; import java.lang.reflect.Field; @@ -13,25 +30,34 @@ * @date 2019-6-4 14:59:48 */ @Slf4j +@SuppressWarnings({"unchecked","all"}) public class QueryHelp { - /** - * @描述 : 转换为Predicate - * @作者 : Dong ZhaoYang - * @日期 : 2017/8/7 - * @时间 : 17:25 - */ - @SuppressWarnings("unchecked") public static Predicate getPredicate(Root root, Q query, CriteriaBuilder cb) { List list = new ArrayList<>(); - if(query == null){ - return cb.and(list.toArray(new Predicate[list.size()])); + return cb.and(list.toArray(new Predicate[0])); + } + // 数据权限验证 + DataPermission permission = query.getClass().getAnnotation(DataPermission.class); + if(permission != null){ + // 获取数据权限 + List dataScopes = SecurityUtils.getCurrentUserDataScope(); + if(CollectionUtil.isNotEmpty(dataScopes)){ + if(StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) { + Join join = root.join(permission.joinName(), JoinType.LEFT); + list.add(getExpression(permission.fieldName(),join, root).in(dataScopes)); + } else if (StringUtils.isBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) { + list.add(getExpression(permission.fieldName(),null, root).in(dataScopes)); + } + } } try { + Map joinKey = new HashMap<>(); List fields = getAllFields(query.getClass(), new ArrayList<>()); for (Field field : fields) { boolean accessible = field.isAccessible(); + // 设置对象的访问权限,保证对private的属性的访 field.setAccessible(true); Query q = field.getAnnotation(Query.class); if (q != null) { @@ -41,7 +67,7 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild String attributeName = isBlank(propName) ? field.getName() : propName; Class fieldType = field.getType(); Object val = field.get(query); - if (ObjectUtil.isNull(val)) { + if (ObjectUtil.isNull(val) || "".equals(val)) { continue; } Join join = null; @@ -50,21 +76,43 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild String[] blurrys = blurry.split(","); List orPredicate = new ArrayList<>(); for (String s : blurrys) { - orPredicate.add(cb.like(root.get(s) - .as(String.class), "%" + val.toString() + "%")); + orPredicate.add(cb.like(root.get(s).as(String.class), "%" + val.toString() + "%")); } Predicate[] p = new Predicate[orPredicate.size()]; list.add(cb.or(orPredicate.toArray(p))); continue; } if (ObjectUtil.isNotEmpty(joinName)) { - switch (q.join()) { - case LEFT: - join = root.join(joinName, JoinType.LEFT); - break; - case RIGHT: - join = root.join(joinName, JoinType.RIGHT); - break; + join = joinKey.get(joinName); + if(join == null){ + String[] joinNames = joinName.split(">"); + for (String name : joinNames) { + switch (q.join()) { + case LEFT: + if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){ + join = join.join(name, JoinType.LEFT); + } else { + join = root.join(name, JoinType.LEFT); + } + break; + case RIGHT: + if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){ + join = join.join(name, JoinType.RIGHT); + } else { + join = root.join(name, JoinType.RIGHT); + } + break; + case INNER: + if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){ + join = join.join(name, JoinType.INNER); + } else { + join = root.join(name, JoinType.INNER); + } + break; + default: break; + } + } + joinKey.put(joinName, join); } } switch (q.type()) { @@ -95,11 +143,38 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild case RIGHT_LIKE: list.add(cb.like(getExpression(attributeName,join,root) .as(String.class), val.toString() + "%")); + break; case IN: - if (CollUtil.isNotEmpty((Collection)val)) { - list.add(getExpression(attributeName,join,root).in((Collection) val)); + if (CollUtil.isNotEmpty((Collection)val)) { + list.add(getExpression(attributeName,join,root).in((Collection) val)); } break; + case NOT_IN: + if (CollUtil.isNotEmpty((Collection)val)) { + list.add(getExpression(attributeName,join,root).in((Collection) val).not()); + } + break; + case NOT_EQUAL: + list.add(cb.notEqual(getExpression(attributeName,join,root), val)); + break; + case NOT_NULL: + list.add(cb.isNotNull(getExpression(attributeName,join,root))); + break; + case IS_NULL: + list.add(cb.isNull(getExpression(attributeName,join,root))); + break; + case BETWEEN: + List between = new ArrayList<>((List)val); + if(between.size() == 2){ + list.add(cb.between(getExpression(attributeName, join, root).as((Class) between.get(0).getClass()), + (Comparable) between.get(0), (Comparable) between.get(1))); + } + break; + case FIND_IN_SET: + list.add(cb.greaterThan(cb.function("FIND_IN_SET", Integer.class, + cb.literal(val.toString()), root.get(attributeName)), 0)); + break; + default: break; } } field.setAccessible(accessible); @@ -107,7 +182,8 @@ public static Predicate getPredicate(Root root, Q query, CriteriaBuild } catch (Exception e) { log.error(e.getMessage(), e); } - return cb.and(list.toArray(new Predicate[list.size()])); + int size = list.size(); + return cb.and(list.toArray(new Predicate[size])); } @SuppressWarnings("unchecked") @@ -119,22 +195,20 @@ private static Expression getExpression(String attributeName, Join joi } } - @SuppressWarnings("unchecked") - public static boolean isBlank(final CharSequence cs) { + private static boolean isBlank(final CharSequence cs) { int strLen; if (cs == null || (strLen = cs.length()) == 0) { return true; } for (int i = 0; i < strLen; i++) { - if (Character.isWhitespace(cs.charAt(i)) == false) { + if (!Character.isWhitespace(cs.charAt(i))) { return false; } } return true; } - @SuppressWarnings("unchecked") - private static List getAllFields(Class clazz, List fields) { + public static List getAllFields(Class clazz, List fields) { if (clazz != null) { fields.addAll(Arrays.asList(clazz.getDeclaredFields())); getAllFields(clazz.getSuperclass(), fields); diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java new file mode 100644 index 000000000..806431ccc --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java @@ -0,0 +1,809 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author / + */ +@Component +@SuppressWarnings({"all"}) +public class RedisUtils { + private static final Logger log = LoggerFactory.getLogger(RedisUtils.class); + + private RedisTemplate redisTemplate; + + public RedisUtils(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + this.redisTemplate.setKeySerializer(new StringRedisSerializer()); + this.redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @param timeUnit 单位 + */ + public boolean expire(String key, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.expire(key, time, timeUnit); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 根据 key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(Object key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 查找匹配key + * + * @param pattern key + * @return / + */ + public List scan(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(); + while (cursor.hasNext()) { + result.add(new String(cursor.next())); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 分页查询 key + * + * @param patternKey key + * @param page 页码 + * @param size 每页数目 + * @return / + */ + public List findKeysForPage(String patternKey, int page, int size) { + ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(size); + int tmpIndex = 0; + int fromIndex = page * size; + int toIndex = page * size + size; + while (cursor.hasNext()) { + if (tmpIndex >= fromIndex && tmpIndex < toIndex) { + result.add(new String(cursor.next())); + tmpIndex++; + continue; + } + // 获取到满足条件的数据后,就可以退出了 + if (tmpIndex >= toIndex) { + break; + } + tmpIndex++; + cursor.next(); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + public void del(String... keys) { + if (keys != null && keys.length > 0) { + if (keys.length == 1) { + boolean result = redisTemplate.delete(keys[0]); + log.debug("--------------------------------------------"); + log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString()); + log.debug("--------------------------------------------"); + } else { + Set keySet = new HashSet<>(); + for (String key : keys) { + if (redisTemplate.hasKey(key)) + keySet.add(key); + } + long count = redisTemplate.delete(keySet); + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keySet.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); + } + } + } + + /** + * 批量模糊删除key + * @param pattern + */ + public void scanDel(String pattern){ + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + try (Cursor cursor = redisTemplate.executeWithStickyConnection( + (RedisCallback>) connection -> (Cursor) new ConvertingCursor<>( + connection.scan(options), redisTemplate.getKeySerializer()::deserialize))) { + while (cursor.hasNext()) { + redisTemplate.delete(cursor.next()); + } + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public T get(String key, Class clazz) { + Object value = key == null ? null : redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } + // 如果 value 不是目标类型,则尝试将其反序列化为 clazz 类型 + if (!clazz.isInstance(value)) { + return JSON.parseObject(value.toString(), clazz); + } else if (clazz.isInstance(value)) { + return clazz.cast(value); + } else { + return null; + } + } + + /** + * 普通缓存获取 + * + * @param key 键 + * @param clazz 列表中元素的类型 + * @return 值 + */ + public List getList(String key, Class clazz) { + Object value = key == null ? null : redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } + if (value instanceof List) { + List list = (List) value; + // 检查每个元素是否为指定类型 + if (list.stream().allMatch(clazz::isInstance)) { + return list.stream().map(clazz::cast).collect(Collectors.toList()); + } + } + return null; + } + + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public String getStr(String key) { + if(StrUtil.isBlank(key)){ + return null; + } + Object value = redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } else { + return String.valueOf(value); + } + } + + /** + * 批量获取 + * + * @param keys + * @return + */ + public List multiGet(List keys) { + List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys)); + List resultList = Lists.newArrayList(); + Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add))); + return resultList; + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + int attempt = 0; + while (attempt < 3) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + attempt++; + log.error("Attempt {} failed: {}", attempt, e.getMessage(), e); + } + } + return false; + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期,注意:这里将会替换原有的时间 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间,注意:这里将会替换原有的时间 + * @param timeUnit 类型 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, timeUnit); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return / + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + return redisTemplate.opsForList().remove(key, count, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * @param prefix 前缀 + * @param ids id + */ + public void delByKeys(String prefix, Set ids) { + Set keys = new HashSet<>(); + for (Long id : ids) { + keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString())); + } + long count = redisTemplate.delete(keys); + } + + // ============================incr============================= + + /** + * 递增 + * @param key + * @return + */ + public Long increment(String key) { + return redisTemplate.opsForValue().increment(key); + } + + /** + * 递减 + * @param key + * @return + */ + public Long decrement(String key) { + return redisTemplate.opsForValue().decrement(key); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java b/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java index 343b451ba..8d90ac48e 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java @@ -1,8 +1,24 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; +import java.util.Objects; /** * 获取 HttpServletRequest @@ -12,6 +28,6 @@ public class RequestHolder { public static HttpServletRequest getHttpServletRequest() { - return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java new file mode 100644 index 000000000..8123ff388 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java @@ -0,0 +1,198 @@ +package me.zhengjie.utils; + +import org.apache.commons.codec.binary.Base64; +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * @author https://www.cnblogs.com/nihaorz/p/10690643.html + * @description Rsa 工具类,公钥私钥生成,加解密 + * @date 2020-05-18 + **/ +public class RsaUtils { + + private static final String SRC = "123456"; + + public static void main(String[] args) throws Exception { + System.out.println("\n"); + RsaKeyPair keyPair = generateKeyPair(); + System.out.println("公钥:" + keyPair.getPublicKey()); + System.out.println("私钥:" + keyPair.getPrivateKey()); + System.out.println("\n"); + test1(keyPair); + System.out.println("\n"); + test2(keyPair); + System.out.println("\n"); + } + + /** + * 公钥加密私钥解密 + */ + private static void test1(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 公钥加密私钥解密开始 *****************"); + String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC); + String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1); + System.out.println("加密前:" + RsaUtils.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (RsaUtils.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 公钥加密私钥解密结束 *****************"); + } + + /** + * 私钥加密公钥解密 + * @throws Exception / + */ + private static void test2(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 私钥加密公钥解密开始 *****************"); + String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC); + String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1); + System.out.println("加密前:" + RsaUtils.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (RsaUtils.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 私钥加密公钥解密结束 *****************"); + } + + /** + * 公钥解密 + * + * @param publicKeyText 公钥 + * @param text 待解密的信息 + * @return / + * @throws Exception / + */ + public static String decryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 私钥加密 + * + * @param privateKeyText 私钥 + * @param text 待加密的信息 + * @return / + * @throws Exception / + */ + public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 私钥解密 + * + * @param privateKeyText 私钥 + * @param text 待解密的文本 + * @return / + * @throws Exception / + */ + public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 公钥加密 + * + * @param publicKeyText 公钥 + * @param text 待加密的文本 + * @return / + */ + public static String encryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes()); + return Base64.encodeBase64String(result); + } + + private static byte[] doLongerCipherFinal(int opMode,Cipher cipher, byte[] source) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (opMode == Cipher.DECRYPT_MODE) { + out.write(cipher.doFinal(source)); + } else { + int offset = 0; + int totalSize = source.length; + while (totalSize - offset > 0) { + int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset); + out.write(cipher.doFinal(source, offset, size)); + offset += size; + } + } + out.close(); + return out.toByteArray(); + } + + /** + * 构建RSA密钥对 + * + * @return / + * @throws NoSuchAlgorithmException / + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair { + + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java index e040c111b..1c1f81341 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java @@ -1,44 +1,145 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; -import cn.hutool.json.JSONObject; -import me.zhengjie.exception.BadRequestException; -import org.springframework.http.HttpStatus; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.JWTUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.utils.enums.DataScopeEnum; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Objects; /** * 获取当前登录的用户 * @author Zheng Jie * @date 2019-01-17 */ +@Slf4j +@Component public class SecurityUtils { - public static UserDetails getUserDetails() { - UserDetails userDetails = null; - try { - userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - } catch (Exception e) { - throw new BadRequestException(HttpStatus.UNAUTHORIZED, "登录状态过期"); + public static String header; + + public static String tokenStartWith; + + @Value("${jwt.header}") + public void setHeader(String header) { + SecurityUtils.header = header; + } + + @Value("${jwt.token-start-with}") + public void setTokenStartWith(String tokenStartWith) { + SecurityUtils.tokenStartWith = tokenStartWith; + } + + /** + * 获取当前登录的用户 + * @return UserDetails + */ + public static UserDetails getCurrentUser() { + UserDetailsService userDetailsService = SpringBeanHolder.getBean(UserDetailsService.class); + return userDetailsService.loadUserByUsername(getCurrentUsername()); + } + + /** + * 获取当前用户的数据权限 + * @return / + */ + public static List getCurrentUserDataScope(){ + UserDetails userDetails = getCurrentUser(); + // 将 Java 对象转换为 JSONObject 对象 + JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails); + JSONArray jsonArray = jsonObject.getJSONArray("dataScopes"); + return JSON.parseArray(jsonArray.toJSONString(), Long.class); + } + + /** + * 获取数据权限级别 + * @return 级别 + */ + public static String getDataScopeType() { + List dataScopes = getCurrentUserDataScope(); + if(CollUtil.isEmpty(dataScopes)){ + return ""; } - return userDetails; + return DataScopeEnum.ALL.getValue(); + } + + /** + * 获取用户ID + * @return 系统用户ID + */ + public static Long getCurrentUserId() { + return getCurrentUserId(getToken()); + } + + /** + * 获取用户ID + * @return 系统用户ID + */ + public static Long getCurrentUserId(String token) { + JWT jwt = JWTUtil.parseToken(token); + return Long.valueOf(jwt.getPayload("userId").toString()); + } + + /** + * 获取系统用户名称 + * + * @return 系统用户名称 + */ + public static String getCurrentUsername() { + return getCurrentUsername(getToken()); } /** * 获取系统用户名称 + * * @return 系统用户名称 */ - public static String getUsername(){ - Object obj = getUserDetails(); - JSONObject json = new JSONObject(obj); - return json.get("username", String.class); + public static String getCurrentUsername(String token) { + JWT jwt = JWTUtil.parseToken(token); + return jwt.getPayload("sub").toString(); } /** - * 获取系统用户id - * @return 系统用户id + * 获取Token + * @return / */ - public static Long getUserId(){ - Object obj = getUserDetails(); - JSONObject json = new JSONObject(obj); - return json.get("id", Long.class); + public static String getToken() { + HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder + .getRequestAttributes())).getRequest(); + String bearerToken = request.getHeader(header); + if (bearerToken != null && bearerToken.startsWith(tokenStartWith)) { + // 去掉令牌前缀 + return bearerToken.replace(tokenStartWith, ""); + } else { + log.debug("非法Token:{}", bearerToken); + } + return null; } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java b/eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java new file mode 100644 index 000000000..ef7ac7b35 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/SpringBeanHolder.java @@ -0,0 +1,172 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Jie + * @date 2019-01-07 + */ +@Slf4j +@SuppressWarnings({"unchecked","all"}) +public class SpringBeanHolder implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + private static final List CALL_BACKS = new ArrayList<>(); + private static boolean addCallback = true; + + /** + * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。 + * 在SpringContextHolder 初始化后,进行回调使用 + * + * @param callBack 回调函数 + */ + public synchronized static void addCallBacks(SpringBeanHolder.CallBack callBack) { + if (addCallback) { + SpringBeanHolder.CALL_BACKS.add(callBack); + } else { + log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName()); + callBack.executor(); + } + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param defaultValue 默认值 + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, T defaultValue, Class requiredType) { + T result = defaultValue; + try { + result = getBean(Environment.class).getProperty(property, requiredType); + } catch (Exception ignored) {} + return result; + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @return / + */ + public static String getProperties(String property) { + return getProperties(property, null, String.class); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, Class requiredType) { + return getProperties(property, null, requiredType); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + if (applicationContext == null) { + throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" + + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); + } + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + private static void clearHolder() { + log.debug("清除SpringContextHolder中的ApplicationContext:" + + applicationContext); + applicationContext = null; + } + + @Override + public void destroy() { + SpringBeanHolder.clearHolder(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringBeanHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringBeanHolder.applicationContext); + } + SpringBeanHolder.applicationContext = applicationContext; + if (addCallback) { + for (SpringBeanHolder.CallBack callBack : SpringBeanHolder.CALL_BACKS) { + callBack.executor(); + } + CALL_BACKS.clear(); + } + SpringBeanHolder.addCallback = false; + } + + /** + * 获取 @Service 的所有 bean 名称 + * @return / + */ + public static List getAllServiceBeanName() { + return new ArrayList<>(Arrays.asList(applicationContext + .getBeanNamesForAnnotation(Service.class))); + } + + interface CallBack { + + /** + * 回调执行方法 + */ + void executor(); + + /** + * 本回调任务名称 + * @return / + */ + default String getCallBackName() { + return Thread.currentThread().getId() + ":" + this.getClass().getName(); + } + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java b/eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java deleted file mode 100644 index e4bfcbbc4..000000000 --- a/eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java +++ /dev/null @@ -1,73 +0,0 @@ -package me.zhengjie.utils; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * @author - * @date 2019-01-07 - */ -@Slf4j -public class SpringContextHolder implements ApplicationContextAware, DisposableBean { - - private static ApplicationContext applicationContext = null; - - /** - * 取得存储在静态变量中的ApplicationContext. - */ - public static ApplicationContext getApplicationContext() { - assertContextInjected(); - return applicationContext; - } - - /** - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. - */ - public static T getBean(String name) { - assertContextInjected(); - return (T) applicationContext.getBean(name); - } - - /** - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. - */ - public static T getBean(Class requiredType) { - assertContextInjected(); - return applicationContext.getBean(requiredType); - } - - /** - * 检查ApplicationContext不为空. - */ - private static void assertContextInjected() { - if (applicationContext == null) { - throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" + - ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); - } - } - - /** - * 清除SpringContextHolder中的ApplicationContext为Null. - */ - public static void clearHolder() { - log.debug("清除SpringContextHolder中的ApplicationContext:" - + applicationContext); - applicationContext = null; - } - - @Override - public void destroy() throws Exception { - SpringContextHolder.clearHolder(); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - if (SpringContextHolder.applicationContext != null) { - log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext); - } - SpringContextHolder.applicationContext = applicationContext; - } -} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java index fd2647d04..c6f1d9585 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java @@ -1,42 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; -import cn.hutool.core.io.resource.ClassPathResource; -import org.lionsoul.ip2region.DataBlock; -import org.lionsoul.ip2region.DbConfig; -import org.lionsoul.ip2region.DbSearcher; - +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import lombok.extern.slf4j.Slf4j; +import net.dreamlu.mica.ip2region.core.Ip2regionSearcher; +import net.dreamlu.mica.ip2region.core.IpInfo; import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.UnknownHostException; import java.util.Calendar; import java.util.Date; +import java.util.Enumeration; /** + * @author Zheng Jie * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类 */ +@Slf4j public class StringUtils extends org.apache.commons.lang3.StringUtils { private static final char SEPARATOR = '_'; - private static final String CHARSET_NAME = "UTF-8"; + private static final String UNKNOWN = "unknown"; /** - * 是否包含字符串 - * - * @param str 验证字符串 - * @param strs 字符串组 - * @return 包含返回true + * 注入bean */ - public static boolean inString(String str, String... strs) { - if (str != null) { - for (String s : strs) { - if (str.equals(trim(s))) { - return true; - } - } - } - return false; - } + private final static Ip2regionSearcher IP_SEARCHER = SpringBeanHolder.getBean(Ip2regionSearcher.class); /** * 驼峰命名法工具 @@ -92,7 +97,7 @@ public static String toCapitalizeCamelCase(String s) { * toCapitalizeCamelCase("hello_world") == "HelloWorld" * toUnderScoreCase("helloWorld") = "hello_world" */ - public static String toUnderScoreCase(String s) { + static String toUnderScoreCase(String s) { if (s == null) { return null; } @@ -125,77 +130,106 @@ public static String toUnderScoreCase(String s) { /** * 获取ip地址 - * @param request - * @return */ - public static String getIP(HttpServletRequest request) { + public static String getIp(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); - if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } - if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } - if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } - String[] ips = ip.split(","); - return "0:0:0:0:0:0:0:1".equals(ips[0])?"127.0.0.1":ips[0]; + String comma = ","; + String localhost = "127.0.0.1"; + if (ip.contains(comma)) { + ip = ip.split(",")[0]; + } + if (localhost.equals(ip)) { + // 获取本机真正的ip地址 + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error(e.getMessage(), e); + } + } + return ip; } /** * 根据ip获取详细地址 - * @param ip - * @return */ public static String getCityInfo(String ip) { - try { - String path = "ip2region/ip2region.db"; - String name = "ip2region.db"; - int algorithm = DbSearcher.BTREE_ALGORITHM; - DbConfig config = new DbConfig(); - File file = FileUtil.inputStreamToFile(new ClassPathResource(path).getStream(), name); - DbSearcher searcher = new DbSearcher(config, file.getPath()); - Method method = null; - switch (algorithm) { - case DbSearcher.BTREE_ALGORITHM: - method = searcher.getClass().getMethod("btreeSearch", String.class); - break; - case DbSearcher.BINARY_ALGORITHM: - method = searcher.getClass().getMethod("binarySearch", String.class); - break; - case DbSearcher.MEMORY_ALGORITYM: - method = searcher.getClass().getMethod("memorySearch", String.class); - break; - default: - method = searcher.getClass().getMethod("memorySearch", String.class); - break; - } - DataBlock dataBlock = null; - dataBlock = (DataBlock) method.invoke(searcher, ip); - String address = dataBlock.getRegion().replace("0|",""); - if(address.charAt(address.length()-1) == '|'){ - address = address.substring(0,address.length() - 1); - } - return address.equals(ElAdminConstant.REGION)?"内网IP":address; - } catch (Exception e) { - e.printStackTrace(); + IpInfo ipInfo = IP_SEARCHER.memorySearch(ip); + if(ipInfo != null){ + return ipInfo.getAddress(); } - return ""; + return null; + } + + /** + * 获取浏览器 + */ + public static String getBrowser(HttpServletRequest request) { + UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent")); + String browser = ua.getBrowser().toString() + " " + ua.getVersion(); + return browser.replace(".0.0.0",""); } /** * 获得当天是周几 */ - public static String getWeekDay(){ + public static String getWeekDay() { String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); int w = cal.get(Calendar.DAY_OF_WEEK) - 1; - if (w < 0){ + if (w < 0) { w = 0; } return weekDays[w]; } -} \ No newline at end of file + + /** + * 获取当前机器的IP + * + * @return / + */ + public static String getLocalIp() { + try { + InetAddress candidateAddress = null; + // 遍历所有的网络接口 + for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); interfaces.hasMoreElements();) { + NetworkInterface anInterface = interfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration inetAddresses = anInterface.getInetAddresses(); inetAddresses.hasMoreElements();) { + InetAddress inetAddr = inetAddresses.nextElement(); + // 排除loopback类型地址 + if (!inetAddr.isLoopbackAddress()) { + if (inetAddr.isSiteLocalAddress()) { + // 如果是site-local地址,就是它了 + return inetAddr.getHostAddress(); + } else if (candidateAddress == null) { + // site-local类型的地址未被发现,先记录候选地址 + candidateAddress = inetAddr; + } + } + } + } + if (candidateAddress != null) { + return candidateAddress.getHostAddress(); + } + // 如果没有发现 non-loopback地址.只能用最次选的方案 + InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); + if (jdkSuppliedAddress == null) { + return ""; + } + return jdkSuppliedAddress.getHostAddress(); + } catch (Exception e) { + return ""; + } + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java index 3725a1a19..59f1d98f4 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java @@ -1,28 +1,37 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; import java.io.PrintWriter; import java.io.StringWriter; /** - * 异常工具 + * 异常工具 2019-01-06 * @author Zheng Jie - * @date 2019-01-06 */ public class ThrowableUtil { /** * 获取堆栈信息 - * @param throwable - * @return */ public static String getStackTrace(Throwable throwable){ StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - try { + try (PrintWriter pw = new PrintWriter(sw)) { throwable.printStackTrace(pw); return sw.toString(); - } finally { - pw.close(); } } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java index fa8e7baf7..2e82d6c4a 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java @@ -1,38 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.ObjectUtil; import me.zhengjie.exception.BadRequestException; -import java.util.Optional; /** * 验证工具 + * * @author Zheng Jie * @date 2018-11-23 */ -public class ValidationUtil{ +public class ValidationUtil { /** * 验证空 - * @param optional */ - public static void isNull(Optional optional, String entity,String parameter , Object value){ - if(!optional.isPresent()){ - String msg = entity - + " 不存在 " - +"{ "+ parameter +":"+ value.toString() +" }"; + public static void isNull(Object obj, String entity, String parameter , Object value){ + if(ObjectUtil.isNull(obj)){ + String msg = entity + " 不存在: "+ parameter +" is "+ value; throw new BadRequestException(msg); } } - /** - * 验证是否为邮箱 - * @param string - * @return - */ - public static boolean isEmail(String string) { - if (string == null){ - return false; - } - String regEx1 = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; - return string.matches(regEx1); - } + /** + * 验证是否为邮箱 + */ + public static boolean isEmail(String email) { + return Validator.isEmail(email); + } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java new file mode 100644 index 000000000..2c6fbb6bf --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 验证码业务场景 + *

+ * @author Zheng Jie + * @date 2020-05-02 + */ +@Getter +@AllArgsConstructor +public enum CodeBiEnum { + + /* 旧邮箱修改邮箱 */ + ONE(1, "旧邮箱修改邮箱"), + + /* 通过邮箱修改密码 */ + TWO(2, "通过邮箱修改密码"); + + private final Integer code; + private final String description; + + public static CodeBiEnum find(Integer code) { + for (CodeBiEnum value : CodeBiEnum.values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java new file mode 100644 index 000000000..3993993f1 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 验证码业务场景对应的 Redis 中的 key + *

+ * @author Zheng Jie + * @date 2020-05-02 + */ +@Getter +@AllArgsConstructor +public enum CodeEnum { + + /* 通过手机号码重置邮箱 */ + PHONE_RESET_EMAIL_CODE("phone_reset_email_code_", "通过手机号码重置邮箱"), + + /* 通过旧邮箱重置邮箱 */ + EMAIL_RESET_EMAIL_CODE("email_reset_email_code_", "通过旧邮箱重置邮箱"), + + /* 通过手机号码重置密码 */ + PHONE_RESET_PWD_CODE("phone_reset_pwd_code_", "通过手机号码重置密码"), + + /* 通过邮箱重置密码 */ + EMAIL_RESET_PWD_CODE("email_reset_pwd_code_", "通过邮箱重置密码"); + + private final String key; + private final String description; +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java new file mode 100644 index 000000000..08e41f66d --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 数据权限枚举 + *

+ * @author Zheng Jie + * @date 2020-05-07 + */ +@Getter +@AllArgsConstructor +public enum DataScopeEnum { + + /* 全部的数据权限 */ + ALL("全部", "全部的数据权限"), + + /* 自己部门的数据权限 */ + THIS_LEVEL("本级", "自己部门的数据权限"), + + /* 自定义的数据权限 */ + CUSTOMIZE("自定义", "自定义的数据权限"); + + private final String value; + private final String description; + + public static DataScopeEnum find(String val) { + for (DataScopeEnum dataScopeEnum : DataScopeEnum.values()) { + if (dataScopeEnum.getValue().equals(val)) { + return dataScopeEnum; + } + } + return null; + } + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java b/eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java new file mode 100644 index 000000000..5d9448350 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.utils.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author Zheng Jie + * @website https://eladmin.vip + * @description + * @date 2020-06-10 + **/ +@Getter +@AllArgsConstructor +public enum RequestMethodEnum { + + /** + * 搜寻 @AnonymousGetMapping + */ + GET("GET"), + + /** + * 搜寻 @AnonymousPostMapping + */ + POST("POST"), + + /** + * 搜寻 @AnonymousPutMapping + */ + PUT("PUT"), + + /** + * 搜寻 @AnonymousPatchMapping + */ + PATCH("PATCH"), + + /** + * 搜寻 @AnonymousDeleteMapping + */ + DELETE("DELETE"), + + /** + * 否则就是所有 Request 接口都放行 + */ + ALL("All"); + + /** + * Request 类型 + */ + private final String type; + + public static RequestMethodEnum find(String type) { + for (RequestMethodEnum value : RequestMethodEnum.values()) { + if (value.getType().equals(type)) { + return value; + } + } + return ALL; + } +} diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java new file mode 100644 index 000000000..dfe01e09e --- /dev/null +++ b/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java @@ -0,0 +1,26 @@ +package me.zhengjie.utils; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.Date; + +public class DateUtilsTest { + @Test + public void test1() { + long l = System.currentTimeMillis() / 1000; + LocalDateTime localDateTime = DateUtil.fromTimeStamp(l); + System.out.print(DateUtil.localDateTimeFormatyMdHms(localDateTime)); + } + + @Test + public void test2() { + LocalDateTime now = LocalDateTime.now(); + System.out.println(DateUtil.localDateTimeFormatyMdHms(now)); + Date date = DateUtil.toDate(now); + LocalDateTime localDateTime = DateUtil.toLocalDateTime(date); + System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime)); + LocalDateTime localDateTime1 = DateUtil.fromTimeStamp(date.getTime() / 1000); + System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime1)); + } +} diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java index f909d9dbd..3ec737523 100644 --- a/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java +++ b/eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java @@ -1,8 +1,9 @@ package me.zhengjie.utils; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; + import static me.zhengjie.utils.EncryptUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; public class EncryptUtilsTest { diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java index f069c1587..48e06bd7d 100644 --- a/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java +++ b/eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java @@ -1,10 +1,10 @@ package me.zhengjie.utils; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockMultipartFile; -import static org.junit.Assert.*; import static me.zhengjie.utils.FileUtil.*; +import static org.junit.jupiter.api.Assertions.assertEquals; public class FileUtilTest { diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java index f18fb1d92..ffb2cf88b 100644 --- a/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java +++ b/eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java @@ -1,22 +1,21 @@ package me.zhengjie.utils; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Date; -import static me.zhengjie.utils.StringUtils.*; -import static org.junit.Assert.*; +import static me.zhengjie.utils.StringUtils.getIp; +import static me.zhengjie.utils.StringUtils.getWeekDay; +import static me.zhengjie.utils.StringUtils.toCamelCase; +import static me.zhengjie.utils.StringUtils.toCapitalizeCamelCase; +import static me.zhengjie.utils.StringUtils.toUnderScoreCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; public class StringUtilsTest { - @Test - public void testInString() { - assertTrue(inString("?", "?")); - assertFalse(inString("?", new String[]{})); - } - @Test public void testToCamelCase() { assertNull(toCamelCase(null)); @@ -44,6 +43,6 @@ public void testGetWeekDay() { @Test public void testGetIP() { - assertEquals("127.0.0.1", getIP(new MockHttpServletRequest())); + assertEquals("127.0.0.1", getIp(new MockHttpServletRequest())); } -} \ No newline at end of file +} diff --git a/eladmin-generator/pom.xml b/eladmin-generator/pom.xml index bf69a3a4b..46b8e757b 100644 --- a/eladmin-generator/pom.xml +++ b/eladmin-generator/pom.xml @@ -1,25 +1,24 @@ - + eladmin me.zhengjie - 2.1 + 2.7 4.0.0 eladmin-generator + 代码生成模块 - 1.9 + 1.10 me.zhengjie eladmin-common - 2.1 + 2.7 diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java b/eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java new file mode 100644 index 000000000..fddc19935 --- /dev/null +++ b/eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.domain; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import me.zhengjie.utils.GenUtil; +import javax.persistence.*; +import java.io.Serializable; + +/** + * 列的数据信息 + * @author Zheng Jie + * @date 2019-01-02 + */ +@Getter +@Setter +@Entity +@NoArgsConstructor +@Table(name = "code_column") +public class ColumnInfo implements Serializable { + + @Id + @Column(name = "column_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ApiModelProperty(value = "表名") + private String tableName; + + @ApiModelProperty(value = "数据库字段名称") + private String columnName; + + @ApiModelProperty(value = "数据库字段类型") + private String columnType; + + @ApiModelProperty(value = "数据库字段键类型") + private String keyType; + + @ApiModelProperty(value = "字段额外的参数") + private String extra; + + @ApiModelProperty(value = "数据库字段描述") + private String remark; + + @ApiModelProperty(value = "是否必填") + private Boolean notNull; + + @ApiModelProperty(value = "是否在列表显示") + private Boolean listShow; + + @ApiModelProperty(value = "是否表单显示") + private Boolean formShow; + + @ApiModelProperty(value = "表单类型") + private String formType; + + @ApiModelProperty(value = "查询 1:模糊 2:精确") + private String queryType; + + @ApiModelProperty(value = "字典名称") + private String dictName; + + @ApiModelProperty(value = "日期注解") + private String dateAnnotation; + + public ColumnInfo(String tableName, String columnName, Boolean notNull, String columnType, String remark, String keyType, String extra) { + this.tableName = tableName; + this.columnName = columnName; + this.columnType = columnType; + this.keyType = keyType; + this.extra = extra; + this.notNull = notNull; + if(GenUtil.PK.equalsIgnoreCase(keyType) && GenUtil.EXTRA.equalsIgnoreCase(extra)){ + this.notNull = false; + } + this.remark = remark; + this.listShow = true; + this.formShow = true; + } +} diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java b/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java index 1f79a9110..47e87d318 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java +++ b/eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java @@ -1,41 +1,78 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.domain; -import lombok.Data; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import java.io.Serializable; /** * 代码生成配置 * @author Zheng Jie * @date 2019-01-03 */ -@Data +@Getter +@Setter @Entity -@Table(name = "gen_config") -public class GenConfig { +@NoArgsConstructor +@Table(name = "code_config") +public class GenConfig implements Serializable { + + public GenConfig(String tableName) { + this.tableName = tableName; + } @Id + @Column(name = "config_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** 包路径 **/ + @NotBlank + @ApiModelProperty(value = "表名") + private String tableName; + + @ApiModelProperty(value = "接口名称") + private String apiAlias; + + @NotBlank + @ApiModelProperty(value = "包路径") private String pack; - /** 模块名 **/ - @Column(name = "module_name") + @NotBlank + @ApiModelProperty(value = "模块名") private String moduleName; - /** 前端文件路径 **/ + @NotBlank + @ApiModelProperty(value = "前端文件路径") private String path; - /** 前端文件路径 **/ - @Column(name = "api_path") + @ApiModelProperty(value = "前端文件路径") private String apiPath; - /** 作者 **/ + @ApiModelProperty(value = "作者") private String author; - /** 表前缀 **/ + @ApiModelProperty(value = "表前缀") private String prefix; - /** 是否覆盖 **/ - private Boolean cover; + @ApiModelProperty(value = "是否覆盖") + private Boolean cover = false; } diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/ColumnInfo.java b/eladmin-generator/src/main/java/me/zhengjie/domain/vo/ColumnInfo.java deleted file mode 100644 index 5a8475a2b..000000000 --- a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/ColumnInfo.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.zhengjie.domain.vo; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 列的数据信息 - * @author Zheng Jie - * @date 2019-01-02 - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ColumnInfo { - - /** 数据库字段名称 **/ - private Object columnName; - - /** 允许空值 **/ - private Object isNullable; - - /** 数据库字段类型 **/ - private Object columnType; - - /** 数据库字段注释 **/ - private Object columnComment; - - /** 数据库字段键类型 **/ - private Object columnKey; - - /** 额外的参数 **/ - private Object extra; - - /** 查询 1:模糊 2:精确 **/ - private String columnQuery; - - /** 是否在列表显示 **/ - private String columnShow; -} diff --git a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java b/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java index 92712d757..c900a6540 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java +++ b/eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java @@ -1,5 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.domain.vo; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -14,20 +31,19 @@ @NoArgsConstructor public class TableInfo { - /** 表名称 **/ + @ApiModelProperty(value = "表名称") private Object tableName; - /** 创建日期 **/ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建日期:yyyy-MM-dd HH:mm:ss") private Object createTime; - // 数据库引擎 + @ApiModelProperty(value = "数据库引擎") private Object engine; - // 编码集 + @ApiModelProperty(value = "编码集") private Object coding; - // 备注 + @ApiModelProperty(value = "备注") private Object remark; - - } diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java b/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java new file mode 100644 index 000000000..7a174d2d2 --- /dev/null +++ b/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.repository; + +import me.zhengjie.domain.ColumnInfo; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +/** + * @author Zheng Jie + * @date 2019-01-14 + */ +public interface ColumnInfoRepository extends JpaRepository { + + /** + * 查询表信息 + * @param tableName 表格名 + * @return 表信息 + */ + List findByTableNameOrderByIdAsc(String tableName); +} diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java b/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java index d72787d06..1513c8102 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java +++ b/eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.repository; import me.zhengjie.domain.GenConfig; @@ -8,4 +23,11 @@ * @date 2019-01-14 */ public interface GenConfigRepository extends JpaRepository { + + /** + * 查询表配置 + * @param tableName 表名 + * @return / + */ + GenConfig findByTableName(String tableName); } diff --git a/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java b/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java index 19c092f85..85a2a4bb3 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java +++ b/eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java @@ -1,8 +1,25 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.rest; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; import me.zhengjie.domain.GenConfig; import me.zhengjie.service.GenConfigService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -13,23 +30,22 @@ * @date 2019-01-14 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@RequestMapping("/api/genConfig") +@Api(tags = "系统:代码生成器配置管理") public class GenConfigController { - @Autowired - private GenConfigService genConfigService; + private final GenConfigService genConfigService; - /** - * 查询生成器配置 - * @return - */ - @GetMapping(value = "/genConfig") - public ResponseEntity get(){ - return new ResponseEntity(genConfigService.find(), HttpStatus.OK); + @ApiOperation("查询") + @GetMapping(value = "/{tableName}") + public ResponseEntity queryGenConfig(@PathVariable String tableName){ + return new ResponseEntity<>(genConfigService.find(tableName), HttpStatus.OK); } - @PutMapping(value = "/genConfig") - public ResponseEntity emailConfig(@Validated @RequestBody GenConfig genConfig){ - return new ResponseEntity(genConfigService.update(genConfig),HttpStatus.OK); + @PutMapping + @ApiOperation("修改") + public ResponseEntity updateGenConfig(@Validated @RequestBody GenConfig genConfig){ + return new ResponseEntity<>(genConfigService.update(genConfig.getTableName(), genConfig),HttpStatus.OK); } } diff --git a/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java b/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java index 65158981d..68148b942 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java +++ b/eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java @@ -1,15 +1,36 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.rest; -import cn.hutool.core.util.PageUtil; -import me.zhengjie.domain.vo.ColumnInfo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.domain.ColumnInfo; +import me.zhengjie.domain.vo.TableInfo; import me.zhengjie.exception.BadRequestException; import me.zhengjie.service.GenConfigService; import me.zhengjie.service.GeneratorService; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.PageResult; +import me.zhengjie.utils.PageUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.List; /** @@ -17,54 +38,72 @@ * @date 2019-01-02 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@RequestMapping("/api/generator") +@Api(tags = "系统:代码生成管理") public class GeneratorController { - @Autowired - private GeneratorService generatorService; - - @Autowired - private GenConfigService genConfigService; + private final GeneratorService generatorService; + private final GenConfigService genConfigService; @Value("${generator.enabled}") private Boolean generatorEnabled; - /** - * 查询数据库元数据 - * @param name - * @param page - * @param size - * @return - */ - @GetMapping(value = "/generator/tables") - public ResponseEntity getTables(@RequestParam(defaultValue = "") String name, - @RequestParam(defaultValue = "0")Integer page, - @RequestParam(defaultValue = "10")Integer size){ - int[] startEnd = PageUtil.transToStartEnd(page+1, size); - return new ResponseEntity(generatorService.getTables(name,startEnd), HttpStatus.OK); + @ApiOperation("查询数据库数据") + @GetMapping(value = "/tables/all") + public ResponseEntity queryAllTables(){ + return new ResponseEntity<>(generatorService.getTables(), HttpStatus.OK); + } + + @ApiOperation("查询数据库数据") + @GetMapping(value = "/tables") + public ResponseEntity> queryTables(@RequestParam(defaultValue = "") String name, + @RequestParam(defaultValue = "0")Integer page, + @RequestParam(defaultValue = "10")Integer size){ + int[] startEnd = PageUtil.transToStartEnd(page, size); + return new ResponseEntity<>(generatorService.getTables(name,startEnd), HttpStatus.OK); + } + + @ApiOperation("查询字段数据") + @GetMapping(value = "/columns") + public ResponseEntity> queryColumns(@RequestParam String tableName){ + List columnInfos = generatorService.getColumns(tableName); + return new ResponseEntity<>(PageUtil.toPage(columnInfos,columnInfos.size()), HttpStatus.OK); } - /** - * 查询表内元数据 - * @param tableName - * @return - */ - @GetMapping(value = "/generator/columns") - public ResponseEntity getTables(@RequestParam String tableName){ - return new ResponseEntity(generatorService.getColumns(tableName), HttpStatus.OK); + @ApiOperation("保存字段数据") + @PutMapping + public ResponseEntity saveColumn(@RequestBody List columnInfos){ + generatorService.save(columnInfos); + return new ResponseEntity<>(HttpStatus.OK); } - /** - * 生成代码 - * @param columnInfos - * @return - */ - @PostMapping(value = "/generator") - public ResponseEntity generator(@RequestBody List columnInfos, @RequestParam String tableName){ - if(!generatorEnabled){ - throw new BadRequestException("此环境不允许生成代码!"); + @ApiOperation("同步字段数据") + @PostMapping(value = "sync") + public ResponseEntity syncColumn(@RequestBody List tables){ + for (String table : tables) { + generatorService.sync(generatorService.getColumns(table), generatorService.query(table)); + } + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation("生成代码") + @PostMapping(value = "/{tableName}/{type}") + public ResponseEntity generatorCode(@PathVariable String tableName, @PathVariable Integer type, HttpServletRequest request, HttpServletResponse response){ + if(!generatorEnabled && type == 0){ + throw new BadRequestException("此环境不允许生成代码,请选择预览或者下载查看!"); + } + switch (type){ + // 生成代码 + case 0: generatorService.generator(genConfigService.find(tableName), generatorService.getColumns(tableName)); + break; + // 预览 + case 1: return generatorService.preview(genConfigService.find(tableName), generatorService.getColumns(tableName)); + // 打包 + case 2: generatorService.download(genConfigService.find(tableName), generatorService.getColumns(tableName), request, response); + break; + default: throw new BadRequestException("没有这个选项"); } - generatorService.generator(columnInfos,genConfigService.find(),tableName); - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } } diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java b/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java index 34276545e..175439aba 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java +++ b/eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java @@ -1,30 +1,40 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service; import me.zhengjie.domain.GenConfig; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; /** * @author Zheng Jie * @date 2019-01-14 */ -@CacheConfig(cacheNames = "genConfig") public interface GenConfigService { /** - * find - * @return + * 查询表配置 + * @param tableName 表名 + * @return 表配置 */ - @Cacheable(key = "'1'") - GenConfig find(); + GenConfig find(String tableName); /** - * update - * @param genConfig - * @return + * 更新表配置 + * @param tableName 表名 + * @param genConfig 表配置 + * @return 表配置 */ - @CacheEvict(allEntries = true) - GenConfig update(GenConfig genConfig); + GenConfig update(String tableName, GenConfig genConfig); } diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java b/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java index 8b9158a92..6e59160ae 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java +++ b/eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java @@ -1,7 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service; import me.zhengjie.domain.GenConfig; -import me.zhengjie.domain.vo.ColumnInfo; +import me.zhengjie.domain.ColumnInfo; +import me.zhengjie.domain.vo.TableInfo; +import me.zhengjie.utils.PageResult; +import org.springframework.http.ResponseEntity; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.List; /** @@ -12,24 +32,66 @@ public interface GeneratorService { /** * 查询数据库元数据 - * @param name - * @param startEnd - * @return + * @param name 表名 + * @param startEnd 分页参数 + * @return / */ - Object getTables(String name, int[] startEnd); + PageResult getTables(String name, int[] startEnd); /** * 得到数据表的元数据 - * @param name - * @return + * @param name 表名 + * @return / + */ + List getColumns(String name); + + /** + * 同步表数据 + * @param columnInfos / + * @param columnInfoList / + */ + void sync(List columnInfos, List columnInfoList); + + /** + * 保持数据 + * @param columnInfos / + */ + void save(List columnInfos); + + /** + * 获取所有table + * @return / + */ + Object getTables(); + + /** + * 代码生成 + * @param genConfig 配置信息 + * @param columns 字段信息 + */ + void generator(GenConfig genConfig, List columns); + + /** + * 预览 + * @param genConfig 配置信息 + * @param columns 字段信息 + * @return / + */ + ResponseEntity preview(GenConfig genConfig, List columns); + + /** + * 打包下载 + * @param genConfig 配置信息 + * @param columns 字段信息 + * @param request / + * @param response / */ - Object getColumns(String name); + void download(GenConfig genConfig, List columns, HttpServletRequest request, HttpServletResponse response); /** - * 生成代码 - * @param columnInfos - * @param genConfig - * @param tableName + * 查询数据库的表字段数据数据 + * @param table / + * @return / */ - void generator(List columnInfos, GenConfig genConfig, String tableName); + List query(String table); } diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java index ec53052ea..5801d983f 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java +++ b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java @@ -1,35 +1,67 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service.impl; +import lombok.RequiredArgsConstructor; import me.zhengjie.domain.GenConfig; import me.zhengjie.repository.GenConfigRepository; import me.zhengjie.service.GenConfigService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.Optional; +import java.io.File; /** * @author Zheng Jie * @date 2019-01-14 */ @Service +@RequiredArgsConstructor +@SuppressWarnings({"unchecked","all"}) public class GenConfigServiceImpl implements GenConfigService { - @Autowired - private GenConfigRepository genConfigRepository; + private final GenConfigRepository genConfigRepository; @Override - public GenConfig find() { - Optional genConfig = genConfigRepository.findById(1L); - if(genConfig.isPresent()){ - return genConfig.get(); - } else { - return new GenConfig(); + public GenConfig find(String tableName) { + GenConfig genConfig = genConfigRepository.findByTableName(tableName); + if(genConfig == null){ + return new GenConfig(tableName); } + return genConfig; } @Override - public GenConfig update(GenConfig genConfig) { - genConfig.setId(1L); + public GenConfig update(String tableName, GenConfig genConfig) { + String separator = File.separator; + String[] paths; + String symbol = "\\"; + if (symbol.equals(separator)) { + paths = genConfig.getPath().split("\\\\"); + } else { + paths = genConfig.getPath().split(File.separator); + } + StringBuilder api = new StringBuilder(); + for (String path : paths) { + api.append(path); + api.append(separator); + if ("src".equals(path)) { + api.append("api"); + break; + } + } + genConfig.setApiPath(api.toString()); return genConfigRepository.save(genConfig); } } diff --git a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java index 49e4e1c50..fdd0a0ae2 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java +++ b/eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java @@ -1,76 +1,205 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service.impl; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ZipUtil; +import lombok.RequiredArgsConstructor; import me.zhengjie.domain.GenConfig; -import me.zhengjie.domain.vo.ColumnInfo; +import me.zhengjie.domain.ColumnInfo; import me.zhengjie.domain.vo.TableInfo; import me.zhengjie.exception.BadRequestException; +import me.zhengjie.repository.ColumnInfoRepository; import me.zhengjie.service.GeneratorService; -import me.zhengjie.utils.GenUtil; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.StringUtils; +import me.zhengjie.utils.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; import java.io.IOException; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * @author Zheng Jie * @date 2019-01-02 */ @Service +@RequiredArgsConstructor +@SuppressWarnings({"unchecked","all"}) public class GeneratorServiceImpl implements GeneratorService { - + private static final Logger log = LoggerFactory.getLogger(GeneratorServiceImpl.class); @PersistenceContext private EntityManager em; + private final ColumnInfoRepository columnInfoRepository; + + private final String CONFIG_MESSAGE = "请先配置生成器"; @Override - public Object getTables(String name, int[] startEnd) { + public Object getTables() { // 使用预编译防止sql注入 String sql = "select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables " + "where table_schema = (select database()) " + - "and table_name like ? order by create_time desc"; + "order by create_time desc"; + Query query = em.createNativeQuery(sql); + return query.getResultList(); + } + + @Override + public PageResult getTables(String name, int[] startEnd) { + // 使用预编译防止sql注入 + String sql = "select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables " + + "where table_schema = (select database()) " + + "and table_name like :table order by create_time desc"; Query query = em.createNativeQuery(sql); query.setFirstResult(startEnd[0]); - query.setMaxResults(startEnd[1]-startEnd[0]); - query.setParameter(1, StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%"); - List result = query.getResultList(); + query.setMaxResults(startEnd[1] - startEnd[0]); + query.setParameter("table", StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%"); + List result = query.getResultList(); List tableInfos = new ArrayList<>(); - for (Object[] obj : result) { - tableInfos.add(new TableInfo(obj[0],obj[1],obj[2],obj[3], ObjectUtil.isNotEmpty(obj[4])? obj[4] : "-")); + for (Object obj : result) { + Object[] arr = (Object[]) obj; + tableInfos.add(new TableInfo(arr[0], arr[1], arr[2], arr[3], ObjectUtil.isNotEmpty(arr[4]) ? arr[4] : "-")); + } + String countSql = "select count(1) from information_schema.tables " + + "where table_schema = (select database()) and table_name like :table"; + Query queryCount = em.createNativeQuery(countSql); + queryCount.setParameter("table", StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%"); + BigInteger totalElements = (BigInteger) queryCount.getSingleResult(); + return PageUtil.toPage(tableInfos, totalElements.longValue()); + } + + @Override + public List getColumns(String tableName) { + List columnInfos = columnInfoRepository.findByTableNameOrderByIdAsc(tableName); + if (CollectionUtil.isNotEmpty(columnInfos)) { + return columnInfos; + } else { + columnInfos = query(tableName); + return columnInfoRepository.saveAll(columnInfos); } - Query query1 = em.createNativeQuery("SELECT COUNT(*) from information_schema.tables where table_schema = (select database())"); - Object totalElements = query1.getSingleResult(); - return PageUtil.toPage(tableInfos,totalElements); } @Override - public Object getColumns(String name) { + public List query(String tableName) { // 使用预编译防止sql注入 String sql = "select column_name, is_nullable, data_type, column_comment, column_key, extra from information_schema.columns " + "where table_name = ? and table_schema = (select database()) order by ordinal_position"; Query query = em.createNativeQuery(sql); - query.setParameter(1, StringUtils.isNotBlank(name) ? name : null); - List result = query.getResultList(); + query.setParameter(1, tableName); + List result = query.getResultList(); List columnInfos = new ArrayList<>(); - for (Object[] obj : result) { - columnInfos.add(new ColumnInfo(obj[0],obj[1],obj[2],obj[3],obj[4],obj[5],null,"true")); + for (Object obj : result) { + Object[] arr = (Object[]) obj; + columnInfos.add( + new ColumnInfo( + tableName, + arr[0].toString(), + "NO".equals(arr[1]), + arr[2].toString(), + ObjectUtil.isNotNull(arr[3]) ? arr[3].toString() : null, + ObjectUtil.isNotNull(arr[4]) ? arr[4].toString() : null, + ObjectUtil.isNotNull(arr[5]) ? arr[5].toString() : null) + ); + } + return columnInfos; + } + + @Override + public void sync(List columnInfos, List columnInfoList) { + // 第一种情况,数据库类字段改变或者新增字段 + for (ColumnInfo columnInfo : columnInfoList) { + // 根据字段名称查找 + List columns = columnInfos.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList()); + // 如果能找到,就修改部分可能被字段 + if (CollectionUtil.isNotEmpty(columns)) { + ColumnInfo column = columns.get(0); + column.setColumnType(columnInfo.getColumnType()); + column.setExtra(columnInfo.getExtra()); + column.setKeyType(columnInfo.getKeyType()); + if (StringUtils.isBlank(column.getRemark())) { + column.setRemark(columnInfo.getRemark()); + } + columnInfoRepository.save(column); + } else { + // 如果找不到,则保存新字段信息 + columnInfoRepository.save(columnInfo); + } + } + // 第二种情况,数据库字段删除了 + for (ColumnInfo columnInfo : columnInfos) { + // 根据字段名称查找 + List columns = columnInfoList.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList()); + // 如果找不到,就代表字段被删除了,则需要删除该字段 + if (CollectionUtil.isEmpty(columns)) { + columnInfoRepository.delete(columnInfo); + } + } + } + + @Override + public void save(List columnInfos) { + columnInfoRepository.saveAll(columnInfos); + } + + @Override + public void generator(GenConfig genConfig, List columns) { + if (genConfig.getId() == null) { + throw new BadRequestException(CONFIG_MESSAGE); + } + try { + GenUtil.generatorCode(columns, genConfig); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new BadRequestException("生成失败,请手动处理已生成的文件"); + } + } + + @Override + public ResponseEntity preview(GenConfig genConfig, List columns) { + if (genConfig.getId() == null) { + throw new BadRequestException(CONFIG_MESSAGE); } - return PageUtil.toPage(columnInfos,columnInfos.size()); + List> genList = GenUtil.preview(columns, genConfig); + return new ResponseEntity<>(genList, HttpStatus.OK); } @Override - public void generator(List columnInfos, GenConfig genConfig, String tableName) { - if(genConfig.getId() == null){ - throw new BadRequestException("请先配置生成器"); + public void download(GenConfig genConfig, List columns, HttpServletRequest request, HttpServletResponse response) { + if (genConfig.getId() == null) { + throw new BadRequestException(CONFIG_MESSAGE); } try { - GenUtil.generatorCode(columnInfos,genConfig,tableName); + File file = new File(GenUtil.download(columns, genConfig)); + String zipPath = file.getPath() + ".zip"; + ZipUtil.zip(file.getPath(), zipPath); + FileUtil.downloadFile(request, response, new File(zipPath), true); } catch (IOException e) { - throw new RuntimeException(e); + throw new BadRequestException("打包失败"); } } } diff --git a/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java b/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java index 66026d940..cee76364c 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java +++ b/eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java @@ -1,6 +1,23 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; import org.apache.commons.configuration.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * sql字段转java @@ -9,15 +26,18 @@ * @date 2019-01-03 */ public class ColUtil { + private static final Logger log = LoggerFactory.getLogger(ColUtil.class); /** * 转换mysql数据类型为java数据类型 - * @param type - * @return + * + * @param type 数据库字段类型 + * @return String */ - public static String cloToJava(String type){ + static String cloToJava(String type) { Configuration config = getConfig(); - return config.getString(type,"unknowType"); + assert config != null; + return config.getString(type, "unknowType"); } /** @@ -25,9 +45,9 @@ public static String cloToJava(String type){ */ public static PropertiesConfiguration getConfig() { try { - return new PropertiesConfiguration("generator.properties" ); + return new PropertiesConfiguration("gen.properties"); } catch (ConfigurationException e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return null; } diff --git a/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java b/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java index ca198b314..bb5a85e43 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java +++ b/eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java @@ -1,176 +1,355 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.template.*; import lombok.extern.slf4j.Slf4j; import me.zhengjie.domain.GenConfig; -import me.zhengjie.domain.vo.ColumnInfo; +import me.zhengjie.domain.ColumnInfo; import org.springframework.util.ObjectUtils; + import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; + +import static me.zhengjie.utils.FileUtil.SYS_TEM_DIR; /** * 代码生成 + * * @author Zheng Jie * @date 2019-01-02 */ @Slf4j +@SuppressWarnings({"unchecked", "all"}) public class GenUtil { private static final String TIMESTAMP = "Timestamp"; private static final String BIGDECIMAL = "BigDecimal"; - private static final String PK = "PRI"; + public static final String PK = "PRI"; - private static final String EXTRA = "auto_increment"; + public static final String EXTRA = "auto_increment"; /** * 获取后端代码模板名称 - * @return + * + * @return List */ - public static List getAdminTemplateNames() { + private static List getAdminTemplateNames() { List templateNames = new ArrayList<>(); templateNames.add("Entity"); templateNames.add("Dto"); templateNames.add("Mapper"); - templateNames.add("Repository"); + templateNames.add("Controller"); + templateNames.add("QueryCriteria"); templateNames.add("Service"); templateNames.add("ServiceImpl"); - templateNames.add("QueryCriteria"); - templateNames.add("Controller"); + templateNames.add("Repository"); return templateNames; } /** * 获取前端代码模板名称 - * @return + * + * @return List */ - public static List getFrontTemplateNames() { + private static List getFrontTemplateNames() { List templateNames = new ArrayList<>(); - templateNames.add("api"); templateNames.add("index"); - templateNames.add("eForm"); + templateNames.add("api"); return templateNames; } - /** - * 生成代码 - * @param columnInfos 表元数据 - * @param genConfig 生成代码的参数配置,如包路径,作者 - */ - public static void generatorCode(List columnInfos, GenConfig genConfig, String tableName) throws IOException { - Map map = new HashMap(); - map.put("package",genConfig.getPack()); - map.put("moduleName",genConfig.getModuleName()); - map.put("author",genConfig.getAuthor()); - map.put("date", LocalDate.now().toString()); - map.put("tableName",tableName); - String className = StringUtils.toCapitalizeCamelCase(tableName); - String changeClassName = StringUtils.toCamelCase(tableName); + public static List> preview(List columns, GenConfig genConfig) { + Map genMap = getGenMap(columns, genConfig); + List> genList = new ArrayList<>(); + // 获取后端模版 + List templates = getAdminTemplateNames(); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + for (String templateName : templates) { + Map map = new HashMap<>(1); + Template template = engine.getTemplate("admin/" + templateName + ".ftl"); + map.put("content", template.render(genMap)); + map.put("name", templateName); + genList.add(map); + } + // 获取前端模版 + templates = getFrontTemplateNames(); + for (String templateName : templates) { + Map map = new HashMap<>(1); + Template template = engine.getTemplate("front/" + templateName + ".ftl"); + map.put(templateName, template.render(genMap)); + map.put("content", template.render(genMap)); + map.put("name", templateName); + genList.add(map); + } + return genList; + } - // 判断是否去除表前缀 - if (StringUtils.isNotEmpty(genConfig.getPrefix())) { - className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(tableName,genConfig.getPrefix())); - changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(tableName,genConfig.getPrefix())); - } - map.put("className", className); - map.put("upperCaseClassName", className.toUpperCase()); - map.put("changeClassName", changeClassName); - map.put("hasTimestamp",false); - map.put("hasBigDecimal",false); - map.put("hasQuery",false); - map.put("auto",false); - - List> columns = new ArrayList<>(); - List> queryColumns = new ArrayList<>(); - for (ColumnInfo column : columnInfos) { - Map listMap = new HashMap(); - listMap.put("columnComment",column.getColumnComment()); - listMap.put("columnKey",column.getColumnKey()); - - String colType = ColUtil.cloToJava(column.getColumnType().toString()); - String changeColumnName = StringUtils.toCamelCase(column.getColumnName().toString()); - String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getColumnName().toString()); - if(PK.equals(column.getColumnKey())){ - map.put("pkColumnType",colType); - map.put("pkChangeColName",changeColumnName); - map.put("pkCapitalColName",capitalColumnName); - } - if(TIMESTAMP.equals(colType)){ - map.put("hasTimestamp",true); - } - if(BIGDECIMAL.equals(colType)){ - map.put("hasBigDecimal",true); - } - if(EXTRA.equals(column.getExtra())){ - map.put("auto",true); + public static String download(List columns, GenConfig genConfig) throws IOException { + // 拼接的路径:/tmpeladmin-gen-temp/,这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败,更改为: /tmp/eladmin-gen-temp/ + // String tempPath =SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator; + String tempPath = SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator; + Map genMap = getGenMap(columns, genConfig); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + // 生成后端代码 + List templates = getAdminTemplateNames(); + for (String templateName : templates) { + Template template = engine.getTemplate("admin/" + templateName + ".ftl"); + String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), tempPath + "eladmin" + File.separator); + assert filePath != null; + File file = new File(filePath); + // 如果非覆盖生成 + if (!genConfig.getCover() && FileUtil.exist(file)) { + continue; } - listMap.put("columnType",colType); - listMap.put("columnName",column.getColumnName()); - listMap.put("isNullable",column.getIsNullable()); - listMap.put("columnShow",column.getColumnShow()); - listMap.put("changeColumnName",changeColumnName); - listMap.put("capitalColumnName",capitalColumnName); - - // 判断是否有查询,如有则把查询的字段set进columnQuery - if(!StringUtils.isBlank(column.getColumnQuery())){ - listMap.put("columnQuery",column.getColumnQuery()); - map.put("hasQuery",true); - queryColumns.add(listMap); + // 生成代码 + genFile(file, template, genMap); + } + // 生成前端代码 + templates = getFrontTemplateNames(); + for (String templateName : templates) { + Template template = engine.getTemplate("front/" + templateName + ".ftl"); + String path = tempPath + "eladmin-web" + File.separator; + String apiPath = path + "src" + File.separator + "api" + File.separator; + String srcPath = path + "src" + File.separator + "views" + File.separator + genMap.get("changeClassName").toString() + File.separator; + String filePath = getFrontFilePath(templateName, apiPath, srcPath, genMap.get("changeClassName").toString()); + assert filePath != null; + File file = new File(filePath); + // 如果非覆盖生成 + if (!genConfig.getCover() && FileUtil.exist(file)) { + continue; } - columns.add(listMap); + // 生成代码 + genFile(file, template, genMap); } - map.put("columns",columns); - map.put("queryColumns",queryColumns); - TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + return tempPath; + } + public static void generatorCode(List columnInfos, GenConfig genConfig) throws IOException { + Map genMap = getGenMap(columnInfos, genConfig); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); // 生成后端代码 List templates = getAdminTemplateNames(); for (String templateName : templates) { - Template template = engine.getTemplate("generator/admin/"+templateName+".ftl"); - String filePath = getAdminFilePath(templateName,genConfig,className); + Template template = engine.getTemplate("admin/" + templateName + ".ftl"); + String rootPath = System.getProperty("user.dir"); + String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), rootPath); + assert filePath != null; File file = new File(filePath); // 如果非覆盖生成 - if(!genConfig.getCover() && FileUtil.exist(file)){ + if (!genConfig.getCover() && FileUtil.exist(file)) { continue; } // 生成代码 - genFile(file, template, map); + genFile(file, template, genMap); } // 生成前端代码 templates = getFrontTemplateNames(); for (String templateName : templates) { - Template template = engine.getTemplate("generator/front/"+templateName+".ftl"); - String filePath = getFrontFilePath(templateName,genConfig,map.get("changeClassName").toString()); + Template template = engine.getTemplate("front/" + templateName + ".ftl"); + String filePath = getFrontFilePath(templateName, genConfig.getApiPath(), genConfig.getPath(), genMap.get("changeClassName").toString()); + assert filePath != null; File file = new File(filePath); // 如果非覆盖生成 - if(!genConfig.getCover() && FileUtil.exist(file)){ + if (!genConfig.getCover() && FileUtil.exist(file)) { continue; } // 生成代码 - genFile(file, template, map); + genFile(file, template, genMap); } } + // 获取模版数据 + private static Map getGenMap(List columnInfos, GenConfig genConfig) { + // 存储模版字段数据 + Map genMap = new HashMap<>(16); + // 接口别名 + genMap.put("apiAlias", genConfig.getApiAlias()); + // 包名称 + genMap.put("package", genConfig.getPack()); + // 模块名称 + genMap.put("moduleName", genConfig.getModuleName()); + // 作者 + genMap.put("author", genConfig.getAuthor()); + // 创建日期 + genMap.put("date", LocalDate.now().toString()); + // 表名 + genMap.put("tableName", genConfig.getTableName()); + // 大写开头的类名 + String className = StringUtils.toCapitalizeCamelCase(genConfig.getTableName()); + // 小写开头的类名 + String changeClassName = StringUtils.toCamelCase(genConfig.getTableName()); + // 判断是否去除表前缀 + if (StringUtils.isNotEmpty(genConfig.getPrefix())) { + className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(genConfig.getTableName(), genConfig.getPrefix())); + changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(genConfig.getTableName(), genConfig.getPrefix())); + changeClassName = StringUtils.uncapitalize(changeClassName); + } + // 保存类名 + genMap.put("className", className); + // 保存小写开头的类名 + genMap.put("changeClassName", changeClassName); + // 存在 Timestamp 字段 + genMap.put("hasTimestamp", false); + // 查询类中存在 Timestamp 字段 + genMap.put("queryHasTimestamp", false); + // 存在 BigDecimal 字段 + genMap.put("hasBigDecimal", false); + // 查询类中存在 BigDecimal 字段 + genMap.put("queryHasBigDecimal", false); + // 是否需要创建查询 + genMap.put("hasQuery", false); + // 自增主键 + genMap.put("auto", false); + // 存在字典 + genMap.put("hasDict", false); + // 存在日期注解 + genMap.put("hasDateAnnotation", false); + // 保存字段信息 + List> columns = new ArrayList<>(); + // 保存查询字段的信息 + List> queryColumns = new ArrayList<>(); + // 存储字典信息 + List dicts = new ArrayList<>(); + // 存储 between 信息 + List> betweens = new ArrayList<>(); + // 存储不为空的字段信息 + List> isNotNullColumns = new ArrayList<>(); + + for (ColumnInfo column : columnInfos) { + Map listMap = new HashMap<>(16); + // 字段描述 + listMap.put("remark", column.getRemark()); + // 字段类型 + listMap.put("columnKey", column.getKeyType()); + // 主键类型 + String colType = ColUtil.cloToJava(column.getColumnType()); + // 小写开头的字段名 + String changeColumnName = StringUtils.toCamelCase(column.getColumnName()); + // 大写开头的字段名 + String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getColumnName()); + if (PK.equals(column.getKeyType())) { + // 存储主键类型 + genMap.put("pkColumnType", colType); + // 存储小写开头的字段名 + genMap.put("pkChangeColName", changeColumnName); + // 存储大写开头的字段名 + genMap.put("pkCapitalColName", capitalColumnName); + } + // 是否存在 Timestamp 类型的字段 + if (TIMESTAMP.equals(colType)) { + genMap.put("hasTimestamp", true); + } + // 是否存在 BigDecimal 类型的字段 + if (BIGDECIMAL.equals(colType)) { + genMap.put("hasBigDecimal", true); + } + // 主键是否自增 + if (EXTRA.equals(column.getExtra())) { + genMap.put("auto", true); + } + // 主键存在字典 + if (StringUtils.isNotBlank(column.getDictName())) { + genMap.put("hasDict", true); + if(!dicts.contains(column.getDictName())) + dicts.add(column.getDictName()); + } + + // 存储字段类型 + listMap.put("columnType", colType); + // 存储字原始段名称 + listMap.put("columnName", column.getColumnName()); + // 不为空 + listMap.put("istNotNull", column.getNotNull()); + // 字段列表显示 + listMap.put("columnShow", column.getListShow()); + // 表单显示 + listMap.put("formShow", column.getFormShow()); + // 表单组件类型 + listMap.put("formType", StringUtils.isNotBlank(column.getFormType()) ? column.getFormType() : "Input"); + // 小写开头的字段名称 + listMap.put("changeColumnName", changeColumnName); + //大写开头的字段名称 + listMap.put("capitalColumnName", capitalColumnName); + // 字典名称 + listMap.put("dictName", column.getDictName()); + // 日期注解 + listMap.put("dateAnnotation", column.getDateAnnotation()); + if (StringUtils.isNotBlank(column.getDateAnnotation())) { + genMap.put("hasDateAnnotation", true); + } + // 添加非空字段信息 + if (column.getNotNull()) { + isNotNullColumns.add(listMap); + } + // 判断是否有查询,如有则把查询的字段set进columnQuery + if (!StringUtils.isBlank(column.getQueryType())) { + // 查询类型 + listMap.put("queryType", column.getQueryType()); + // 是否存在查询 + genMap.put("hasQuery", true); + if (TIMESTAMP.equals(colType)) { + // 查询中存储 Timestamp 类型 + genMap.put("queryHasTimestamp", true); + } + if (BIGDECIMAL.equals(colType)) { + // 查询中存储 BigDecimal 类型 + genMap.put("queryHasBigDecimal", true); + } + if ("between".equalsIgnoreCase(column.getQueryType())) { + betweens.add(listMap); + } else { + // 添加到查询列表中 + queryColumns.add(listMap); + } + } + // 添加到字段列表中 + columns.add(listMap); + } + // 保存字段列表 + genMap.put("columns", columns); + // 保存查询列表 + genMap.put("queryColumns", queryColumns); + // 保存字段列表 + genMap.put("dicts", dicts); + // 保存查询列表 + genMap.put("betweens", betweens); + // 保存非空字段信息 + genMap.put("isNotNullColumns", isNotNullColumns); + return genMap; + } + /** * 定义后端文件路径以及名称 */ - public static String getAdminFilePath(String templateName, GenConfig genConfig, String className) { - String projectPath = System.getProperty("user.dir") + File.separator + genConfig.getModuleName(); - String packagePath = projectPath + File.separator + "src" +File.separator+ "main" + File.separator + "java" + File.separator; + private static String getAdminFilePath(String templateName, GenConfig genConfig, String className, String rootPath) { + String projectPath = rootPath + File.separator + genConfig.getModuleName(); + String packagePath = projectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator; if (!ObjectUtils.isEmpty(genConfig.getPack())) { packagePath += genConfig.getPack().replace(".", File.separator) + File.separator; } @@ -192,7 +371,7 @@ public static String getAdminFilePath(String templateName, GenConfig genConfig, } if ("Dto".equals(templateName)) { - return packagePath + "service" + File.separator + "dto" + File.separator + className + "DTO.java"; + return packagePath + "service" + File.separator + "dto" + File.separator + className + "Dto.java"; } if ("QueryCriteria".equals(templateName)) { @@ -200,7 +379,7 @@ public static String getAdminFilePath(String templateName, GenConfig genConfig, } if ("Mapper".equals(templateName)) { - return packagePath + "service" + File.separator + "mapper" + File.separator + className + "Mapper.java"; + return packagePath + "service" + File.separator + "mapstruct" + File.separator + className + "Mapper.java"; } if ("Repository".equals(templateName)) { @@ -213,35 +392,30 @@ public static String getAdminFilePath(String templateName, GenConfig genConfig, /** * 定义前端文件路径以及名称 */ - public static String getFrontFilePath(String templateName, GenConfig genConfig, String apiName) { - String path = genConfig.getPath(); + private static String getFrontFilePath(String templateName, String apiPath, String path, String apiName) { if ("api".equals(templateName)) { - return genConfig.getApiPath() + File.separator + apiName + ".js"; + return apiPath + File.separator + apiName + ".js"; } if ("index".equals(templateName)) { - return path + File.separator + "index.vue"; + return path + File.separator + "index.vue"; } - if ("eForm".equals(templateName)) { - return path + File.separator + File.separator + "form.vue"; - } return null; } - public static void genFile(File file,Template template,Map map) throws IOException { + private static void genFile(File file, Template template, Map map) throws IOException { // 生成目标文件 Writer writer = null; try { FileUtil.touch(file); writer = new FileWriter(file); template.render(map, writer); - } catch (TemplateException e) { - throw new RuntimeException(e); - } catch (IOException e) { + } catch (TemplateException | IOException e) { throw new RuntimeException(e); } finally { + assert writer != null; writer.close(); } } diff --git a/eladmin-system/src/main/resources/generator.properties b/eladmin-generator/src/main/resources/gen.properties similarity index 91% rename from eladmin-system/src/main/resources/generator.properties rename to eladmin-generator/src/main/resources/gen.properties index b4d3d6447..e64d0608b 100644 --- a/eladmin-system/src/main/resources/generator.properties +++ b/eladmin-generator/src/main/resources/gen.properties @@ -1,4 +1,4 @@ -#���ݿ�����ת����java���� +# Database type to Java type tinyint=Integer smallint=Integer mediumint=Integer diff --git a/eladmin-generator/src/main/resources/template/admin/Controller.ftl b/eladmin-generator/src/main/resources/template/admin/Controller.ftl new file mode 100644 index 000000000..215fc953f --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/Controller.ftl @@ -0,0 +1,88 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.rest; + +import me.zhengjie.annotation.Log; +import ${package}.domain.${className}; +import ${package}.service.${className}Service; +import ${package}.service.dto.${className}QueryCriteria; +import org.springframework.data.domain.Pageable; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import io.swagger.annotations.*; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import me.zhengjie.utils.PageResult; +import ${package}.service.dto.${className}Dto; + +/** +* @website https://eladmin.vip +* @author ${author} +* @date ${date} +**/ +@RestController +@RequiredArgsConstructor +@Api(tags = "${apiAlias}") +@RequestMapping("/api/${changeClassName}") +public class ${className}Controller { + + private final ${className}Service ${changeClassName}Service; + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('${changeClassName}:list')") + public void export${className}(HttpServletResponse response, ${className}QueryCriteria criteria) throws IOException { + ${changeClassName}Service.download(${changeClassName}Service.queryAll(criteria), response); + } + + @GetMapping + @ApiOperation("查询${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:list')") + public ResponseEntity> query${className}(${className}QueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(${changeClassName}Service.queryAll(criteria,pageable),HttpStatus.OK); + } + + @PostMapping + @Log("新增${apiAlias}") + @ApiOperation("新增${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:add')") + public ResponseEntity create${className}(@Validated @RequestBody ${className} resources){ + ${changeClassName}Service.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @PutMapping + @Log("修改${apiAlias}") + @ApiOperation("修改${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:edit')") + public ResponseEntity update${className}(@Validated @RequestBody ${className} resources){ + ${changeClassName}Service.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @DeleteMapping + @Log("删除${apiAlias}") + @ApiOperation("删除${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:del')") + public ResponseEntity delete${className}(@ApiParam(value = "传ID数组[]") @RequestBody ${pkColumnType}[] ids) { + ${changeClassName}Service.deleteAll(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} \ No newline at end of file diff --git a/eladmin-generator/src/main/resources/template/admin/Dto.ftl b/eladmin-generator/src/main/resources/template/admin/Dto.ftl new file mode 100644 index 000000000..cc0f8f060 --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/Dto.ftl @@ -0,0 +1,57 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.service.dto; + +import lombok.Data; +<#if hasTimestamp> +import java.sql.Timestamp; + +<#if hasBigDecimal> +import java.math.BigDecimal; + +import java.io.Serializable; +<#if !auto && pkColumnType = 'Long'> +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.serializer.ToStringSerializer; + +import io.swagger.annotations.ApiModelProperty; + +/** +* @website https://eladmin.vip +* @description / +* @author ${author} +* @date ${date} +**/ +@Data +public class ${className}Dto implements Serializable { +<#if columns??> + <#list columns as column> + + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + <#if column.columnKey = 'PRI'> + <#if !auto && pkColumnType = 'Long'> + /** 防止精度丢失 */ + @JSONField(serializeUsing = ToStringSerializer.class) + + + private ${column.columnType} ${column.changeColumnName}; + + +} \ No newline at end of file diff --git a/eladmin-generator/src/main/resources/template/admin/Entity.ftl b/eladmin-generator/src/main/resources/template/admin/Entity.ftl new file mode 100644 index 000000000..0945eef3b --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/Entity.ftl @@ -0,0 +1,104 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.domain; + +import lombok.Data; +import cn.hutool.core.bean.BeanUtil; +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.copier.CopyOptions; +import javax.persistence.*; +<#if isNotNullColumns??> +import javax.validation.constraints.*; + +<#if hasDateAnnotation> +import javax.persistence.Entity; +import javax.persistence.Table; +import org.hibernate.annotations.*; + +<#if hasTimestamp> +import java.sql.Timestamp; + +<#if hasBigDecimal> +import java.math.BigDecimal; + +<#assign notBlankUsed = false> +<#assign notNullUsed = false> +<#if columns??> + <#list columns as column> + <#if column.istNotNull && column.columnKey != 'PRI'> + <#if column.columnType = 'String'> + <#assign notBlankUsed = true> + <#else> + <#assign notNullUsed = true> + + + + +<#if notBlankUsed> +import javax.validation.constraints.NotBlank; + +<#if notNullUsed> +import javax.validation.constraints.NotNull; + +import java.io.Serializable; + +/** +* @website https://eladmin.vip +* @description / +* @author ${author} +* @date ${date} +**/ +@Entity +@Data +@Table(name="${tableName}") +public class ${className} implements Serializable { +<#if columns??> + <#list columns as column> + + <#if column.columnKey = 'PRI'> + @Id + <#if auto> + @GeneratedValue(strategy = GenerationType.IDENTITY) + + + @Column(name = "`${column.columnName}`"<#if column.columnKey = 'UNI'>,unique = true<#if column.istNotNull && column.columnKey != 'PRI'>,nullable = false) + <#if column.istNotNull && column.columnKey != 'PRI'> + <#if column.columnType = 'String'> + @NotBlank + <#else> + @NotNull + + + <#if (column.dateAnnotation)?? && column.dateAnnotation != ''> + <#if column.dateAnnotation = 'CreationTimestamp'> + @CreationTimestamp + <#else> + @UpdateTimestamp + + + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + + + + public void copy(${className} source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-generator/src/main/resources/template/admin/Mapper.ftl b/eladmin-generator/src/main/resources/template/admin/Mapper.ftl new file mode 100644 index 000000000..3a29ccde7 --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/Mapper.ftl @@ -0,0 +1,32 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import ${package}.domain.${className}; +import ${package}.service.dto.${className}Dto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @website https://eladmin.vip +* @author ${author} +* @date ${date} +**/ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ${className}Mapper extends BaseMapper<${className}Dto, ${className}> { + +} \ No newline at end of file diff --git a/eladmin-generator/src/main/resources/template/admin/QueryCriteria.ftl b/eladmin-generator/src/main/resources/template/admin/QueryCriteria.ftl new file mode 100644 index 000000000..d6bd59256 --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/QueryCriteria.ftl @@ -0,0 +1,112 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.service.dto; + +import lombok.Data; +<#if queryHasTimestamp> +import java.sql.Timestamp; + +<#if queryHasBigDecimal> +import java.math.BigDecimal; + +<#if betweens??> +import java.util.List; + +<#if queryColumns??> +import me.zhengjie.annotation.Query; + +import io.swagger.annotations.ApiModelProperty; + +/** +* @website https://eladmin.vip +* @author ${author} +* @date ${date} +**/ +@Data +public class ${className}QueryCriteria{ +<#if queryColumns??> + <#list queryColumns as column> + +<#if column.queryType = '='> + /** 精确 */ + @Query + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = 'Like'> + /** 模糊 */ + @Query(type = Query.Type.INNER_LIKE) + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = '!='> + /** 不等于 */ + @Query(type = Query.Type.NOT_EQUAL) + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = 'NotNull'> + /** 不为空 */ + @Query(type = Query.Type.NOT_NULL) + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = '>='> + /** 大于等于 */ + @Query(type = Query.Type.GREATER_THAN) + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = '<='> + /** 小于等于 */ + @Query(type = Query.Type.LESS_THAN) + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + + + +<#if betweens??> + <#list betweens as column> + /** BETWEEN */ + @Query(type = Query.Type.BETWEEN) + private List<${column.columnType}> ${column.changeColumnName}; + + +} \ No newline at end of file diff --git a/eladmin-generator/src/main/resources/template/admin/Repository.ftl b/eladmin-generator/src/main/resources/template/admin/Repository.ftl new file mode 100644 index 000000000..426887167 --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/Repository.ftl @@ -0,0 +1,40 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.repository; + +import ${package}.domain.${className}; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @website https://eladmin.vip +* @author ${author} +* @date ${date} +**/ +public interface ${className}Repository extends JpaRepository<${className}, ${pkColumnType}>, JpaSpecificationExecutor<${className}> { +<#if columns??> + <#list columns as column> + <#if column.columnKey = 'UNI'> + /** + * 根据 ${column.capitalColumnName} 查询 + * @param ${column.columnName} / + * @return / + */ + ${className} findBy${column.capitalColumnName}(${column.columnType} ${column.columnName}); + + + +} \ No newline at end of file diff --git a/eladmin-generator/src/main/resources/template/admin/Service.ftl b/eladmin-generator/src/main/resources/template/admin/Service.ftl new file mode 100644 index 000000000..17f5ece92 --- /dev/null +++ b/eladmin-generator/src/main/resources/template/admin/Service.ftl @@ -0,0 +1,83 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package ${package}.service; + +import ${package}.domain.${className}; +import ${package}.service.dto.${className}Dto; +import ${package}.service.dto.${className}QueryCriteria; +import org.springframework.data.domain.Pageable; +import java.util.Map; +import java.util.List; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import me.zhengjie.utils.PageResult; + +/** +* @website https://eladmin.vip +* @description 服务接口 +* @author ${author} +* @date ${date} +**/ +public interface ${className}Service { + + /** + * 查询数据分页 + * @param criteria 条件 + * @param pageable 分页参数 + * @return Map + */ + PageResult<${className}Dto> queryAll(${className}QueryCriteria criteria, Pageable pageable); + + /** + * 查询所有数据不分页 + * @param criteria 条件参数 + * @return List<${className}Dto> + */ + List<${className}Dto> queryAll(${className}QueryCriteria criteria); + + /** + * 根据ID查询 + * @param ${pkChangeColName} ID + * @return ${className}Dto + */ + ${className}Dto findById(${pkColumnType} ${pkChangeColName}); + + /** + * 创建 + * @param resources / + */ + void create(${className} resources); + + /** + * 编辑 + * @param resources / + */ + void update(${className} resources); + + /** + * 多选删除 + * @param ids / + */ + void deleteAll(${pkColumnType}[] ids); + + /** + * 导出数据 + * @param all 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List<${className}Dto> all, HttpServletResponse response) throws IOException; +} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/ServiceImpl.ftl b/eladmin-generator/src/main/resources/template/admin/ServiceImpl.ftl similarity index 51% rename from eladmin-system/src/main/resources/template/generator/admin/ServiceImpl.ftl rename to eladmin-generator/src/main/resources/template/admin/ServiceImpl.ftl index efa5e89b4..784edd6b6 100644 --- a/eladmin-system/src/main/resources/template/generator/admin/ServiceImpl.ftl +++ b/eladmin-generator/src/main/resources/template/admin/ServiceImpl.ftl @@ -1,3 +1,18 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ package ${package}.service.impl; import ${package}.domain.${className}; @@ -11,16 +26,15 @@ import me.zhengjie.exception.EntityExistException; import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.FileUtil; +import lombok.RequiredArgsConstructor; import ${package}.repository.${className}Repository; import ${package}.service.${className}Service; -import ${package}.service.dto.${className}DTO; +import ${package}.service.dto.${className}Dto; import ${package}.service.dto.${className}QueryCriteria; -import ${package}.service.mapper.${className}Mapper; -import org.springframework.beans.factory.annotation.Autowired; +import ${package}.service.mapstruct.${className}Mapper; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; <#if !auto && pkColumnType = 'Long'> import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.IdUtil; @@ -32,42 +46,49 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import me.zhengjie.utils.PageUtil; import me.zhengjie.utils.QueryHelp; +import java.util.List; +import java.util.Map; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import me.zhengjie.utils.PageResult; /** +* @website https://eladmin.vip +* @description 服务实现 * @author ${author} * @date ${date} -*/ +**/ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class ${className}ServiceImpl implements ${className}Service { - @Autowired - private ${className}Repository ${changeClassName}Repository; - - @Autowired - private ${className}Mapper ${changeClassName}Mapper; + private final ${className}Repository ${changeClassName}Repository; + private final ${className}Mapper ${changeClassName}Mapper; @Override - public Object queryAll(${className}QueryCriteria criteria, Pageable pageable){ + public PageResult<${className}Dto> queryAll(${className}QueryCriteria criteria, Pageable pageable){ Page<${className}> page = ${changeClassName}Repository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); return PageUtil.toPage(page.map(${changeClassName}Mapper::toDto)); } @Override - public Object queryAll(${className}QueryCriteria criteria){ + public List<${className}Dto> queryAll(${className}QueryCriteria criteria){ return ${changeClassName}Mapper.toDto(${changeClassName}Repository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); } @Override - public ${className}DTO findById(${pkColumnType} ${pkChangeColName}) { - Optional<${className}> ${changeClassName} = ${changeClassName}Repository.findById(${pkChangeColName}); - ValidationUtil.isNull(${changeClassName},"${className}","${pkChangeColName}",${pkChangeColName}); - return ${changeClassName}Mapper.toDto(${changeClassName}.get()); + @Transactional + public ${className}Dto findById(${pkColumnType} ${pkChangeColName}) { + ${className} ${changeClassName} = ${changeClassName}Repository.findById(${pkChangeColName}).orElseGet(${className}::new); + ValidationUtil.isNull(${changeClassName}.get${pkCapitalColName}(),"${className}","${pkChangeColName}",${pkChangeColName}); + return ${changeClassName}Mapper.toDto(${changeClassName}); } @Override @Transactional(rollbackFor = Exception.class) - public ${className}DTO create(${className} resources) { + public void create(${className} resources) { <#if !auto && pkColumnType = 'Long'> Snowflake snowflake = IdUtil.createSnowflake(1, 1); resources.set${pkCapitalColName}(snowflake.nextId()); @@ -84,15 +105,14 @@ public class ${className}ServiceImpl implements ${className}Service { - return ${changeClassName}Mapper.toDto(${changeClassName}Repository.save(resources)); + ${changeClassName}Repository.save(resources); } @Override @Transactional(rollbackFor = Exception.class) public void update(${className} resources) { - Optional<${className}> optional${className} = ${changeClassName}Repository.findById(resources.get${pkCapitalColName}()); - ValidationUtil.isNull( optional${className},"${className}","id",resources.get${pkCapitalColName}()); - ${className} ${changeClassName} = optional${className}.get(); + ${className} ${changeClassName} = ${changeClassName}Repository.findById(resources.get${pkCapitalColName}()).orElseGet(${className}::new); + ValidationUtil.isNull( ${changeClassName}.get${pkCapitalColName}(),"${className}","id",resources.get${pkCapitalColName}()); <#if columns??> <#list columns as column> <#if column.columnKey = 'UNI'> @@ -111,8 +131,28 @@ public class ${className}ServiceImpl implements ${className}Service { } @Override - @Transactional(rollbackFor = Exception.class) - public void delete(${pkColumnType} ${pkChangeColName}) { - ${changeClassName}Repository.deleteById(${pkChangeColName}); + public void deleteAll(${pkColumnType}[] ids) { + for (${pkColumnType} ${pkChangeColName} : ids) { + ${changeClassName}Repository.deleteById(${pkChangeColName}); + } + } + + @Override + public void download(List<${className}Dto> all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (${className}Dto ${changeClassName} : all) { + Map map = new LinkedHashMap<>(); + <#list columns as column> + <#if column.columnKey != 'PRI'> + <#if column.remark != ''> + map.put("${column.remark}", ${changeClassName}.get${column.capitalColumnName}()); + <#else> + map.put(" ${column.changeColumnName}", ${changeClassName}.get${column.capitalColumnName}()); + + + + list.add(map); + } + FileUtil.downloadExcel(list, response); } } \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/front/api.ftl b/eladmin-generator/src/main/resources/template/front/api.ftl similarity index 69% rename from eladmin-system/src/main/resources/template/generator/front/api.ftl rename to eladmin-generator/src/main/resources/template/front/api.ftl index a39d3bc70..9587d0dff 100644 --- a/eladmin-system/src/main/resources/template/generator/front/api.ftl +++ b/eladmin-generator/src/main/resources/template/front/api.ftl @@ -8,10 +8,11 @@ export function add(data) { }) } -export function del(${pkChangeColName}) { +export function del(ids) { return request({ - url: 'api/${changeClassName}/' + ${pkChangeColName}, - method: 'delete' + url: 'api/${changeClassName}/', + method: 'delete', + data: ids }) } @@ -22,3 +23,5 @@ export function edit(data) { data }) } + +export default { add, edit, del } diff --git a/eladmin-generator/src/main/resources/template/front/index.ftl b/eladmin-generator/src/main/resources/template/front/index.ftl new file mode 100644 index 000000000..4b9111a9e --- /dev/null +++ b/eladmin-generator/src/main/resources/template/front/index.ftl @@ -0,0 +1,169 @@ +<#--noinspection ALL--> + + + + + diff --git a/eladmin-logging/pom.xml b/eladmin-logging/pom.xml index d899d23bd..db4808cb5 100644 --- a/eladmin-logging/pom.xml +++ b/eladmin-logging/pom.xml @@ -1,21 +1,20 @@ - + eladmin me.zhengjie - 2.1 + 2.7 4.0.0 eladmin-logging + 日志模块 me.zhengjie eladmin-common - 2.1 + 2.7 \ No newline at end of file diff --git a/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java b/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java new file mode 100644 index 000000000..590a1738d --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Zheng Jie + * @date 2018-11-24 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Log { + String value() default ""; +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/aop/log/Log.java b/eladmin-logging/src/main/java/me/zhengjie/aop/log/Log.java deleted file mode 100644 index 3b6f7e0ab..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/aop/log/Log.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.aop.log; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Zheng Jie - * @date 2018-11-24 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface Log { - String value() default ""; -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java b/eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java index 1fbf7cfa8..b091d7ba5 100644 --- a/eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java +++ b/eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java @@ -1,9 +1,23 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.aspect; import lombok.extern.slf4j.Slf4j; -import me.zhengjie.domain.Log; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.service.LogService; +import me.zhengjie.domain.SysLog; +import me.zhengjie.service.SysLogService; import me.zhengjie.utils.RequestHolder; import me.zhengjie.utils.SecurityUtils; import me.zhengjie.utils.StringUtils; @@ -14,9 +28,7 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; - import javax.servlet.http.HttpServletRequest; /** @@ -28,15 +40,18 @@ @Slf4j public class LogAspect { - @Autowired - private LogService logService; + private final SysLogService sysLogService; + + ThreadLocal currentTime = new ThreadLocal<>(); - private long currentTime = 0L; + public LogAspect(SysLogService sysLogService) { + this.sysLogService = sysLogService; + } /** * 配置切入点 */ - @Pointcut("@annotation(me.zhengjie.aop.log.Log)") + @Pointcut("@annotation(me.zhengjie.annotation.Log)") public void logPointcut() { // 该方法无方法体,主要为了让同类中其他方法使用此切入点 } @@ -48,11 +63,13 @@ public void logPointcut() { */ @Around("logPointcut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { - Object result = null; - currentTime = System.currentTimeMillis(); + Object result; + currentTime.set(System.currentTimeMillis()); result = joinPoint.proceed(); - Log log = new Log("INFO",System.currentTimeMillis() - currentTime); - logService.save(getUsername(), StringUtils.getIP(RequestHolder.getHttpServletRequest()),joinPoint, log); + SysLog sysLog = new SysLog("INFO",System.currentTimeMillis() - currentTime.get()); + currentTime.remove(); + HttpServletRequest request = RequestHolder.getHttpServletRequest(); + sysLogService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request),joinPoint, sysLog); return result; } @@ -64,14 +81,20 @@ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { */ @AfterThrowing(pointcut = "logPointcut()", throwing = "e") public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { - Log log = new Log("ERROR",System.currentTimeMillis() - currentTime); - log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes()); - logService.save(getUsername(), StringUtils.getIP(RequestHolder.getHttpServletRequest()), (ProceedingJoinPoint)joinPoint, log); + SysLog sysLog = new SysLog("ERROR",System.currentTimeMillis() - currentTime.get()); + currentTime.remove(); + sysLog.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes()); + HttpServletRequest request = RequestHolder.getHttpServletRequest(); + sysLogService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request), (ProceedingJoinPoint)joinPoint, sysLog); } + /** + * 获取用户名 + * @return / + */ public String getUsername() { try { - return SecurityUtils.getUsername(); + return SecurityUtils.getCurrentUsername(); }catch (Exception e){ return ""; } diff --git a/eladmin-logging/src/main/java/me/zhengjie/domain/Log.java b/eladmin-logging/src/main/java/me/zhengjie/domain/Log.java deleted file mode 100644 index a7146c015..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/domain/Log.java +++ /dev/null @@ -1,82 +0,0 @@ -package me.zhengjie.domain; - -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.CreationTimestamp; -import javax.persistence.*; -import java.io.Serializable; -import java.sql.Timestamp; - -/** - * @author Zheng Jie - * @date 2018-11-24 - */ -@Entity -@Data -@Table(name = "log") -@NoArgsConstructor -public class Log implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * 操作用户 - */ - private String username; - - /** - * 描述 - */ - private String description; - - /** - * 方法名 - */ - private String method; - - /** - * 参数 - */ - @Column(columnDefinition = "text") - private String params; - - /** - * 日志类型 - */ - @Column(name = "log_type") - private String logType; - - /** - * 请求ip - */ - @Column(name = "request_ip") - private String requestIp; - - @Column(name = "address") - private String address; - - /** - * 请求耗时 - */ - private Long time; - - /** - * 异常详细 - */ - @Column(name = "exception_detail", columnDefinition = "text") - private byte[] exceptionDetail; - - /** - * 创建日期 - */ - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; - - public Log(String logType, Long time) { - this.logType = logType; - this.time = time; - } -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/domain/SysLog.java b/eladmin-logging/src/main/java/me/zhengjie/domain/SysLog.java new file mode 100644 index 000000000..f15a333c8 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/domain/SysLog.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import javax.persistence.*; +import java.io.Serializable; +import java.sql.Timestamp; + +/** + * @author Zheng Jie + * @date 2018-11-24 + */ +@Entity +@Getter +@Setter +@Table(name = "sys_log") +@NoArgsConstructor +public class SysLog implements Serializable { + + @Id + @Column(name = "log_id") + @ApiModelProperty(value = "ID") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ApiModelProperty(value = "操作用户") + private String username; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "方法名") + private String method; + + @ApiModelProperty(value = "参数") + private String params; + + @ApiModelProperty(value = "日志类型") + private String logType; + + @ApiModelProperty(value = "请求ip") + private String requestIp; + + @ApiModelProperty(value = "地址") + private String address; + + @ApiModelProperty(value = "浏览器") + private String browser; + + @ApiModelProperty(value = "请求耗时") + private Long time; + + @ApiModelProperty(value = "异常详细") + private byte[] exceptionDetail; + + /** 创建日期 */ + @CreationTimestamp + @ApiModelProperty(value = "创建日期:yyyy-MM-dd HH:mm:ss") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private Timestamp createTime; + + public SysLog(String logType, Long time) { + this.logType = logType; + this.time = time; + } +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java b/eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java index e557f8c61..5a68c1c5d 100644 --- a/eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java +++ b/eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java @@ -1,8 +1,24 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.repository; -import me.zhengjie.domain.Log; +import me.zhengjie.domain.SysLog; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -11,22 +27,13 @@ * @date 2018-11-24 */ @Repository -public interface LogRepository extends JpaRepository, JpaSpecificationExecutor { - - /** - * 获取一个时间段的IP记录 - * @param date1 - * @param date2 - * @return - */ - @Query(value = "select count(*) FROM (select request_ip FROM log where create_time between ?1 and ?2 GROUP BY request_ip) as s",nativeQuery = true) - Long findIp(String date1, String date2); +public interface LogRepository extends JpaRepository, JpaSpecificationExecutor { /** - * findExceptionById - * @param id - * @return + * 根据日志类型删除信息 + * @param logType 日志类型 */ - @Query(value = "select exception_detail FROM log where id = ?1",nativeQuery = true) - String findExceptionById(Long id); + @Modifying + @Query(value = "delete from sys_log where log_type = ?1", nativeQuery = true) + void deleteByLogType(String logType); } diff --git a/eladmin-logging/src/main/java/me/zhengjie/rest/LogController.java b/eladmin-logging/src/main/java/me/zhengjie/rest/LogController.java deleted file mode 100644 index 051c071da..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/rest/LogController.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.zhengjie.rest; - -import me.zhengjie.service.LogService; -import me.zhengjie.service.dto.LogQueryCriteria; -import me.zhengjie.utils.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Zheng Jie - * @date 2018-11-24 - */ -@RestController -@RequestMapping("api") -public class LogController { - - @Autowired - private LogService logService; - - @GetMapping(value = "/logs") - @PreAuthorize("hasAnyRole('ADMIN')") - public ResponseEntity getLogs(LogQueryCriteria criteria, Pageable pageable){ - criteria.setLogType("INFO"); - return new ResponseEntity(logService.queryAll(criteria,pageable), HttpStatus.OK); - } - - @GetMapping(value = "/logs/user") - public ResponseEntity getUserLogs(LogQueryCriteria criteria, Pageable pageable){ - criteria.setLogType("INFO"); - criteria.setBlurry(SecurityUtils.getUsername()); - return new ResponseEntity(logService.queryAllByUser(criteria,pageable), HttpStatus.OK); - } - - @GetMapping(value = "/logs/error") - @PreAuthorize("hasAnyRole('ADMIN')") - public ResponseEntity getErrorLogs(LogQueryCriteria criteria, Pageable pageable){ - criteria.setLogType("ERROR"); - return new ResponseEntity(logService.queryAll(criteria,pageable), HttpStatus.OK); - } - - @GetMapping(value = "/logs/error/{id}") - @PreAuthorize("hasAnyRole('ADMIN')") - public ResponseEntity getErrorLogs(@PathVariable Long id){ - return new ResponseEntity(logService.findByErrDetail(id), HttpStatus.OK); - } -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/rest/SysLogController.java b/eladmin-logging/src/main/java/me/zhengjie/rest/SysLogController.java new file mode 100644 index 000000000..59dc4f802 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/rest/SysLogController.java @@ -0,0 +1,111 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.service.SysLogService; +import me.zhengjie.service.dto.SysLogQueryCriteria; +import me.zhengjie.service.dto.SysLogSmallDto; +import me.zhengjie.utils.PageResult; +import me.zhengjie.utils.SecurityUtils; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Zheng Jie + * @date 2018-11-24 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/logs") +@Api(tags = "系统:日志管理") +public class SysLogController { + + private final SysLogService sysLogService; + + @Log("导出数据") + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check()") + public void exportLog(HttpServletResponse response, SysLogQueryCriteria criteria) throws IOException { + criteria.setLogType("INFO"); + sysLogService.download(sysLogService.queryAll(criteria), response); + } + + @Log("导出错误数据") + @ApiOperation("导出错误数据") + @GetMapping(value = "/error/download") + @PreAuthorize("@el.check()") + public void exportErrorLog(HttpServletResponse response, SysLogQueryCriteria criteria) throws IOException { + criteria.setLogType("ERROR"); + sysLogService.download(sysLogService.queryAll(criteria), response); + } + @GetMapping + @ApiOperation("日志查询") + @PreAuthorize("@el.check()") + public ResponseEntity queryLog(SysLogQueryCriteria criteria, Pageable pageable){ + criteria.setLogType("INFO"); + return new ResponseEntity<>(sysLogService.queryAll(criteria,pageable), HttpStatus.OK); + } + + @GetMapping(value = "/user") + @ApiOperation("用户日志查询") + public ResponseEntity> queryUserLog(SysLogQueryCriteria criteria, Pageable pageable){ + criteria.setLogType("INFO"); + criteria.setUsername(SecurityUtils.getCurrentUsername()); + return new ResponseEntity<>(sysLogService.queryAllByUser(criteria,pageable), HttpStatus.OK); + } + + @GetMapping(value = "/error") + @ApiOperation("错误日志查询") + @PreAuthorize("@el.check()") + public ResponseEntity queryErrorLog(SysLogQueryCriteria criteria, Pageable pageable){ + criteria.setLogType("ERROR"); + return new ResponseEntity<>(sysLogService.queryAll(criteria,pageable), HttpStatus.OK); + } + + @GetMapping(value = "/error/{id}") + @ApiOperation("日志异常详情查询") + @PreAuthorize("@el.check()") + public ResponseEntity queryErrorLogDetail(@PathVariable Long id){ + return new ResponseEntity<>(sysLogService.findByErrDetail(id), HttpStatus.OK); + } + @DeleteMapping(value = "/del/error") + @Log("删除所有ERROR日志") + @ApiOperation("删除所有ERROR日志") + @PreAuthorize("@el.check()") + public ResponseEntity delAllErrorLog(){ + sysLogService.delAllByError(); + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping(value = "/del/info") + @Log("删除所有INFO日志") + @ApiOperation("删除所有INFO日志") + @PreAuthorize("@el.check()") + public ResponseEntity delAllInfoLog(){ + sysLogService.delAllByInfo(); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/LogService.java b/eladmin-logging/src/main/java/me/zhengjie/service/LogService.java deleted file mode 100644 index 3eb69de90..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/LogService.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.zhengjie.service; - -import me.zhengjie.domain.Log; -import me.zhengjie.service.dto.LogQueryCriteria; -import org.aspectj.lang.ProceedingJoinPoint; -import org.springframework.data.domain.Pageable; -import org.springframework.scheduling.annotation.Async; - -/** - * @author Zheng Jie - * @date 2018-11-24 - */ -public interface LogService { - - /** - * queryAll - * @param criteria - * @param pageable - * @return - */ - Object queryAll(LogQueryCriteria criteria, Pageable pageable); - - /** - * queryAllByUser - * @param criteria - * @param pageable - * @return - */ - Object queryAllByUser(LogQueryCriteria criteria, Pageable pageable); - - /** - * 新增日志 - * @param username - * @param ip - * @param joinPoint - * @param log - */ - @Async - void save(String username, String ip, ProceedingJoinPoint joinPoint, Log log); - - /** - * 查询异常详情 - * @param id - * @return - */ - Object findByErrDetail(Long id); -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/SysLogService.java b/eladmin-logging/src/main/java/me/zhengjie/service/SysLogService.java new file mode 100644 index 000000000..c4a873fe6 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/SysLogService.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service; + +import me.zhengjie.domain.SysLog; +import me.zhengjie.service.dto.SysLogQueryCriteria; +import me.zhengjie.service.dto.SysLogSmallDto; +import me.zhengjie.utils.PageResult; +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Async; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * @author Zheng Jie + * @date 2018-11-24 + */ +public interface SysLogService { + + /** + * 分页查询 + * @param criteria 查询条件 + * @param pageable 分页参数 + * @return / + */ + Object queryAll(SysLogQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria 查询条件 + * @return / + */ + List queryAll(SysLogQueryCriteria criteria); + + /** + * 查询用户日志 + * @param criteria 查询条件 + * @param pageable 分页参数 + * @return - + */ + PageResult queryAllByUser(SysLogQueryCriteria criteria, Pageable pageable); + + /** + * 保存日志数据 + * @param username 用户 + * @param browser 浏览器 + * @param ip 请求IP + * @param joinPoint / + * @param sysLog 日志实体 + */ + @Async + void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog); + + /** + * 查询异常详情 + * @param id 日志ID + * @return Object + */ + Object findByErrDetail(Long id); + + /** + * 导出日志 + * @param sysLogs 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List sysLogs, HttpServletResponse response) throws IOException; + + /** + * 删除所有错误日志 + */ + void delAllByError(); + + /** + * 删除所有INFO日志 + */ + void delAllByInfo(); +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogErrorDTO.java b/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogErrorDTO.java deleted file mode 100644 index f23ae95fb..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogErrorDTO.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.zhengjie.service.dto; - -import lombok.Data; -import java.io.Serializable; -import java.sql.Timestamp; - -/** -* @author Zheng Jie -* @date 2019-5-22 -*/ -@Data -public class LogErrorDTO implements Serializable { - - private Long id; - - /** - * 操作用户 - */ - private String username; - - /** - * 描述 - */ - private String description; - - /** - * 方法名 - */ - private String method; - - /** - * 参数 - */ - private String params; - - /** - * 请求ip - */ - private String requestIp; - - private String address; - - - /** - * 创建日期 - */ - private Timestamp createTime; -} \ No newline at end of file diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogQueryCriteria.java b/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogQueryCriteria.java deleted file mode 100644 index 6b52cc778..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogQueryCriteria.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.zhengjie.service.dto; - -import lombok.Data; -import me.zhengjie.annotation.Query; - -/** - * 日志查询类 - * @author Zheng Jie - * @date 2019-6-4 09:23:07 - */ -@Data -public class LogQueryCriteria { - - // 多字段模糊 - @Query(blurry = "username,description,address,requestIp,method,params") - private String blurry; - - @Query - private String logType; -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogSmallDTO.java b/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogSmallDTO.java deleted file mode 100644 index 33b6abfdf..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/dto/LogSmallDTO.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.zhengjie.service.dto; - -import lombok.Data; - -import java.io.Serializable; -import java.sql.Timestamp; - -/** - * @author Zheng Jie - * @date 2019-5-22 - */ -@Data -public class LogSmallDTO implements Serializable { - - /** - * 描述 - */ - private String description; - - /** - * 请求ip - */ - private String requestIp; - - /** - * 请求耗时 - */ - private Long time; - - private String address; - - /** - * 创建日期 - */ - private Timestamp createTime; -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogErrorDto.java b/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogErrorDto.java new file mode 100644 index 000000000..fd66068a2 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogErrorDto.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +import java.sql.Timestamp; + +/** +* @author Zheng Jie +* @date 2019-5-22 +*/ +@Data +public class SysLogErrorDto implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "方法") + private String method; + + @ApiModelProperty(value = "参数") + private String params; + + @ApiModelProperty(value = "浏览器") + private String browser; + + @ApiModelProperty(value = "请求ip") + private String requestIp; + + @ApiModelProperty(value = "地址") + private String address; + + @ApiModelProperty(value = "创建时间") + private Timestamp createTime; +} \ No newline at end of file diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogQueryCriteria.java b/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogQueryCriteria.java new file mode 100644 index 000000000..769919c8d --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogQueryCriteria.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** + * 日志查询类 + * @author Zheng Jie + * @date 2019-6-4 09:23:07 + */ +@Data +public class SysLogQueryCriteria { + + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "username,description,address,requestIp,method,params") + private String blurry; + + @Query + @ApiModelProperty(value = "用户名") + private String username; + + @Query + @ApiModelProperty(value = "日志类型") + private String logType; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogSmallDto.java b/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogSmallDto.java new file mode 100644 index 000000000..55894dbd7 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/dto/SysLogSmallDto.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +import java.sql.Timestamp; + +/** + * @author Zheng Jie + * @date 2019-5-22 + */ +@Data +public class SysLogSmallDto implements Serializable { + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "请求IP") + private String requestIp; + + @ApiModelProperty(value = "耗时") + private Long time; + + @ApiModelProperty(value = "地址") + private String address; + + @ApiModelProperty(value = "浏览器") + private String browser; + + @ApiModelProperty(value = "创建时间") + private Timestamp createTime; +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/impl/LogServiceImpl.java b/eladmin-logging/src/main/java/me/zhengjie/service/impl/LogServiceImpl.java deleted file mode 100644 index 4d39206fc..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/impl/LogServiceImpl.java +++ /dev/null @@ -1,107 +0,0 @@ -package me.zhengjie.service.impl; - -import cn.hutool.core.lang.Dict; -import cn.hutool.json.JSONObject; -import me.zhengjie.domain.Log; -import me.zhengjie.repository.LogRepository; -import me.zhengjie.service.LogService; -import me.zhengjie.service.dto.LogQueryCriteria; -import me.zhengjie.service.mapper.LogErrorMapper; -import me.zhengjie.service.mapper.LogSmallMapper; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.StringUtils; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import java.lang.reflect.Method; - -/** - * @author Zheng Jie - * @date 2018-11-24 - */ -@Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class LogServiceImpl implements LogService { - - @Autowired - private LogRepository logRepository; - - @Autowired - private LogErrorMapper logErrorMapper; - - @Autowired - private LogSmallMapper logSmallMapper; - - private final String LOGINPATH = "login"; - - @Override - public Object queryAll(LogQueryCriteria criteria, Pageable pageable){ - Page page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)),pageable); - if ("ERROR".equals(criteria.getLogType())) { - return PageUtil.toPage(page.map(logErrorMapper::toDto)); - } - return page; - } - - @Override - public Object queryAllByUser(LogQueryCriteria criteria, Pageable pageable) { - Page page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)),pageable); - return PageUtil.toPage(page.map(logSmallMapper::toDto)); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void save(String username, String ip, ProceedingJoinPoint joinPoint, Log log){ - - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - Method method = signature.getMethod(); - me.zhengjie.aop.log.Log aopLog = method.getAnnotation(me.zhengjie.aop.log.Log.class); - - // 描述 - if (log != null) { - log.setDescription(aopLog.value()); - } - - // 方法路径 - String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()"; - - String params = "{"; - //参数值 - Object[] argValues = joinPoint.getArgs(); - //参数名称 - String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); - if(argValues != null){ - for (int i = 0; i < argValues.length; i++) { - params += " " + argNames[i] + ": " + argValues[i]; - } - } - - // 获取IP地址 - log.setRequestIp(ip); - - if(LOGINPATH.equals(signature.getName())){ - try { - JSONObject jsonObject = new JSONObject(argValues[0]); - username = jsonObject.get("username").toString(); - }catch (Exception e){ - e.printStackTrace(); - } - } - log.setAddress(StringUtils.getCityInfo(log.getRequestIp())); - log.setMethod(methodName); - log.setUsername(username); - log.setParams(params + " }"); - logRepository.save(log); - } - - @Override - public Object findByErrDetail(Long id) { - return Dict.create().set("exception",logRepository.findExceptionById(id)); - } -} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java b/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java new file mode 100644 index 000000000..455f7b867 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java @@ -0,0 +1,201 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.impl; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.RequiredArgsConstructor; +import me.zhengjie.domain.SysLog; +import me.zhengjie.repository.LogRepository; +import me.zhengjie.service.SysLogService; +import me.zhengjie.service.dto.SysLogQueryCriteria; +import me.zhengjie.service.dto.SysLogSmallDto; +import me.zhengjie.service.mapstruct.LogErrorMapper; +import me.zhengjie.service.mapstruct.LogSmallMapper; +import me.zhengjie.utils.*; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; + +/** + * @author Zheng Jie + * @date 2018-11-24 + */ +@Service +@RequiredArgsConstructor +public class SysLogServiceImpl implements SysLogService { + + private final LogRepository logRepository; + private final LogErrorMapper logErrorMapper; + private final LogSmallMapper logSmallMapper; + // 定义敏感字段常量数组 + private static final String[] SENSITIVE_KEYS = {"password"}; + + @Override + public Object queryAll(SysLogQueryCriteria criteria, Pageable pageable) { + Page page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable); + String status = "ERROR"; + if (status.equals(criteria.getLogType())) { + return PageUtil.toPage(page.map(logErrorMapper::toDto)); + } + return PageUtil.toPage(page); + } + + @Override + public List queryAll(SysLogQueryCriteria criteria) { + return logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb))); + } + + @Override + public PageResult queryAllByUser(SysLogQueryCriteria criteria, Pageable pageable) { + Page page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable); + return PageUtil.toPage(page.map(logSmallMapper::toDto)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog) { + if (sysLog == null) { + throw new IllegalArgumentException("Log 不能为 null!"); + } + + // 获取方法签名 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + me.zhengjie.annotation.Log aopLog = method.getAnnotation(me.zhengjie.annotation.Log.class); + + // 方法路径 + String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()"; + + // 获取参数 + JSONObject params = getParameter(method, joinPoint.getArgs()); + + // 填充基本信息 + sysLog.setRequestIp(ip); + sysLog.setAddress(StringUtils.getCityInfo(sysLog.getRequestIp())); + sysLog.setMethod(methodName); + sysLog.setUsername(username); + sysLog.setParams(JSON.toJSONString(params)); + sysLog.setBrowser(browser); + sysLog.setDescription(aopLog.value()); + + // 如果没有获取到用户名,尝试从参数中获取 + if(StringUtils.isBlank(sysLog.getUsername())){ + sysLog.setUsername(params.getString("username")); + } + + // 保存 + logRepository.save(sysLog); + } + + /** + * 根据方法和传入的参数获取请求参数 + */ + private JSONObject getParameter(Method method, Object[] args) { + JSONObject params = new JSONObject(); + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + // 过滤掉 MultiPartFile + if (args[i] instanceof MultipartFile) { + continue; + } + // 过滤掉 HttpServletResponse + if (args[i] instanceof HttpServletResponse) { + continue; + } + // 过滤掉 HttpServletRequest + if (args[i] instanceof HttpServletRequest) { + continue; + } + // 将RequestBody注解修饰的参数作为请求参数 + RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); + if (requestBody != null) { + // [el-async-1] ERROR o.s.a.i.SimpleAsyncUncaughtExceptionHandler - Unexpected exception occurred invoking async method: public void me.zhengjie.service.impl.SysLogServiceImpl.save(java.lang.String,java.lang.String,java.lang.String,org.aspectj.lang.ProceedingJoinPoint,me.zhengjie.domain.SysLog) + // java.lang.ClassCastException: com.alibaba.fastjson2.JSONArray cannot be cast to com.alibaba.fastjson2.JSONObject + Object json = JSON.toJSON(args[i]); + if (json instanceof JSONArray) { + params.put("reqBodyList", json); + } else { + params.putAll((JSONObject) json); + } + } else { + String key = parameters[i].getName(); + params.put(key, args[i]); + } + } + // 遍历敏感字段数组并替换值 + Set keys = params.keySet(); + for (String key : SENSITIVE_KEYS) { + if (keys.contains(key)) { + params.put(key, "******"); + } + } + // 返回参数 + return params; + } + + @Override + public Object findByErrDetail(Long id) { + SysLog sysLog = logRepository.findById(id).orElseGet(SysLog::new); + ValidationUtil.isNull(sysLog.getId(), "Log", "id", id); + byte[] details = sysLog.getExceptionDetail(); + return Dict.create().set("exception", new String(ObjectUtil.isNotNull(details) ? details : "".getBytes())); + } + + @Override + public void download(List sysLogs, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SysLog sysLog : sysLogs) { + Map map = new LinkedHashMap<>(); + map.put("用户名", sysLog.getUsername()); + map.put("IP", sysLog.getRequestIp()); + map.put("IP来源", sysLog.getAddress()); + map.put("描述", sysLog.getDescription()); + map.put("浏览器", sysLog.getBrowser()); + map.put("请求耗时/毫秒", sysLog.getTime()); + map.put("异常详情", new String(ObjectUtil.isNotNull(sysLog.getExceptionDetail()) ? sysLog.getExceptionDetail() : "".getBytes())); + map.put("创建日期", sysLog.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delAllByError() { + logRepository.deleteByLogType("ERROR"); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delAllByInfo() { + logRepository.deleteByLogType("INFO"); + } +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/mapper/LogErrorMapper.java b/eladmin-logging/src/main/java/me/zhengjie/service/mapper/LogErrorMapper.java deleted file mode 100644 index a2fc4b871..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/mapper/LogErrorMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.service.mapper; - -import me.zhengjie.domain.Log; -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.service.dto.LogErrorDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2019-5-22 - */ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface LogErrorMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/mapper/LogSmallMapper.java b/eladmin-logging/src/main/java/me/zhengjie/service/mapper/LogSmallMapper.java deleted file mode 100644 index 8c6d2c633..000000000 --- a/eladmin-logging/src/main/java/me/zhengjie/service/mapper/LogSmallMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.service.mapper; - -import me.zhengjie.domain.Log; -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.service.dto.LogSmallDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2019-5-22 - */ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface LogSmallMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogErrorMapper.java b/eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogErrorMapper.java new file mode 100644 index 000000000..80e5e2f15 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogErrorMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.domain.SysLog; +import me.zhengjie.service.dto.SysLogErrorDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2019-5-22 + */ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface LogErrorMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogSmallMapper.java b/eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogSmallMapper.java new file mode 100644 index 000000000..284970368 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogSmallMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.domain.SysLog; +import me.zhengjie.service.dto.SysLogSmallDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2019-5-22 + */ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface LogSmallMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-system/pom.xml b/eladmin-system/pom.xml index 62691647b..667dacb62 100644 --- a/eladmin-system/pom.xml +++ b/eladmin-system/pom.xml @@ -1,25 +1,27 @@ - + eladmin me.zhengjie - 2.1 + 2.7 4.0.0 eladmin-system + 核心模块 - 0.9.1 + 0.11.5 + + 5.8.0 + me.zhengjie eladmin-generator - 2.1 + 2.7 me.zhengjie @@ -28,27 +30,57 @@ + me.zhengjie eladmin-tools - 2.1 + 2.7 - + + + org.springframework.boot + spring-boot-starter-quartz + + + io.jsonwebtoken - jjwt + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson ${jjwt.version} - + - org.quartz-scheduler - quartz + ch.ethz.ganymed + ganymed-ssh2 + build210 + + + com.jcraft + jsch + 0.1.55 + + + com.github.oshi + oshi-core + 6.6.5 + + @@ -65,4 +97,4 @@ - \ No newline at end of file + diff --git a/eladmin-system/src/main/java/me/zhengjie/AppRun.java b/eladmin-system/src/main/java/me/zhengjie/AppRun.java index 1a14e9ebb..441d124c1 100644 --- a/eladmin-system/src/main/java/me/zhengjie/AppRun.java +++ b/eladmin-system/src/main/java/me/zhengjie/AppRun.java @@ -1,27 +1,74 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie; -import me.zhengjie.utils.SpringContextHolder; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.rest.AnonymousGetMapping; +import me.zhengjie.utils.SpringBeanHolder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.ApplicationPidFileWriter; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.bind.annotation.RestController; /** + * 开启审计功能 -> @EnableJpaAuditing + * * @author Zheng Jie * @date 2018/11/15 9:20:19 */ +@Slf4j @EnableAsync +@RestController +@Api(hidden = true) @SpringBootApplication @EnableTransactionManagement +@EnableJpaAuditing(auditorAwareRef = "auditorAware") public class AppRun { public static void main(String[] args) { - SpringApplication.run(AppRun.class, args); + SpringApplication springApplication = new SpringApplication(AppRun.class); + // 监控应用的PID,启动时可指定PID路径:--spring.pid.file=/home/eladmin/app.pid + // 或者在 application.yml 添加文件路径,方便 kill,kill `cat /home/eladmin/app.pid` + springApplication.addListeners(new ApplicationPidFileWriter()); + ConfigurableApplicationContext context = springApplication.run(args); + String port = context.getEnvironment().getProperty("server.port"); + log.info("---------------------------------------------"); + log.info("Local: http://localhost:{}", port); + log.info("Swagger: http://localhost:{}/doc.html", port); + log.info("---------------------------------------------"); } @Bean - public SpringContextHolder springContextHolder() { - return new SpringContextHolder(); + public SpringBeanHolder springContextHolder() { + return new SpringBeanHolder(); + } + + /** + * 访问首页提示 + * + * @return / + */ + @AnonymousGetMapping("/") + public String index() { + return "Backend service started successfully"; } } diff --git a/eladmin-system/src/main/java/me/zhengjie/config/ConfigurerAdapter.java b/eladmin-system/src/main/java/me/zhengjie/config/ConfigurerAdapter.java deleted file mode 100644 index 24d0b831a..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/config/ConfigurerAdapter.java +++ /dev/null @@ -1,61 +0,0 @@ -package me.zhengjie.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -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.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.util.List; - -/** - * WebMvcConfigurer - * - * @author Zheng Jie - * @date 2018-11-30 - */ -@Configuration -@EnableWebMvc -public class ConfigurerAdapter implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowCredentials(true) - .allowedHeaders("*") - .allowedOrigins("*") - .allowedMethods("GET","POST","PUT","DELETE"); - - } - -// 可解决Long 类型在 前端精度丢失的问题, 如不想全局 直接添加注解 @JsonSerialize(using= ToStringSerializer.class) 到相应的字段 - -// @Override -// public void configureMessageConverters(List> converters) { -// -// MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = -// new MappingJackson2HttpMessageConverter(); -// -// ObjectMapper objectMapper = new ObjectMapper(); -// SimpleModule simpleModule = new SimpleModule(); -// simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance); -// simpleModule.addSerializer(Long.class, ToStringSerializer.instance); -// simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); -// objectMapper.registerModule(simpleModule); -// jackson2HttpMessageConverter.setObjectMapper(objectMapper); -// converters.add(jackson2HttpMessageConverter); -// converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); -// } - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/config/DataScope.java b/eladmin-system/src/main/java/me/zhengjie/config/DataScope.java deleted file mode 100644 index 0ae0ff071..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/config/DataScope.java +++ /dev/null @@ -1,88 +0,0 @@ -package me.zhengjie.config; - -import me.zhengjie.modules.system.domain.Dept; -import me.zhengjie.modules.system.service.DeptService; -import me.zhengjie.modules.system.service.RoleService; -import me.zhengjie.modules.system.service.UserService; -import me.zhengjie.modules.system.service.dto.DeptDTO; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; -import me.zhengjie.modules.system.service.dto.UserDTO; -import me.zhengjie.utils.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * 数据权限配置 - * @author Zheng Jie - * @date 2019-4-1 - */ -@Component -public class DataScope { - - private final String[] scopeType = {"全部","本级","自定义"}; - - @Autowired - private UserService userService; - - @Autowired - private RoleService roleService; - - @Autowired - private DeptService deptService; - - public Set getDeptIds() { - - UserDTO user = userService.findByName(SecurityUtils.getUsername()); - - // 用于存储部门id - Set deptIds = new HashSet<>(); - - // 查询用户角色 - List roleSet = roleService.findByUsers_Id(user.getId()); - - for (RoleSmallDTO role : roleSet) { - - if (scopeType[0].equals(role.getDataScope())) { - return new HashSet<>() ; - } - - // 存储本级的数据权限 - if (scopeType[1].equals(role.getDataScope())) { - deptIds.add(user.getDept().getId()); - } - - // 存储自定义的数据权限 - if (scopeType[2].equals(role.getDataScope())) { - Set depts = deptService.findByRoleIds(role.getId()); - for (Dept dept : depts) { - deptIds.add(dept.getId()); - List deptChildren = deptService.findByPid(dept.getId()); - if (deptChildren != null && deptChildren.size() != 0) { - deptIds.addAll(getDeptChildren(deptChildren)); - } - } - } - } - return deptIds; - } - - - public List getDeptChildren(List deptList) { - List list = new ArrayList<>(); - deptList.forEach(dept -> { - if (dept!=null && dept.getEnabled()){ - List depts = deptService.findByPid(dept.getId()); - if(deptList!=null && deptList.size()!=0){ - list.addAll(getDeptChildren(depts)); - } - list.add(dept.getId()); - } - } - ); - return list; - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/App.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/App.java new file mode 100644 index 000000000..f8b01990b --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/App.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.domain; + +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import java.io.Serializable; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Entity +@Getter +@Setter +@Table(name="mnt_app") +public class App extends BaseEntity implements Serializable { + + @Id + @Column(name = "app_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "端口") + private int port; + + @ApiModelProperty(value = "上传路径") + private String uploadPath; + + @ApiModelProperty(value = "部署路径") + private String deployPath; + + @ApiModelProperty(value = "备份路径") + private String backupPath; + + @ApiModelProperty(value = "启动脚本") + private String startScript; + + @ApiModelProperty(value = "部署脚本") + private String deployScript; + + public void copy(App source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Database.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Database.java new file mode 100644 index 000000000..babf3ba62 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Database.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.domain; + +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import java.io.Serializable; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Entity +@Getter +@Setter +@Table(name="mnt_database") +public class Database extends BaseEntity implements Serializable { + + @Id + @Column(name = "db_id") + @ApiModelProperty(value = "ID", hidden = true) + private String id; + + @ApiModelProperty(value = "数据库名称") + private String name; + + @ApiModelProperty(value = "数据库连接地址") + private String jdbcUrl; + + @ApiModelProperty(value = "数据库密码") + private String pwd; + + @ApiModelProperty(value = "用户名") + private String userName; + + public void copy(Database source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Deploy.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Deploy.java new file mode 100644 index 000000000..8c2e2d71c --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/Deploy.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.domain; + +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import java.io.Serializable; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Entity +@Getter +@Setter +@Table(name="mnt_deploy") +public class Deploy extends BaseEntity implements Serializable { + + @Id + @Column(name = "deploy_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToMany + @ApiModelProperty(name = "服务器", hidden = true) + @JoinTable(name = "mnt_deploy_server", + joinColumns = {@JoinColumn(name = "deploy_id",referencedColumnName = "deploy_id")}, + inverseJoinColumns = {@JoinColumn(name = "server_id",referencedColumnName = "server_id")}) + private Set deploys; + + @ManyToOne + @JoinColumn(name = "app_id") + @ApiModelProperty(value = "应用编号") + private App app; + + public void copy(Deploy source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/DeployHistory.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/DeployHistory.java new file mode 100644 index 000000000..e4de70cf5 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/DeployHistory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.domain; + +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import javax.persistence.*; +import java.io.Serializable; +import java.sql.Timestamp; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Entity +@Getter +@Setter +@Table(name="mnt_deploy_history") +public class DeployHistory implements Serializable { + + @Id + @Column(name = "history_id") + @ApiModelProperty(value = "ID", hidden = true) + private String id; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "IP") + private String ip; + + @CreationTimestamp + @ApiModelProperty(value = "部署时间") + private Timestamp deployDate; + + @ApiModelProperty(value = "部署者") + private String deployUser; + + @ApiModelProperty(value = "部署ID") + private Long deployId; + + public void copy(DeployHistory source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/ServerDeploy.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/ServerDeploy.java new file mode 100644 index 000000000..d7dc1d0bf --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/ServerDeploy.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.domain; + +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Entity +@Getter +@Setter +@Table(name="mnt_server") +public class ServerDeploy extends BaseEntity implements Serializable { + + @Id + @Column(name = "server_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ApiModelProperty(value = "服务器名称") + private String name; + + @ApiModelProperty(value = "IP") + private String ip; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "账号") + private String account; + + @ApiModelProperty(value = "密码") + private String password; + + public void copy(ServerDeploy source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerDeploy that = (ServerDeploy) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/enums/DataTypeEnum.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/enums/DataTypeEnum.java new file mode 100644 index 000000000..6d4ab1c91 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/domain/enums/DataTypeEnum.java @@ -0,0 +1,140 @@ +/* + * << + * Davinci + * == + * Copyright (C) 2016 - 2019 EDP + * == + * 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. + * >> + * + */ + +package me.zhengjie.modules.maint.domain.enums; +import lombok.extern.slf4j.Slf4j; + +/** + * @author / + */ +@Slf4j +@SuppressWarnings({"unchecked","all"}) +public enum DataTypeEnum { + + /** mysql */ + MYSQL("mysql", "mysql", "com.mysql.jdbc.Driver", "`", "`", "'", "'"), + + /** oracle */ + ORACLE("oracle", "oracle", "oracle.jdbc.driver.OracleDriver", "\"", "\"", "\"", "\""), + + /** sql server */ + SQLSERVER("sqlserver", "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "\"", "\"", "\"", "\""), + + /** h2 */ + H2("h2", "h2", "org.h2.Driver", "`", "`", "\"", "\""), + + /** phoenix */ + PHOENIX("phoenix", "hbase phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", "", "", "\"", "\""), + + /** mongo */ + MONGODB("mongo", "mongodb", "mongodb.jdbc.MongoDriver", "`", "`", "\"", "\""), + + /** sql4es */ + ELASTICSEARCH("sql4es", "elasticsearch", "nl.anchormen.sql4es.jdbc.ESDriver", "", "", "'", "'"), + + /** presto */ + PRESTO("presto", "presto", "com.facebook.presto.jdbc.PrestoDriver", "", "", "\"", "\""), + + /** moonbox */ + MOONBOX("moonbox", "moonbox", "moonbox.jdbc.MbDriver", "`", "`", "`", "`"), + + /** cassandra */ + CASSANDRA("cassandra", "cassandra", "com.github.adejanovski.cassandra.jdbc.CassandraDriver", "", "", "'", "'"), + + /** click house */ + CLICKHOUSE("clickhouse", "clickhouse", "ru.yandex.clickhouse.ClickHouseDriver", "", "", "\"", "\""), + + /** kylin */ + KYLIN("kylin", "kylin", "org.apache.kylin.jdbc.Driver", "\"", "\"", "\"", "\""), + + /** vertica */ + VERTICA("vertica", "vertica", "com.vertica.jdbc.Driver", "", "", "'", "'"), + + /** sap */ + HANA("sap", "sap hana", "com.sap.db.jdbc.Driver", "", "", "'", "'"), + + /** impala */ + IMPALA("impala", "impala", "com.cloudera.impala.jdbc41.Driver", "", "", "'", "'"); + + private String feature; + private String desc; + private String driver; + private String keywordPrefix; + private String keywordSuffix; + private String aliasPrefix; + private String aliasSuffix; + + private static final String JDBC_URL_PREFIX = "jdbc:"; + + DataTypeEnum(String feature, String desc, String driver, String keywordPrefix, String keywordSuffix, String aliasPrefix, String aliasSuffix) { + this.feature = feature; + this.desc = desc; + this.driver = driver; + this.keywordPrefix = keywordPrefix; + this.keywordSuffix = keywordSuffix; + this.aliasPrefix = aliasPrefix; + this.aliasSuffix = aliasSuffix; + } + + public static DataTypeEnum urlOf(String jdbcUrl) { + String url = jdbcUrl.toLowerCase().trim(); + for (DataTypeEnum dataTypeEnum : values()) { + if (url.startsWith(JDBC_URL_PREFIX + dataTypeEnum.feature)) { + try { + Class aClass = Class.forName(dataTypeEnum.getDriver()); + if (null == aClass) { + throw new RuntimeException("Unable to get driver instance for jdbcUrl: " + jdbcUrl); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to get driver instance: " + jdbcUrl); + } + return dataTypeEnum; + } + } + return null; + } + + public String getFeature() { + return feature; + } + + public String getDesc() { + return desc; + } + + public String getDriver() { + return driver; + } + + public String getKeywordPrefix() { + return keywordPrefix; + } + + public String getKeywordSuffix() { + return keywordSuffix; + } + + public String getAliasPrefix() { + return aliasPrefix; + } + + public String getAliasSuffix() { + return aliasSuffix; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/AppRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/AppRepository.java new file mode 100644 index 000000000..9dda8c26b --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/AppRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.repository; + +import me.zhengjie.modules.maint.domain.App; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface AppRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DatabaseRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DatabaseRepository.java new file mode 100644 index 000000000..2872b9901 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DatabaseRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.repository; + +import me.zhengjie.modules.maint.domain.Database; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface DatabaseRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployHistoryRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployHistoryRepository.java new file mode 100644 index 000000000..54d207b46 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployHistoryRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.repository; + +import me.zhengjie.modules.maint.domain.DeployHistory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface DeployHistoryRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployRepository.java new file mode 100644 index 000000000..0adf89897 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/DeployRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.repository; + +import me.zhengjie.modules.maint.domain.Deploy; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface DeployRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/ServerDeployRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/ServerDeployRepository.java new file mode 100644 index 000000000..ae8882b9f --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/repository/ServerDeployRepository.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.repository; + +import me.zhengjie.modules.maint.domain.ServerDeploy; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface ServerDeployRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据IP查询 + * @param ip / + * @return / + */ + ServerDeploy findByIp(String ip); +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/AppController.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/AppController.java new file mode 100644 index 000000000..ae931a48f --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/AppController.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.modules.maint.domain.App; +import me.zhengjie.modules.maint.service.AppService; +import me.zhengjie.modules.maint.service.dto.AppDto; +import me.zhengjie.modules.maint.service.dto.AppQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@RestController +@RequiredArgsConstructor +@Api(tags = "运维:应用管理") +@RequestMapping("/api/app") +public class AppController { + + private final AppService appService; + + @ApiOperation("导出应用数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('app:list')") + public void exportApp(HttpServletResponse response, AppQueryCriteria criteria) throws IOException { + appService.download(appService.queryAll(criteria), response); + } + + @ApiOperation(value = "查询应用") + @GetMapping + @PreAuthorize("@el.check('app:list')") + public ResponseEntity> queryApp(AppQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(appService.queryAll(criteria,pageable),HttpStatus.OK); + } + + @Log("新增应用") + @ApiOperation(value = "新增应用") + @PostMapping + @PreAuthorize("@el.check('app:add')") + public ResponseEntity createApp(@Validated @RequestBody App resources){ + appService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @Log("修改应用") + @ApiOperation(value = "修改应用") + @PutMapping + @PreAuthorize("@el.check('app:edit')") + public ResponseEntity updateApp(@Validated @RequestBody App resources){ + appService.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Log("删除应用") + @ApiOperation(value = "删除应用") + @DeleteMapping + @PreAuthorize("@el.check('app:del')") + public ResponseEntity deleteApp(@RequestBody Set ids){ + appService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DatabaseController.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DatabaseController.java new file mode 100644 index 000000000..7e93506f7 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DatabaseController.java @@ -0,0 +1,124 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.maint.domain.Database; +import me.zhengjie.modules.maint.service.DatabaseService; +import me.zhengjie.modules.maint.service.dto.DatabaseDto; +import me.zhengjie.modules.maint.service.dto.DatabaseQueryCriteria; +import me.zhengjie.modules.maint.util.SqlUtils; +import me.zhengjie.utils.FileUtil; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Api(tags = "运维:数据库管理") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/database") +public class DatabaseController { + + private final String fileSavePath = FileUtil.getTmpDirPath()+"/"; + private final DatabaseService databaseService; + + @ApiOperation("导出数据库数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('database:list')") + public void exportDatabase(HttpServletResponse response, DatabaseQueryCriteria criteria) throws IOException { + databaseService.download(databaseService.queryAll(criteria), response); + } + + @ApiOperation(value = "查询数据库") + @GetMapping + @PreAuthorize("@el.check('database:list')") + public ResponseEntity> queryDatabase(DatabaseQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(databaseService.queryAll(criteria,pageable),HttpStatus.OK); + } + + @Log("新增数据库") + @ApiOperation(value = "新增数据库") + @PostMapping + @PreAuthorize("@el.check('database:add')") + public ResponseEntity createDatabase(@Validated @RequestBody Database resources){ + databaseService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @Log("修改数据库") + @ApiOperation(value = "修改数据库") + @PutMapping + @PreAuthorize("@el.check('database:edit')") + public ResponseEntity updateDatabase(@Validated @RequestBody Database resources){ + databaseService.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Log("删除数据库") + @ApiOperation(value = "删除数据库") + @DeleteMapping + @PreAuthorize("@el.check('database:del')") + public ResponseEntity deleteDatabase(@RequestBody Set ids){ + databaseService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Log("测试数据库链接") + @ApiOperation(value = "测试数据库链接") + @PostMapping("/testConnect") + @PreAuthorize("@el.check('database:testConnect')") + public ResponseEntity testConnect(@Validated @RequestBody Database resources){ + return new ResponseEntity<>(databaseService.testConnection(resources),HttpStatus.CREATED); + } + + @Log("执行SQL脚本") + @ApiOperation(value = "执行SQL脚本") + @PostMapping(value = "/upload") + @PreAuthorize("@el.check('database:add')") + public ResponseEntity uploadDatabase(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{ + String id = request.getParameter("id"); + DatabaseDto database = databaseService.findById(id); + String fileName; + if(database != null){ + fileName = FileUtil.verifyFilename(file.getOriginalFilename()); + File executeFile = new File(fileSavePath + fileName); + FileUtil.del(executeFile); + file.transferTo(executeFile); + String result = SqlUtils.executeFile(database.getJdbcUrl(), database.getUserName(), database.getPwd(), executeFile); + return new ResponseEntity<>(result,HttpStatus.OK); + }else{ + throw new BadRequestException("Database not exist"); + } + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployController.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployController.java new file mode 100644 index 000000000..3dfe5eb44 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployController.java @@ -0,0 +1,159 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.Log; +import me.zhengjie.modules.maint.domain.Deploy; +import me.zhengjie.modules.maint.domain.DeployHistory; +import me.zhengjie.modules.maint.service.DeployService; +import me.zhengjie.modules.maint.service.dto.DeployDto; +import me.zhengjie.modules.maint.service.dto.DeployQueryCriteria; +import me.zhengjie.utils.FileUtil; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Slf4j +@RestController +@Api(tags = "运维:部署管理") +@RequiredArgsConstructor +@RequestMapping("/api/deploy") +public class DeployController { + + private final String fileSavePath = FileUtil.getTmpDirPath()+"/"; + private final DeployService deployService; + + + @ApiOperation("导出部署数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('database:list')") + public void exportDeployData(HttpServletResponse response, DeployQueryCriteria criteria) throws IOException { + deployService.download(deployService.queryAll(criteria), response); + } + + @ApiOperation(value = "查询部署") + @GetMapping + @PreAuthorize("@el.check('deploy:list')") + public ResponseEntity> queryDeployData(DeployQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(deployService.queryAll(criteria,pageable),HttpStatus.OK); + } + + @Log("新增部署") + @ApiOperation(value = "新增部署") + @PostMapping + @PreAuthorize("@el.check('deploy:add')") + public ResponseEntity createDeploy(@Validated @RequestBody Deploy resources){ + deployService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @Log("修改部署") + @ApiOperation(value = "修改部署") + @PutMapping + @PreAuthorize("@el.check('deploy:edit')") + public ResponseEntity updateDeploy(@Validated @RequestBody Deploy resources){ + deployService.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Log("删除部署") + @ApiOperation(value = "删除部署") + @DeleteMapping + @PreAuthorize("@el.check('deploy:del')") + public ResponseEntity deleteDeploy(@RequestBody Set ids){ + deployService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Log("上传文件部署") + @ApiOperation(value = "上传文件部署") + @PostMapping(value = "/upload") + @PreAuthorize("@el.check('deploy:edit')") + public ResponseEntity uploadDeploy(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{ + Long id = Long.valueOf(request.getParameter("id")); + String fileName = ""; + if(file != null){ + fileName = FileUtil.verifyFilename(file.getOriginalFilename()); + File deployFile = new File(fileSavePath + fileName); + FileUtil.del(deployFile); + file.transferTo(deployFile); + //文件下一步要根据文件名字来 + deployService.deploy(fileSavePath + fileName ,id); + }else{ + log.warn("没有找到相对应的文件"); + } + Map map = new HashMap<>(2); + map.put("error",0); + map.put("id",fileName); + return new ResponseEntity<>(map,HttpStatus.OK); + } + + @Log("系统还原") + @ApiOperation(value = "系统还原") + @PostMapping(value = "/serverReduction") + @PreAuthorize("@el.check('deploy:edit')") + public ResponseEntity serverReduction(@Validated @RequestBody DeployHistory resources){ + String result = deployService.serverReduction(resources); + return new ResponseEntity<>(result,HttpStatus.OK); + } + + @Log("服务运行状态") + @ApiOperation(value = "服务运行状态") + @PostMapping(value = "/serverStatus") + @PreAuthorize("@el.check('deploy:edit')") + public ResponseEntity serverStatus(@Validated @RequestBody Deploy resources){ + String result = deployService.serverStatus(resources); + return new ResponseEntity<>(result,HttpStatus.OK); + } + + @Log("启动服务") + @ApiOperation(value = "启动服务") + @PostMapping(value = "/startServer") + @PreAuthorize("@el.check('deploy:edit')") + public ResponseEntity startServer(@Validated @RequestBody Deploy resources){ + String result = deployService.startServer(resources); + return new ResponseEntity<>(result,HttpStatus.OK); + } + + @Log("停止服务") + @ApiOperation(value = "停止服务") + @PostMapping(value = "/stopServer") + @PreAuthorize("@el.check('deploy:edit')") + public ResponseEntity stopServer(@Validated @RequestBody Deploy resources){ + String result = deployService.stopServer(resources); + return new ResponseEntity<>(result,HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployHistoryController.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployHistoryController.java new file mode 100644 index 000000000..212952646 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/DeployHistoryController.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.modules.maint.service.DeployHistoryService; +import me.zhengjie.modules.maint.service.dto.DeployHistoryDto; +import me.zhengjie.modules.maint.service.dto.DeployHistoryQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@RestController +@RequiredArgsConstructor +@Api(tags = "运维:部署历史管理") +@RequestMapping("/api/deployHistory") +public class DeployHistoryController { + + private final DeployHistoryService deployhistoryService; + + @ApiOperation("导出部署历史数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('deployHistory:list')") + public void exportDeployHistory(HttpServletResponse response, DeployHistoryQueryCriteria criteria) throws IOException { + deployhistoryService.download(deployhistoryService.queryAll(criteria), response); + } + + @ApiOperation(value = "查询部署历史") + @GetMapping + @PreAuthorize("@el.check('deployHistory:list')") + public ResponseEntity> queryDeployHistory(DeployHistoryQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(deployhistoryService.queryAll(criteria,pageable),HttpStatus.OK); + } + + @Log("删除DeployHistory") + @ApiOperation(value = "删除部署历史") + @DeleteMapping + @PreAuthorize("@el.check('deployHistory:del')") + public ResponseEntity deleteDeployHistory(@RequestBody Set ids){ + deployhistoryService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/ServerDeployController.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/ServerDeployController.java new file mode 100644 index 000000000..5fde7be62 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/rest/ServerDeployController.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.modules.maint.domain.ServerDeploy; +import me.zhengjie.modules.maint.service.ServerDeployService; +import me.zhengjie.modules.maint.service.dto.ServerDeployDto; +import me.zhengjie.modules.maint.service.dto.ServerDeployQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@RestController +@Api(tags = "运维:服务器管理") +@RequiredArgsConstructor +@RequestMapping("/api/serverDeploy") +public class ServerDeployController { + + private final ServerDeployService serverDeployService; + + @ApiOperation("导出服务器数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('serverDeploy:list')") + public void exportServerDeploy(HttpServletResponse response, ServerDeployQueryCriteria criteria) throws IOException { + serverDeployService.download(serverDeployService.queryAll(criteria), response); + } + + @ApiOperation(value = "查询服务器") + @GetMapping + @PreAuthorize("@el.check('serverDeploy:list')") + public ResponseEntity> queryServerDeploy(ServerDeployQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(serverDeployService.queryAll(criteria,pageable),HttpStatus.OK); + } + + @Log("新增服务器") + @ApiOperation(value = "新增服务器") + @PostMapping + @PreAuthorize("@el.check('serverDeploy:add')") + public ResponseEntity createServerDeploy(@Validated @RequestBody ServerDeploy resources){ + serverDeployService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @Log("修改服务器") + @ApiOperation(value = "修改服务器") + @PutMapping + @PreAuthorize("@el.check('serverDeploy:edit')") + public ResponseEntity updateServerDeploy(@Validated @RequestBody ServerDeploy resources){ + serverDeployService.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Log("删除服务器") + @ApiOperation(value = "删除Server") + @DeleteMapping + @PreAuthorize("@el.check('serverDeploy:del')") + public ResponseEntity deleteServerDeploy(@RequestBody Set ids){ + serverDeployService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Log("测试连接服务器") + @ApiOperation(value = "测试连接服务器") + @PostMapping("/testConnect") + @PreAuthorize("@el.check('serverDeploy:add')") + public ResponseEntity testConnectServerDeploy(@Validated @RequestBody ServerDeploy resources){ + return new ResponseEntity<>(serverDeployService.testConnect(resources),HttpStatus.CREATED); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/AppService.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/AppService.java new file mode 100644 index 000000000..2134f276f --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/AppService.java @@ -0,0 +1,82 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service; + +import me.zhengjie.modules.maint.domain.App; +import me.zhengjie.modules.maint.service.dto.AppDto; +import me.zhengjie.modules.maint.service.dto.AppQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface AppService { + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(AppQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria 条件 + * @return / + */ + List queryAll(AppQueryCriteria criteria); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + AppDto findById(Long id); + + /** + * 创建 + * @param resources / + */ + void create(App resources); + + /** + * 编辑 + * @param resources / + */ + void update(App resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 导出数据 + * @param queryAll / + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DatabaseService.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DatabaseService.java new file mode 100644 index 000000000..81237fc7c --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DatabaseService.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service; + +import me.zhengjie.modules.maint.domain.Database; +import me.zhengjie.modules.maint.service.dto.DatabaseDto; +import me.zhengjie.modules.maint.service.dto.DatabaseQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + * @author ZhangHouYing + * @date 2019-08-24 + */ +public interface DatabaseService { + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(DatabaseQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部 + * @param criteria 条件 + * @return / + */ + List queryAll(DatabaseQueryCriteria criteria); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + DatabaseDto findById(String id); + + /** + * 创建 + * @param resources / + */ + void create(Database resources); + + /** + * 编辑 + * @param resources / + */ + void update(Database resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 测试连接数据库 + * @param resources / + * @return / + */ + boolean testConnection(Database resources); + + /** + * 导出数据 + * @param queryAll / + * @param response / + * @throws IOException e + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployHistoryService.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployHistoryService.java new file mode 100644 index 000000000..93c60ccee --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployHistoryService.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service; + +import me.zhengjie.modules.maint.domain.DeployHistory; +import me.zhengjie.modules.maint.service.dto.DeployHistoryDto; +import me.zhengjie.modules.maint.service.dto.DeployHistoryQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + * @author zhanghouying + */ +public interface DeployHistoryService { + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(DeployHistoryQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部 + * @param criteria 条件 + * @return / + */ + List queryAll(DeployHistoryQueryCriteria criteria); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + DeployHistoryDto findById(String id); + + /** + * 创建 + * @param resources / + */ + void create(DeployHistory resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 导出数据 + * @param queryAll / + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployService.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployService.java new file mode 100644 index 000000000..0a5fbb138 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/DeployService.java @@ -0,0 +1,117 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service; + +import me.zhengjie.modules.maint.domain.Deploy; +import me.zhengjie.modules.maint.domain.DeployHistory; +import me.zhengjie.modules.maint.service.dto.DeployDto; +import me.zhengjie.modules.maint.service.dto.DeployQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface DeployService { + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(DeployQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria 条件 + * @return / + */ + List queryAll(DeployQueryCriteria criteria); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + DeployDto findById(Long id); + + /** + * 创建 + * @param resources / + */ + void create(Deploy resources); + + + /** + * 编辑 + * @param resources / + */ + void update(Deploy resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 部署文件到服务器 + * @param fileSavePath 文件路径 + * @param appId 应用ID + */ + void deploy(String fileSavePath, Long appId); + + /** + * 查询部署状态 + * @param resources / + * @return / + */ + String serverStatus(Deploy resources); + /** + * 启动服务 + * @param resources / + * @return / + */ + String startServer(Deploy resources); + /** + * 停止服务 + * @param resources / + * @return / + */ + String stopServer(Deploy resources); + + /** + * 停止服务 + * @param resources / + * @return / + */ + String serverReduction(DeployHistory resources); + + /** + * 导出数据 + * @param queryAll / + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/ServerDeployService.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/ServerDeployService.java new file mode 100644 index 000000000..512081fb8 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/ServerDeployService.java @@ -0,0 +1,96 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service; + +import me.zhengjie.modules.maint.domain.ServerDeploy; +import me.zhengjie.modules.maint.service.dto.ServerDeployDto; +import me.zhengjie.modules.maint.service.dto.ServerDeployQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +public interface ServerDeployService { + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(ServerDeployQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria 条件 + * @return / + */ + List queryAll(ServerDeployQueryCriteria criteria); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + ServerDeployDto findById(Long id); + + /** + * 创建 + * @param resources / + */ + void create(ServerDeploy resources); + + /** + * 编辑 + * @param resources / + */ + void update(ServerDeploy resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 根据IP查询 + * @param ip / + * @return / + */ + ServerDeployDto findByIp(String ip); + + /** + * 测试登录服务器 + * @param resources / + * @return / + */ + Boolean testConnect(ServerDeploy resources); + + /** + * 导出数据 + * @param queryAll / + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppDto.java new file mode 100644 index 000000000..df88534a1 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppDto.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Getter +@Setter +public class AppDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "应用名称") + private String name; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "上传目录") + private String uploadPath; + + @ApiModelProperty(value = "部署目录") + private String deployPath; + + @ApiModelProperty(value = "备份目录") + private String backupPath; + + @ApiModelProperty(value = "启动脚本") + private String startScript; + + @ApiModelProperty(value = "部署脚本") + private String deployScript; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppQueryCriteria.java new file mode 100644 index 000000000..9932a3e03 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/AppQueryCriteria.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Data +public class AppQueryCriteria{ + + @ApiModelProperty(value = "模糊") + @Query(type = Query.Type.INNER_LIKE) + private String name; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseDto.java new file mode 100644 index 000000000..04b9155bd --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseDto.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Getter +@Setter +public class DatabaseDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private String id; + + @ApiModelProperty(value = "数据库名称") + private String name; + + @ApiModelProperty(value = "数据库连接地址") + private String jdbcUrl; + + @ApiModelProperty(value = "数据库密码") + private String pwd; + + @ApiModelProperty(value = "用户名") + private String userName; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseQueryCriteria.java new file mode 100644 index 000000000..dceb0f832 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DatabaseQueryCriteria.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Data +public class DatabaseQueryCriteria{ + + @ApiModelProperty(value = "模糊") + @Query(type = Query.Type.INNER_LIKE) + private String name; + + @Query + @ApiModelProperty(value = "数据库连接地址") + private String jdbcUrl; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployDto.java new file mode 100644 index 000000000..6eda1529f --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployDto.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import cn.hutool.core.collection.CollectionUtil; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Getter +@Setter +public class DeployDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private String id; + + @ApiModelProperty(value = "应用") + private AppDto app; + + @ApiModelProperty(value = "服务器") + private Set deploys; + + @ApiModelProperty(value = "服务器名称") + private String servers; + + @ApiModelProperty(value = "服务状态") + private String status; + + public String getServers() { + if(CollectionUtil.isNotEmpty(deploys)){ + return deploys.stream().map(ServerDeployDto::getName).collect(Collectors.joining(",")); + } + return servers; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DeployDto deployDto = (DeployDto) o; + return Objects.equals(id, deployDto.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryDto.java new file mode 100644 index 000000000..d08b99756 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryDto.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +import java.sql.Timestamp; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Data +public class DeployHistoryDto implements Serializable { + + @ApiModelProperty(value = "ID") + private String id; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "部署IP") + private String ip; + + @ApiModelProperty(value = "部署时间") + private Timestamp deployDate; + + @ApiModelProperty(value = "部署人员") + private String deployUser; + + @ApiModelProperty(value = "部署编号") + private Long deployId; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryQueryCriteria.java new file mode 100644 index 000000000..469f66615 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployHistoryQueryCriteria.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Data +public class DeployHistoryQueryCriteria{ + + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "appName,ip,deployUser") + private String blurry; + + @Query + @ApiModelProperty(value = "部署编号") + private Long deployId; + + @ApiModelProperty(value = "部署时间") + @Query(type = Query.Type.BETWEEN) + private List deployDate; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployQueryCriteria.java new file mode 100644 index 000000000..cd6da2b03 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/DeployQueryCriteria.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Data +public class DeployQueryCriteria{ + + @ApiModelProperty(value = "应用名称") + @Query(type = Query.Type.INNER_LIKE, propName = "name", joinName = "app") + private String appName; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployDto.java new file mode 100644 index 000000000..a916517b8 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployDto.java @@ -0,0 +1,68 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.Objects; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Getter +@Setter +public class ServerDeployDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "IP") + private String ip; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "账号") + private String account; + + @ApiModelProperty(value = "密码") + private String password; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerDeployDto that = (ServerDeployDto) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployQueryCriteria.java new file mode 100644 index 000000000..cdc20da35 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/dto/ServerDeployQueryCriteria.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Data +public class ServerDeployQueryCriteria{ + + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "name,ip,account") + private String blurry; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java new file mode 100644 index 000000000..9fee61820 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java @@ -0,0 +1,130 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.impl; + +import lombok.RequiredArgsConstructor; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.maint.domain.App; +import me.zhengjie.modules.maint.repository.AppRepository; +import me.zhengjie.modules.maint.service.AppService; +import me.zhengjie.modules.maint.service.dto.AppDto; +import me.zhengjie.modules.maint.service.dto.AppQueryCriteria; +import me.zhengjie.modules.maint.service.mapstruct.AppMapper; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Service +@RequiredArgsConstructor +public class AppServiceImpl implements AppService { + + private final AppRepository appRepository; + private final AppMapper appMapper; + + @Override + public PageResult queryAll(AppQueryCriteria criteria, Pageable pageable){ + Page page = appRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(appMapper::toDto)); + } + + @Override + public List queryAll(AppQueryCriteria criteria){ + return appMapper.toDto(appRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + } + + @Override + public AppDto findById(Long id) { + App app = appRepository.findById(id).orElseGet(App::new); + ValidationUtil.isNull(app.getId(),"App","id",id); + return appMapper.toDto(app); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(App resources) { + // 验证应用名称是否存在恶意攻击payload,https://github.com/elunez/eladmin/issues/873 + String appName = resources.getName(); + if (appName.contains(";") || appName.contains("|") || appName.contains("&")) { + throw new IllegalArgumentException("非法的应用名称,请勿包含[; | &]等特殊字符"); + } + verification(resources); + appRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(App resources) { + // 验证应用名称是否存在恶意攻击payload,https://github.com/elunez/eladmin/issues/873 + String appName = resources.getName(); + if (appName.contains(";") || appName.contains("|") || appName.contains("&")) { + throw new IllegalArgumentException("非法的应用名称,请勿包含[; | &]等特殊字符"); + } + verification(resources); + App app = appRepository.findById(resources.getId()).orElseGet(App::new); + ValidationUtil.isNull(app.getId(),"App","id",resources.getId()); + app.copy(resources); + appRepository.save(app); + } + + private void verification(App resources){ + String opt = "/opt"; + String home = "/home"; + if (!(resources.getUploadPath().startsWith(opt) || resources.getUploadPath().startsWith(home))) { + throw new BadRequestException("文件只能上传在opt目录或者home目录 "); + } + if (!(resources.getDeployPath().startsWith(opt) || resources.getDeployPath().startsWith(home))) { + throw new BadRequestException("文件只能部署在opt目录或者home目录 "); + } + if (!(resources.getBackupPath().startsWith(opt) || resources.getBackupPath().startsWith(home))) { + throw new BadRequestException("文件只能备份在opt目录或者home目录 "); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (Long id : ids) { + appRepository.deleteById(id); + } + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (AppDto appDto : queryAll) { + Map map = new LinkedHashMap<>(); + map.put("应用名称", appDto.getName()); + map.put("端口", appDto.getPort()); + map.put("上传目录", appDto.getUploadPath()); + map.put("部署目录", appDto.getDeployPath()); + map.put("备份目录", appDto.getBackupPath()); + map.put("启动脚本", appDto.getStartScript()); + map.put("部署脚本", appDto.getDeployScript()); + map.put("创建日期", appDto.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DatabaseServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DatabaseServiceImpl.java new file mode 100644 index 000000000..e620edb66 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DatabaseServiceImpl.java @@ -0,0 +1,114 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.impl; + +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.modules.maint.domain.Database; +import me.zhengjie.modules.maint.repository.DatabaseRepository; +import me.zhengjie.modules.maint.service.DatabaseService; +import me.zhengjie.modules.maint.service.dto.DatabaseDto; +import me.zhengjie.modules.maint.service.dto.DatabaseQueryCriteria; +import me.zhengjie.modules.maint.service.mapstruct.DatabaseMapper; +import me.zhengjie.modules.maint.util.SqlUtils; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Slf4j +@Service +@RequiredArgsConstructor +public class DatabaseServiceImpl implements DatabaseService { + + private final DatabaseRepository databaseRepository; + private final DatabaseMapper databaseMapper; + + @Override + public PageResult queryAll(DatabaseQueryCriteria criteria, Pageable pageable){ + Page page = databaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(databaseMapper::toDto)); + } + + @Override + public List queryAll(DatabaseQueryCriteria criteria){ + return databaseMapper.toDto(databaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + } + + @Override + public DatabaseDto findById(String id) { + Database database = databaseRepository.findById(id).orElseGet(Database::new); + ValidationUtil.isNull(database.getId(),"Database","id",id); + return databaseMapper.toDto(database); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(Database resources) { + resources.setId(IdUtil.simpleUUID()); + databaseRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(Database resources) { + Database database = databaseRepository.findById(resources.getId()).orElseGet(Database::new); + ValidationUtil.isNull(database.getId(),"Database","id",resources.getId()); + database.copy(resources); + databaseRepository.save(database); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (String id : ids) { + databaseRepository.deleteById(id); + } + } + + @Override + public boolean testConnection(Database resources) { + try { + return SqlUtils.testConnection(resources.getJdbcUrl(), resources.getUserName(), resources.getPwd()); + } catch (Exception e) { + log.error(e.getMessage()); + return false; + } + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DatabaseDto databaseDto : queryAll) { + Map map = new LinkedHashMap<>(); + map.put("数据库名称", databaseDto.getName()); + map.put("数据库连接地址", databaseDto.getJdbcUrl()); + map.put("用户名", databaseDto.getUserName()); + map.put("创建日期", databaseDto.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployHistoryServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployHistoryServiceImpl.java new file mode 100644 index 000000000..a206b326c --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployHistoryServiceImpl.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.impl; + +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.maint.domain.DeployHistory; +import me.zhengjie.modules.maint.repository.DeployHistoryRepository; +import me.zhengjie.modules.maint.service.DeployHistoryService; +import me.zhengjie.modules.maint.service.dto.DeployHistoryDto; +import me.zhengjie.modules.maint.service.dto.DeployHistoryQueryCriteria; +import me.zhengjie.modules.maint.service.mapstruct.DeployHistoryMapper; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Service +@RequiredArgsConstructor +public class DeployHistoryServiceImpl implements DeployHistoryService { + + private final DeployHistoryRepository deployhistoryRepository; + private final DeployHistoryMapper deployhistoryMapper; + + @Override + public PageResult queryAll(DeployHistoryQueryCriteria criteria, Pageable pageable){ + Page page = deployhistoryRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(deployhistoryMapper::toDto)); + } + + @Override + public List queryAll(DeployHistoryQueryCriteria criteria){ + return deployhistoryMapper.toDto(deployhistoryRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + } + + @Override + public DeployHistoryDto findById(String id) { + DeployHistory deployhistory = deployhistoryRepository.findById(id).orElseGet(DeployHistory::new); + ValidationUtil.isNull(deployhistory.getId(),"DeployHistory","id",id); + return deployhistoryMapper.toDto(deployhistory); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(DeployHistory resources) { + resources.setId(IdUtil.simpleUUID()); + deployhistoryRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (String id : ids) { + deployhistoryRepository.deleteById(id); + } + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DeployHistoryDto deployHistoryDto : queryAll) { + Map map = new LinkedHashMap<>(); + map.put("部署编号", deployHistoryDto.getDeployId()); + map.put("应用名称", deployHistoryDto.getAppName()); + map.put("部署IP", deployHistoryDto.getIp()); + map.put("部署时间", deployHistoryDto.getDeployDate()); + map.put("部署人员", deployHistoryDto.getDeployUser()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java new file mode 100644 index 000000000..350cff2e7 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java @@ -0,0 +1,434 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.impl; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.maint.domain.App; +import me.zhengjie.modules.maint.domain.Deploy; +import me.zhengjie.modules.maint.domain.DeployHistory; +import me.zhengjie.modules.maint.domain.ServerDeploy; +import me.zhengjie.modules.maint.repository.DeployRepository; +import me.zhengjie.modules.maint.service.DeployHistoryService; +import me.zhengjie.modules.maint.service.DeployService; +import me.zhengjie.modules.maint.service.ServerDeployService; +import me.zhengjie.modules.maint.service.dto.AppDto; +import me.zhengjie.modules.maint.service.dto.DeployDto; +import me.zhengjie.modules.maint.service.dto.DeployQueryCriteria; +import me.zhengjie.modules.maint.service.dto.ServerDeployDto; +import me.zhengjie.modules.maint.service.mapstruct.DeployMapper; +import me.zhengjie.modules.maint.util.ExecuteShellUtil; +import me.zhengjie.modules.maint.util.ScpClientUtil; +import me.zhengjie.modules.maint.websocket.MsgType; +import me.zhengjie.modules.maint.websocket.SocketMsg; +import me.zhengjie.modules.maint.websocket.WebSocketServer; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** + * @author zhanghouying + * @date 2019-08-24 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeployServiceImpl implements DeployService { + + private final String FILE_SEPARATOR = "/"; + private final DeployRepository deployRepository; + private final DeployMapper deployMapper; + private final ServerDeployService serverDeployService; + private final DeployHistoryService deployHistoryService; + /** + * 循环次数 + */ + private final Integer count = 30; + + + @Override + public PageResult queryAll(DeployQueryCriteria criteria, Pageable pageable) { + Page page = deployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable); + return PageUtil.toPage(page.map(deployMapper::toDto)); + } + + @Override + public List queryAll(DeployQueryCriteria criteria) { + return deployMapper.toDto(deployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder))); + } + + @Override + public DeployDto findById(Long id) { + Deploy deploy = deployRepository.findById(id).orElseGet(Deploy::new); + ValidationUtil.isNull(deploy.getId(), "Deploy", "id", id); + return deployMapper.toDto(deploy); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(Deploy resources) { + deployRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(Deploy resources) { + Deploy deploy = deployRepository.findById(resources.getId()).orElseGet(Deploy::new); + ValidationUtil.isNull(deploy.getId(), "Deploy", "id", resources.getId()); + deploy.copy(resources); + deployRepository.save(deploy); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (Long id : ids) { + deployRepository.deleteById(id); + } + } + + @Override + public void deploy(String fileSavePath, Long id) { + deployApp(fileSavePath, id); + } + + /** + * @param fileSavePath 本机路径 + * @param id ID + */ + private void deployApp(String fileSavePath, Long id) { + + DeployDto deploy = findById(id); + if (deploy == null) { + sendMsg("部署信息不存在", MsgType.ERROR); + throw new BadRequestException("部署信息不存在"); + } + AppDto app = deploy.getApp(); + if (app == null) { + sendMsg("包对应应用信息不存在", MsgType.ERROR); + throw new BadRequestException("包对应应用信息不存在"); + } + int port = app.getPort(); + //这个是服务器部署路径 + String uploadPath = app.getUploadPath(); + StringBuilder sb = new StringBuilder(); + String msg; + Set deploys = deploy.getDeploys(); + for (ServerDeployDto deployDTO : deploys) { + String ip = deployDTO.getIp(); + ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip); + //判断是否第一次部署 + boolean flag = checkFile(executeShellUtil, app); + //第一步要确认服务器上有这个目录 + executeShellUtil.execute("mkdir -p " + app.getUploadPath()); + executeShellUtil.execute("mkdir -p " + app.getBackupPath()); + executeShellUtil.execute("mkdir -p " + app.getDeployPath()); + //上传文件 + msg = String.format("登陆到服务器:%s", ip); + ScpClientUtil scpClientUtil = getScpClientUtil(ip); + log.info(msg); + sendMsg(msg, MsgType.INFO); + msg = String.format("上传文件到服务器:%s
目录:%s下,请稍等...", ip, uploadPath); + sendMsg(msg, MsgType.INFO); + scpClientUtil.putFile(fileSavePath, uploadPath); + if (flag) { + sendMsg("停止原来应用", MsgType.INFO); + //停止应用 + stopApp(port, executeShellUtil); + sendMsg("备份原来应用", MsgType.INFO); + //备份应用 + backupApp(executeShellUtil, ip, app.getDeployPath()+FILE_SEPARATOR, app.getName(), app.getBackupPath()+FILE_SEPARATOR, id); + } + sendMsg("部署应用", MsgType.INFO); + //部署文件,并启动应用 + String deployScript = app.getDeployScript(); + executeShellUtil.execute(deployScript); + sleep(3); + sendMsg("应用部署中,请耐心等待部署结果,或者稍后手动查看部署状态", MsgType.INFO); + int i = 0; + boolean result = false; + // 由于启动应用需要时间,所以需要循环获取状态,如果超过30次,则认为是启动失败 + while (i++ < count){ + result = checkIsRunningStatus(port, executeShellUtil); + if(result){ + break; + } + // 休眠6秒 + sleep(6); + } + sb.append("服务器:").append(deployDTO.getName()).append("
应用:").append(app.getName()); + sendResultMsg(result, sb); + executeShellUtil.close(); + } + } + + private void sleep(int second) { + try { + Thread.sleep(second * 1000); + } catch (InterruptedException e) { + log.error(e.getMessage(),e); + } + } + + private void backupApp(ExecuteShellUtil executeShellUtil, String ip, String fileSavePath, String appName, String backupPath, Long id) { + String deployDate = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN); + StringBuilder sb = new StringBuilder(); + backupPath += appName + FILE_SEPARATOR + deployDate + "\n"; + sb.append("mkdir -p ").append(backupPath); + sb.append("mv -f ").append(fileSavePath); + sb.append(appName).append(" ").append(backupPath); + log.info("备份应用脚本:" + sb.toString()); + executeShellUtil.execute(sb.toString()); + //还原信息入库 + DeployHistory deployHistory = new DeployHistory(); + deployHistory.setAppName(appName); + deployHistory.setDeployUser(SecurityUtils.getCurrentUsername()); + deployHistory.setIp(ip); + deployHistory.setDeployId(id); + deployHistoryService.create(deployHistory); + } + + /** + * 停App + * + * @param port 端口 + * @param executeShellUtil / + */ + private void stopApp(int port, ExecuteShellUtil executeShellUtil) { + //发送停止命令 + executeShellUtil.execute(String.format("lsof -i :%d|grep -v \"PID\"|awk '{print \"kill -9\",$2}'|sh", port)); + + } + + /** + * 指定端口程序是否在运行 + * + * @param port 端口 + * @param executeShellUtil / + * @return true 正在运行 false 已经停止 + */ + private boolean checkIsRunningStatus(int port, ExecuteShellUtil executeShellUtil) { + String result = executeShellUtil.executeForResult(String.format("fuser -n tcp %d", port)); + return result.indexOf("/tcp:")>0; + } + + private void sendMsg(String msg, MsgType msgType) { + try { + WebSocketServer.sendInfo(new SocketMsg(msg, msgType), "deploy"); + } catch (IOException e) { + log.error(e.getMessage(),e); + } + } + + @Override + public String serverStatus(Deploy resources) { + Set serverDeploys = resources.getDeploys(); + App app = resources.getApp(); + for (ServerDeploy serverDeploy : serverDeploys) { + StringBuilder sb = new StringBuilder(); + ExecuteShellUtil executeShellUtil = getExecuteShellUtil(serverDeploy.getIp()); + sb.append("服务器:").append(serverDeploy.getName()).append("
应用:").append(app.getName()); + boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil); + if (result) { + sb.append("
正在运行"); + sendMsg(sb.toString(), MsgType.INFO); + } else { + sb.append("
已停止!"); + sendMsg(sb.toString(), MsgType.ERROR); + } + log.info(sb.toString()); + executeShellUtil.close(); + } + return "执行完毕"; + } + + private boolean checkFile(ExecuteShellUtil executeShellUtil, AppDto app) { + String deployPath = app.getDeployPath(); + String appName = app.getName(); + // 使用安全的命令执行方式,避免直接拼接字符串,https://github.com/elunez/eladmin/issues/873 + String[] command = {"find", deployPath, "-name", appName}; + String result = executeShellUtil.executeForResult(Arrays.toString(command)); + return result.contains(appName); + } + + /** + * 启动服务 + * @param resources / + * @return / + */ + @Override + public String startServer(Deploy resources) { + Set deploys = resources.getDeploys(); + App app = resources.getApp(); + for (ServerDeploy deploy : deploys) { + StringBuilder sb = new StringBuilder(); + ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp()); + //为了防止重复启动,这里先停止应用 + stopApp(app.getPort(), executeShellUtil); + sb.append("服务器:").append(deploy.getName()).append("
应用:").append(app.getName()); + sendMsg("下发启动命令", MsgType.INFO); + executeShellUtil.execute(app.getStartScript()); + sleep(3); + sendMsg("应用启动中,请耐心等待启动结果,或者稍后手动查看运行状态", MsgType.INFO); + int i = 0; + boolean result = false; + // 由于启动应用需要时间,所以需要循环获取状态,如果超过30次,则认为是启动失败 + while (i++ < count){ + result = checkIsRunningStatus(app.getPort(), executeShellUtil); + if(result){ + break; + } + // 休眠6秒 + sleep(6); + } + sendResultMsg(result, sb); + log.info(sb.toString()); + executeShellUtil.close(); + } + return "执行完毕"; + } + + /** + * 停止服务 + * @param resources / + * @return / + */ + @Override + public String stopServer(Deploy resources) { + Set deploys = resources.getDeploys(); + App app = resources.getApp(); + for (ServerDeploy deploy : deploys) { + StringBuilder sb = new StringBuilder(); + ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp()); + sb.append("服务器:").append(deploy.getName()).append("
应用:").append(app.getName()); + sendMsg("下发停止命令", MsgType.INFO); + //停止应用 + stopApp(app.getPort(), executeShellUtil); + sleep(1); + boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil); + if (result) { + sb.append("
关闭失败!"); + sendMsg(sb.toString(), MsgType.ERROR); + } else { + sb.append("
关闭成功!"); + sendMsg(sb.toString(), MsgType.INFO); + } + log.info(sb.toString()); + executeShellUtil.close(); + } + return "执行完毕"; + } + + @Override + public String serverReduction(DeployHistory resources) { + Long deployId = resources.getDeployId(); + Deploy deployInfo = deployRepository.findById(deployId).orElseGet(Deploy::new); + String deployDate = DateUtil.format(resources.getDeployDate(), DatePattern.PURE_DATETIME_PATTERN); + App app = deployInfo.getApp(); + if (app == null) { + sendMsg("应用信息不存在:" + resources.getAppName(), MsgType.ERROR); + throw new BadRequestException("应用信息不存在:" + resources.getAppName()); + } + String backupPath = app.getBackupPath()+FILE_SEPARATOR; + backupPath += resources.getAppName() + FILE_SEPARATOR + deployDate; + //这个是服务器部署路径 + String deployPath = app.getDeployPath(); + String ip = resources.getIp(); + ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip); + String msg; + + msg = String.format("登陆到服务器:%s", ip); + log.info(msg); + sendMsg(msg, MsgType.INFO); + sendMsg("停止原来应用", MsgType.INFO); + //停止应用 + stopApp(app.getPort(), executeShellUtil); + //删除原来应用 + sendMsg("删除应用", MsgType.INFO); + executeShellUtil.execute("rm -rf " + deployPath + FILE_SEPARATOR + resources.getAppName()); + //还原应用 + sendMsg("还原应用", MsgType.INFO); + executeShellUtil.execute("cp -r " + backupPath + "/. " + deployPath); + sendMsg("启动应用", MsgType.INFO); + executeShellUtil.execute(app.getStartScript()); + sendMsg("应用启动中,请耐心等待启动结果,或者稍后手动查看启动状态", MsgType.INFO); + int i = 0; + boolean result = false; + // 由于启动应用需要时间,所以需要循环获取状态,如果超过30次,则认为是启动失败 + while (i++ < count){ + result = checkIsRunningStatus(app.getPort(), executeShellUtil); + if(result){ + break; + } + // 休眠6秒 + sleep(6); + } + StringBuilder sb = new StringBuilder(); + sb.append("服务器:").append(ip).append("
应用:").append(resources.getAppName()); + sendResultMsg(result, sb); + executeShellUtil.close(); + return ""; + } + + private ExecuteShellUtil getExecuteShellUtil(String ip) { + ServerDeployDto serverDeployDTO = serverDeployService.findByIp(ip); + if (serverDeployDTO == null) { + sendMsg("IP对应服务器信息不存在:" + ip, MsgType.ERROR); + throw new BadRequestException("IP对应服务器信息不存在:" + ip); + } + return new ExecuteShellUtil(ip, serverDeployDTO.getAccount(), serverDeployDTO.getPassword(),serverDeployDTO.getPort()); + } + + private ScpClientUtil getScpClientUtil(String ip) { + ServerDeployDto serverDeployDTO = serverDeployService.findByIp(ip); + if (serverDeployDTO == null) { + sendMsg("IP对应服务器信息不存在:" + ip, MsgType.ERROR); + throw new BadRequestException("IP对应服务器信息不存在:" + ip); + } + return ScpClientUtil.getInstance(ip, serverDeployDTO.getPort(), serverDeployDTO.getAccount(), serverDeployDTO.getPassword()); + } + + private void sendResultMsg(boolean result, StringBuilder sb) { + if (result) { + sb.append("
启动成功!"); + sendMsg(sb.toString(), MsgType.INFO); + } else { + sb.append("
启动失败!"); + sendMsg(sb.toString(), MsgType.ERROR); + } + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DeployDto deployDto : queryAll) { + Map map = new LinkedHashMap<>(); + map.put("应用名称", deployDto.getApp().getName()); + map.put("服务器", deployDto.getServers()); + map.put("部署日期", deployDto.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/ServerDeployServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/ServerDeployServiceImpl.java new file mode 100644 index 000000000..5075a90b7 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/ServerDeployServiceImpl.java @@ -0,0 +1,122 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.impl; + +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.maint.domain.ServerDeploy; +import me.zhengjie.modules.maint.repository.ServerDeployRepository; +import me.zhengjie.modules.maint.service.ServerDeployService; +import me.zhengjie.modules.maint.service.dto.ServerDeployDto; +import me.zhengjie.modules.maint.service.dto.ServerDeployQueryCriteria; +import me.zhengjie.modules.maint.service.mapstruct.ServerDeployMapper; +import me.zhengjie.modules.maint.util.ExecuteShellUtil; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Service +@RequiredArgsConstructor +public class ServerDeployServiceImpl implements ServerDeployService { + + private final ServerDeployRepository serverDeployRepository; + private final ServerDeployMapper serverDeployMapper; + + @Override + public PageResult queryAll(ServerDeployQueryCriteria criteria, Pageable pageable){ + Page page = serverDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(serverDeployMapper::toDto)); + } + + @Override + public List queryAll(ServerDeployQueryCriteria criteria){ + return serverDeployMapper.toDto(serverDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + } + + @Override + public ServerDeployDto findById(Long id) { + ServerDeploy server = serverDeployRepository.findById(id).orElseGet(ServerDeploy::new); + ValidationUtil.isNull(server.getId(),"ServerDeploy","id",id); + return serverDeployMapper.toDto(server); + } + + @Override + public ServerDeployDto findByIp(String ip) { + ServerDeploy deploy = serverDeployRepository.findByIp(ip); + return serverDeployMapper.toDto(deploy); + } + + @Override + public Boolean testConnect(ServerDeploy resources) { + ExecuteShellUtil executeShellUtil = null; + try { + executeShellUtil = new ExecuteShellUtil(resources.getIp(), resources.getAccount(), resources.getPassword(),resources.getPort()); + return executeShellUtil.execute("ls")==0; + } catch (Exception e) { + return false; + }finally { + if (executeShellUtil != null) { + executeShellUtil.close(); + } + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(ServerDeploy resources) { + serverDeployRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(ServerDeploy resources) { + ServerDeploy serverDeploy = serverDeployRepository.findById(resources.getId()).orElseGet(ServerDeploy::new); + ValidationUtil.isNull( serverDeploy.getId(),"ServerDeploy","id",resources.getId()); + serverDeploy.copy(resources); + serverDeployRepository.save(serverDeploy); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (Long id : ids) { + serverDeployRepository.deleteById(id); + } + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (ServerDeployDto deployDto : queryAll) { + Map map = new LinkedHashMap<>(); + map.put("服务器名称", deployDto.getName()); + map.put("服务器IP", deployDto.getIp()); + map.put("端口", deployDto.getPort()); + map.put("账号", deployDto.getAccount()); + map.put("创建日期", deployDto.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/AppMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/AppMapper.java new file mode 100644 index 000000000..bc2e66fb3 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/AppMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.maint.domain.App; +import me.zhengjie.modules.maint.service.dto.AppDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface AppMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DatabaseMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DatabaseMapper.java new file mode 100644 index 000000000..564438e4d --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DatabaseMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.maint.domain.Database; +import me.zhengjie.modules.maint.service.dto.DatabaseDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DatabaseMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployHistoryMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployHistoryMapper.java new file mode 100644 index 000000000..5e6f6468d --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployHistoryMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.maint.domain.DeployHistory; +import me.zhengjie.modules.maint.service.dto.DeployHistoryDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DeployHistoryMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployMapper.java new file mode 100644 index 000000000..8ed5347e9 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/DeployMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.maint.domain.Deploy; +import me.zhengjie.modules.maint.service.dto.DeployDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Mapper(componentModel = "spring",uses = {AppMapper.class, ServerDeployMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DeployMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/ServerDeployMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/ServerDeployMapper.java new file mode 100644 index 000000000..27d03fa10 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/mapstruct/ServerDeployMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.maint.domain.ServerDeploy; +import me.zhengjie.modules.maint.service.dto.ServerDeployDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author zhanghouying +* @date 2019-08-24 +*/ +@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ServerDeployMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ExecuteShellUtil.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ExecuteShellUtil.java new file mode 100644 index 000000000..1fc60791b --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ExecuteShellUtil.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.util; + +import cn.hutool.core.io.IoUtil; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import java.io.*; +import java.util.Vector; + +/** + * 执行shell命令 + * + * @author ZhangHouYing + * @date 2019/8/10 + */ +@Slf4j +public class ExecuteShellUtil { + + private Vector stdout; + + Session session; + + public ExecuteShellUtil(final String ipAddress, final String username, final String password,int port) { + try { + JSch jsch = new JSch(); + session = jsch.getSession(username, ipAddress, port); + session.setPassword(password); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(3000); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + + } + + public int execute(final String command) { + int returnCode = 0; + ChannelShell channel = null; + PrintWriter printWriter = null; + BufferedReader input = null; + stdout = new Vector(); + try { + channel = (ChannelShell) session.openChannel("shell"); + channel.connect(); + input = new BufferedReader(new InputStreamReader(channel.getInputStream())); + printWriter = new PrintWriter(channel.getOutputStream()); + printWriter.println(command); + printWriter.println("exit"); + printWriter.flush(); + log.info("The remote command is: "); + String line; + while ((line = input.readLine()) != null) { + stdout.add(line); + System.out.println(line); + } + } catch (Exception e) { + log.error(e.getMessage(),e); + return -1; + }finally { + IoUtil.close(printWriter); + IoUtil.close(input); + if (channel != null) { + channel.disconnect(); + } + } + return returnCode; + } + + public void close(){ + if (session != null) { + session.disconnect(); + } + } + + public String executeForResult(String command) { + execute(command); + StringBuilder sb = new StringBuilder(); + for (String str : stdout) { + sb.append(str); + } + return sb.toString(); + } + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ScpClientUtil.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ScpClientUtil.java new file mode 100644 index 000000000..92ba08442 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/ScpClientUtil.java @@ -0,0 +1,102 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.util; + +import ch.ethz.ssh2.Connection; +import ch.ethz.ssh2.SCPClient; +import com.google.common.collect.Maps; +import me.zhengjie.utils.StringUtils; + +import java.io.IOException; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 远程执行linux命令 + * @author ZhangHouYing + * @date 2019-08-10 10:06 + */ +public class ScpClientUtil { + + private final String ip; + private final int port; + private final String username; + private final String password; + + static private final Map instance = Maps.newHashMap(); + + static synchronized public ScpClientUtil getInstance(String ip, int port, String username, String password) { + instance.computeIfAbsent(ip, i -> new ScpClientUtil(i, port, username, password)); + return instance.get(ip); + } + + public ScpClientUtil(String ip, int port, String username, String password) { + this.ip = ip; + this.port = port; + this.username = username; + this.password = password; + } + + public void getFile(String remoteFile, String localTargetDirectory) { + Connection conn = new Connection(ip, port); + try { + conn.connect(); + boolean isAuthenticated = conn.authenticateWithPassword(username, password); + if (!isAuthenticated) { + System.err.println("authentication failed"); + } + SCPClient client = new SCPClient(conn); + client.get(remoteFile, localTargetDirectory); + } catch (IOException ex) { + Logger.getLogger(SCPClient.class.getName()).log(Level.SEVERE, null, ex); + }finally{ + conn.close(); + } + } + + public void putFile(String localFile, String remoteTargetDirectory) { + putFile(localFile, null, remoteTargetDirectory); + } + + public void putFile(String localFile, String remoteFileName, String remoteTargetDirectory) { + putFile(localFile, remoteFileName, remoteTargetDirectory,null); + } + + public void putFile(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) { + Connection conn = new Connection(ip, port); + try { + conn.connect(); + boolean isAuthenticated = conn.authenticateWithPassword(username, password); + if (!isAuthenticated) { + System.err.println("authentication failed"); + } + SCPClient client = new SCPClient(conn); + if (StringUtils.isBlank(mode)) { + mode = "0600"; + } + if (remoteFileName == null) { + client.put(localFile, remoteTargetDirectory); + } else { + client.put(localFile, remoteFileName, remoteTargetDirectory, mode); + } + } catch (IOException ex) { + Logger.getLogger(ScpClientUtil.class.getName()).log(Level.SEVERE, null, ex); + }finally{ + conn.close(); + } + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/SqlUtils.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/SqlUtils.java new file mode 100644 index 000000000..ac4c9d2aa --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/util/SqlUtils.java @@ -0,0 +1,234 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.util; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.modules.maint.domain.enums.DataTypeEnum; +import me.zhengjie.utils.CloseUtil; +import javax.sql.DataSource; +import java.io.BufferedReader; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +/** + * @author / + */ +@Slf4j +public class SqlUtils { + + /** + * 获取数据源 + * + * @param jdbcUrl / + * @param userName / + * @param password / + * @return DataSource + */ + private static DataSource getDataSource(String jdbcUrl, String userName, String password) { + DruidDataSource druidDataSource = new DruidDataSource(); + String className; + try { + className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName(); + } catch (SQLException e) { + throw new RuntimeException("Get class name error: =" + jdbcUrl); + } + if (StringUtils.isEmpty(className)) { + DataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl); + if (null == dataTypeEnum) { + throw new RuntimeException("Not supported data type: jdbcUrl=" + jdbcUrl); + } + druidDataSource.setDriverClassName(dataTypeEnum.getDriver()); + } else { + druidDataSource.setDriverClassName(className); + } + + // 去掉不安全的参数 + jdbcUrl = sanitizeJdbcUrl(jdbcUrl); + + druidDataSource.setUrl(jdbcUrl); + druidDataSource.setUsername(userName); + druidDataSource.setPassword(password); + // 配置获取连接等待超时的时间 + druidDataSource.setMaxWait(3000); + // 配置初始化大小、最小、最大 + druidDataSource.setInitialSize(1); + druidDataSource.setMinIdle(1); + druidDataSource.setMaxActive(1); + + // 如果链接出现异常则直接判定为失败而不是一直重试 + druidDataSource.setBreakAfterAcquireFailure(true); + try { + druidDataSource.init(); + } catch (SQLException e) { + log.error("Exception during pool initialization", e); + throw new RuntimeException(e.getMessage()); + } + + return druidDataSource; + } + + private static Connection getConnection(String jdbcUrl, String userName, String password) { + DataSource dataSource = getDataSource(jdbcUrl, userName, password); + Connection connection = null; + try { + connection = dataSource.getConnection(); + } catch (Exception ignored) {} + try { + int timeOut = 5; + if (null == connection || connection.isClosed() || !connection.isValid(timeOut)) { + log.info("connection is closed or invalid, retry get connection!"); + connection = dataSource.getConnection(); + } + } catch (Exception e) { + log.error("create connection error, jdbcUrl: {}", jdbcUrl); + throw new RuntimeException("create connection error, jdbcUrl: " + jdbcUrl); + } finally { + CloseUtil.close(connection); + } + return connection; + } + + private static void releaseConnection(Connection connection) { + if (null != connection) { + try { + connection.close(); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + } + + public static boolean testConnection(String jdbcUrl, String userName, String password) { + Connection connection = null; + try { + connection = getConnection(jdbcUrl, userName, password); + if (null != connection) { + return true; + } + } catch (Exception e) { + log.error("Get connection failed:{}", e.getMessage()); + } finally { + releaseConnection(connection); + } + return false; + } + + public static String executeFile(String jdbcUrl, String userName, String password, File sqlFile) { + Connection connection = getConnection(jdbcUrl, userName, password); + try { + batchExecute(connection, readSqlList(sqlFile)); + } catch (Exception e) { + log.error("sql脚本执行发生异常:{}",e.getMessage()); + return e.getMessage(); + }finally { + releaseConnection(connection); + } + return "success"; + } + + /** + * 批量执行sql + * @param connection / + * @param sqlList / + */ + public static void batchExecute(Connection connection, List sqlList) { + try (Statement st = connection.createStatement()) { + for (String sql : sqlList) { + // 去除末尾的分号 + if (sql.endsWith(";")) { + sql = sql.substring(0, sql.length() - 1); + } + // 检查 SQL 语句是否为空 + if (!sql.trim().isEmpty()) { + st.addBatch(sql); + } + } + st.executeBatch(); + } catch (SQLException e) { + log.error("SQL脚本批量执行发生异常: {},错误代码: {}", e.getMessage(), e.getErrorCode()); + } + } + + /** + * 将文件中的sql语句以;为单位读取到列表中 + * @param sqlFile / + * @return / + */ + private static List readSqlList(File sqlFile) { + List sqlList = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = Files.newBufferedReader(sqlFile.toPath(), StandardCharsets.UTF_8)) { + String line; + while ((line = reader.readLine()) != null) { + log.info("line: {}", line); + sb.append(line.trim()); + + if (line.trim().endsWith(";")) { + sqlList.add(sb.toString()); + // 清空 StringBuilder + sb.setLength(0); + } else { + // 在行之间加一个空格 + sb.append(" "); + } + } + if (sb.length() > 0) { + sqlList.add(sb.toString().trim()); + } + } catch (Exception e) { + log.error("读取SQL文件时发生异常: {}", e.getMessage()); + } + return sqlList; + } + + /** + * 去除不安全的参数 + * @param jdbcUrl / + * @return / + */ + private static String sanitizeJdbcUrl(String jdbcUrl) { + // 定义不安全参数和其安全替代值 + String[][] unsafeParams = { + // allowLoadLocalInfile:允许使用 LOAD DATA LOCAL INFILE,可能导致文件泄露 + {"allowLoadLocalInfile", "false"}, + // allowUrlInLocalInfile:允许在 LOAD DATA LOCAL INFILE 中使用 URL,可能导致未经授权的文件访问 + {"allowUrlInLocalInfile", "false"}, + // autoDeserialize:允许自动反序列化对象,可能导致反序列化漏洞 + {"autoDeserialize", "false"}, + // allowNanAndInf:允许使用 NaN 和 Infinity 作为数字值,可能导致不一致的数据处理 + {"allowNanAndInf", "false"}, + // allowMultiQueries:允许在一个语句中执行多个查询,可能导致 SQL 注入攻击 + {"allowMultiQueries", "false"}, + // allowPublicKeyRetrieval:允许从服务器检索公钥,可能导致中间人攻击 + {"allowPublicKeyRetrieval", "false"} + }; + + // 替换不安全的参数 + for (String[] param : unsafeParams) { + jdbcUrl = jdbcUrl.replaceAll("(?i)" + param[0] + "=true", param[0] + "=" + param[1]); + } + return jdbcUrl; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/MsgType.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/MsgType.java new file mode 100644 index 000000000..368534520 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/MsgType.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.websocket; + +/** + * @author ZhangHouYing + * @date 2019-08-10 9:56 + */ +public enum MsgType { + /** 连接 */ + CONNECT, + /** 关闭 */ + CLOSE, + /** 信息 */ + INFO, + /** 错误 */ + ERROR +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/SocketMsg.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/SocketMsg.java new file mode 100644 index 000000000..97fde3c6f --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/SocketMsg.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.websocket; + +import lombok.Data; + +/** + * @author ZhangHouYing + * @date 2019-08-10 9:55 + */ +@Data +public class SocketMsg { + private String msg; + private MsgType msgType; + + public SocketMsg(String msg, MsgType msgType) { + this.msg = msg; + this.msgType = msgType; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/WebSocketServer.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/WebSocketServer.java new file mode 100644 index 000000000..8a832e5b2 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/websocket/WebSocketServer.java @@ -0,0 +1,133 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.maint.websocket; + +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArraySet; +/** + * @author ZhangHouYing + * @date 2019-08-10 15:46 + */ +@ServerEndpoint("/webSocket/{sid}") +@Slf4j +@Component +public class WebSocketServer { + + /** + * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 + */ + private static final CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>(); + + /** + * 与某个客户端的连接会话,需要通过它来给客户端发送数据 + */ + private Session session; + + /** + * 接收sid + */ + private String sid=""; + /** + * 连接建立成功调用的方法 + * */ + @OnOpen + public void onOpen(Session session,@PathParam("sid") String sid) { + this.session = session; + //如果存在就先删除一个,防止重复推送消息 + webSocketSet.removeIf(webSocket -> webSocket.sid.equals(sid)); + webSocketSet.add(this); + this.sid=sid; + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + webSocketSet.remove(this); + } + + /** + * 收到客户端消息后调用的方法 + * @param message 客户端发送过来的消息*/ + @OnMessage + public void onMessage(String message, Session session) { + log.info("收到来"+sid+"的信息:"+message); + //群发消息 + for (WebSocketServer item : webSocketSet) { + try { + item.sendMessage(message); + } catch (IOException e) { + log.error(e.getMessage(),e); + } + } + } + + @OnError + public void onError(Session session, Throwable error) { + log.error("发生错误", error); + } + /** + * 实现服务器主动推送 + */ + private void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + + /** + * 群发自定义消息 + * */ + public static void sendInfo(SocketMsg socketMsg,@PathParam("sid") String sid) throws IOException { + String message = JSON.toJSONString(socketMsg); + log.info("推送消息到"+sid+",推送内容:"+message); + for (WebSocketServer item : webSocketSet) { + try { + //这里可以设定只推送给这个sid的,为null则全部推送 + if(sid==null) { + item.sendMessage(message); + }else if(item.sid.equals(sid)){ + item.sendMessage(message); + } + } catch (IOException ignored) { } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WebSocketServer that = (WebSocketServer) o; + return Objects.equals(session, that.session) && + Objects.equals(sid, that.sid); + } + + @Override + public int hashCode() { + return Objects.hash(session, sid); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/config/VisitsInitialization.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/config/VisitsInitialization.java deleted file mode 100644 index f44d72ad2..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/config/VisitsInitialization.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.zhengjie.modules.monitor.config; - -import me.zhengjie.modules.monitor.service.VisitsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; - -/** - * 初始化站点统计 - * @author Zheng Jie - */ -@Component -public class VisitsInitialization implements ApplicationRunner { - - @Autowired - private VisitsService visitsService; - - @Override - public void run(ApplicationArguments args) throws Exception { - System.out.println("--------------- 初始化站点统计,如果存在今日统计则跳过 ---------------"); - visitsService.save(); - System.out.println("--------------- 初始化站点统计完成 ---------------"); - } -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/domain/Visits.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/domain/Visits.java deleted file mode 100644 index 8784121d8..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/domain/Visits.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.zhengjie.modules.monitor.domain; - -import lombok.Data; -import org.hibernate.annotations.CreationTimestamp; - -import javax.persistence.*; -import java.sql.Timestamp; - -/** - * pv 与 ip 统计 - * - * @author Zheng Jie - * @date 2018-12-13 - */ -@Entity -@Data -@Table(name = "visits") -public class Visits { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(unique = true) - private String date; - - @Column(name = "pv_counts") - private Long pvCounts; - - @Column(name = "ip_counts") - private Long ipCounts; - - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; - - @Column(name = "week_day") - private String weekDay; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/domain/vo/RedisVo.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/domain/vo/RedisVo.java deleted file mode 100644 index 019a92c05..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/domain/vo/RedisVo.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.zhengjie.modules.monitor.domain.vo; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.NotBlank; -import java.io.Serializable; - -/** - * @author Zheng Jie - * @date 2018-12-10 - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class RedisVo implements Serializable { - - @NotBlank - private String key; - - @NotBlank - private String value; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/repository/VisitsRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/repository/VisitsRepository.java deleted file mode 100644 index 7a41c8f9a..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/repository/VisitsRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -package me.zhengjie.modules.monitor.repository; - -import me.zhengjie.modules.monitor.domain.Visits; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - * @author Zheng Jie - * @date 2018-12-13 - */ -@Repository -public interface VisitsRepository extends JpaRepository { - - /** - * findByDate - * @param date - * @return - */ - Visits findByDate(String date); - - /** - * 获得一个时间段的记录 - * @param date1 - * @param date2 - * @return - */ - @Query(value = "select * FROM visits where " + - "create_time between ?1 and ?2",nativeQuery = true) - List findAllVisits(String date1, String date2); -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java deleted file mode 100644 index 3a742cac3..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.zhengjie.modules.monitor.rest; - -import me.zhengjie.annotation.Limit; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * - * 接口限流测试类 - */ -@RestController -@RequestMapping("api") -public class LimitController { - private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(); - - /** - * 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test, - */ - @Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit") - @GetMapping("/limit") - public int testLimit() { - return ATOMIC_INTEGER.incrementAndGet(); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/RedisController.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/RedisController.java deleted file mode 100644 index f1cca13c6..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/RedisController.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.zhengjie.modules.monitor.rest; - -import me.zhengjie.aop.log.Log; -import me.zhengjie.modules.monitor.domain.vo.RedisVo; -import me.zhengjie.modules.monitor.service.RedisService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -/** - * @author Zheng Jie - * @date 2018-12-10 - */ -@RestController -@RequestMapping("api") -public class RedisController { - - @Autowired - private RedisService redisService; - - @Log("查询Redis缓存") - @GetMapping(value = "/redis") - @PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_SELECT')") - public ResponseEntity getRedis(String key, Pageable pageable){ - return new ResponseEntity(redisService.findByKey(key,pageable), HttpStatus.OK); - } - - @Log("删除Redis缓存") - @DeleteMapping(value = "/redis") - @PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_DELETE')") - public ResponseEntity delete(@RequestBody RedisVo resources){ - redisService.delete(resources.getKey()); - return new ResponseEntity(HttpStatus.OK); - } - - @Log("清空Redis缓存") - @DeleteMapping(value = "/redis/all") - @PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_DELETE')") - public ResponseEntity deleteAll(){ - redisService.flushdb(); - return new ResponseEntity(HttpStatus.OK); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/VisitsController.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/VisitsController.java deleted file mode 100644 index bd73228f5..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/VisitsController.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.zhengjie.modules.monitor.rest; - -import me.zhengjie.modules.monitor.service.VisitsService; -import me.zhengjie.utils.RequestHolder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Zheng Jie - * @date 2018-12-13 - */ -@RestController -@RequestMapping("api") -public class VisitsController { - - @Autowired - private VisitsService visitsService; - - @PostMapping(value = "/visits") - public ResponseEntity create(){ - visitsService.count(RequestHolder.getHttpServletRequest()); - return new ResponseEntity(HttpStatus.CREATED); - } - - @GetMapping(value = "/visits") - public ResponseEntity get(){ - return new ResponseEntity(visitsService.get(),HttpStatus.OK); - } - - @GetMapping(value = "/visits/chartData") - public ResponseEntity getChartData(){ - return new ResponseEntity(visitsService.getChartData(),HttpStatus.OK); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/RedisService.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/RedisService.java deleted file mode 100644 index c31d05c3b..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/RedisService.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.zhengjie.modules.monitor.service; - -import me.zhengjie.modules.monitor.domain.vo.RedisVo; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -/** - * 可自行扩展 - * @author Zheng Jie - * @date 2018-12-10 - */ -public interface RedisService { - - /** - * findById - * @param key - * @return - */ - Page findByKey(String key, Pageable pageable); - - /** - * 查询验证码的值 - * @param key - * @return - */ - String getCodeVal(String key); - - /** - * 保存验证码 - * @param key - * @param val - */ - void saveCode(String key, Object val); - - /** - * delete - * @param key - */ - void delete(String key); - - /** - * 清空所有缓存 - */ - void flushdb(); -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/VisitsService.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/VisitsService.java deleted file mode 100644 index 2f80df2c3..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/VisitsService.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.zhengjie.modules.monitor.service; - -import org.springframework.scheduling.annotation.Async; - -import javax.servlet.http.HttpServletRequest; - -/** - * @author Zheng Jie - * @date 2018-12-13 - */ -public interface VisitsService { - - /** - * 提供给定时任务,每天0点执行 - */ - void save(); - - /** - * 新增记录 - * @param request - */ - @Async - void count(HttpServletRequest request); - - /** - * 获取数据 - * @return - */ - Object get(); - - /** - * getChartData - * @return - */ - Object getChartData(); -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java deleted file mode 100644 index 249f14726..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -package me.zhengjie.modules.monitor.service.impl; - -import me.zhengjie.modules.monitor.domain.vo.RedisVo; -import me.zhengjie.modules.monitor.service.RedisService; -import me.zhengjie.utils.PageUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * @author Zheng Jie - * @date 2018-12-10 - */ -@Service -public class RedisServiceImpl implements RedisService { - - @Autowired - RedisTemplate redisTemplate; - - @Value("${loginCode.expiration}") - private Long expiration; - - @Override - public Page findByKey(String key, Pageable pageable){ - List redisVos = new ArrayList<>(); - if(!"*".equals(key)){ - key = "*" + key + "*"; - } - for (Object s : redisTemplate.keys(key)) { - // 过滤掉权限的缓存 - if (s.toString().indexOf("role::loadPermissionByUser") != -1 || s.toString().indexOf("user::loadUserByUsername") != -1) { - continue; - } - RedisVo redisVo = new RedisVo(s.toString(),redisTemplate.opsForValue().get(s.toString()).toString()); - redisVos.add(redisVo); - } - Page page = new PageImpl( - PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(),redisVos), - pageable, - redisVos.size()); - return page; - } - - @Override - public void delete(String key) { - redisTemplate.delete(key); - } - - @Override - public void flushdb() { - redisTemplate.getConnectionFactory().getConnection().flushDb(); - } - - @Override - public String getCodeVal(String key) { - try { - String value = redisTemplate.opsForValue().get(key).toString(); - return value; - }catch (Exception e){ - return ""; - } - } - - @Override - public void saveCode(String key, Object val) { - redisTemplate.opsForValue().set(key,val); - redisTemplate.expire(key,expiration, TimeUnit.MINUTES); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/VisitsServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/VisitsServiceImpl.java deleted file mode 100644 index fa00b02d6..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/VisitsServiceImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package me.zhengjie.modules.monitor.service.impl; - -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.modules.monitor.domain.Visits; -import me.zhengjie.modules.monitor.repository.VisitsRepository; -import me.zhengjie.modules.monitor.service.VisitsService; -import me.zhengjie.repository.LogRepository; -import me.zhengjie.utils.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import javax.servlet.http.HttpServletRequest; -import java.time.LocalDate; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * @author Zheng Jie - * @date 2018-12-13 - */ -@Slf4j -@Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class VisitsServiceImpl implements VisitsService { - - @Autowired - private VisitsRepository visitsRepository; - - @Autowired - private LogRepository logRepository; - - @Override - public void save() { - LocalDate localDate = LocalDate.now(); - Visits visits = visitsRepository.findByDate(localDate.toString()); - if(visits == null){ - visits = new Visits(); - visits.setWeekDay(StringUtils.getWeekDay()); - visits.setPvCounts(1L); - visits.setIpCounts(1L); - visits.setDate(localDate.toString()); - visitsRepository.save(visits); - } - } - - @Override - public void count(HttpServletRequest request) { - LocalDate localDate = LocalDate.now(); - Visits visits = visitsRepository.findByDate(localDate.toString()); - visits.setPvCounts(visits.getPvCounts()+1); - long ipCounts = logRepository.findIp(localDate.toString(), localDate.plusDays(1).toString()); - visits.setIpCounts(ipCounts); - visitsRepository.save(visits); - } - - @Override - public Object get() { - Map map = new HashMap(); - LocalDate localDate = LocalDate.now(); - Visits visits = visitsRepository.findByDate(localDate.toString()); - List list = visitsRepository.findAllVisits(localDate.minusDays(6).toString(),localDate.plusDays(1).toString()); - - long recentVisits = 0, recentIp = 0; - for (Visits data : list) { - recentVisits += data.getPvCounts(); - recentIp += data.getIpCounts(); - } - map.put("newVisits",visits.getPvCounts()); - map.put("newIp",visits.getIpCounts()); - map.put("recentVisits",recentVisits); - map.put("recentIp",recentIp); - return map; - } - - @Override - public Object getChartData() { - Map map = new HashMap(); - LocalDate localDate = LocalDate.now(); - List list = visitsRepository.findAllVisits(localDate.minusDays(6).toString(),localDate.plusDays(1).toString()); - map.put("weekDays",list.stream().map(Visits::getWeekDay).collect(Collectors.toList())); - map.put("visitsData",list.stream().map(Visits::getPvCounts).collect(Collectors.toList())); - map.put("ipData",list.stream().map(Visits::getIpCounts).collect(Collectors.toList())); - return map; - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java index a66ec06ec..a82e978c9 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java @@ -1,13 +1,29 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.config; +import lombok.RequiredArgsConstructor; import me.zhengjie.modules.quartz.domain.QuartzJob; import me.zhengjie.modules.quartz.repository.QuartzJobRepository; import me.zhengjie.modules.quartz.utils.QuartzManage; -import org.springframework.beans.factory.annotation.Autowired; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; - import java.util.List; /** @@ -15,26 +31,21 @@ * @date 2019-01-07 */ @Component +@RequiredArgsConstructor public class JobRunner implements ApplicationRunner { - - @Autowired - private QuartzJobRepository quartzJobRepository; - - @Autowired - private QuartzManage quartzManage; + private static final Logger log = LoggerFactory.getLogger(JobRunner.class); + private final QuartzJobRepository quartzJobRepository; + private final QuartzManage quartzManage; /** * 项目启动时重新激活启用的定时任务 - * @param applicationArguments - * @throws Exception + * + * @param applicationArguments / */ @Override - public void run(ApplicationArguments applicationArguments){ - System.out.println("--------------------注入定时任务---------------------"); + public void run(ApplicationArguments applicationArguments) { List quartzJobs = quartzJobRepository.findByIsPauseIsFalse(); - quartzJobs.forEach(quartzJob -> { - quartzManage.addJob(quartzJob); - }); - System.out.println("--------------------定时任务注入完成---------------------"); + quartzJobs.forEach(quartzManage::addJob); + log.info("Timing task injection complete"); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/QuartzConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/QuartzConfig.java index 3486ad81d..afc9c3ce1 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/QuartzConfig.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/QuartzConfig.java @@ -1,55 +1,66 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.config; -import org.quartz.Scheduler; +import lombok.extern.slf4j.Slf4j; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.lang.NonNull; import org.springframework.scheduling.quartz.AdaptableJobFactory; -import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Component; /** * 定时任务配置 - * @author + * @author / * @date 2019-01-07 */ +@Slf4j @Configuration +@Scope("singleton") public class QuartzConfig { /** * 解决Job中注入Spring Bean为null的问题 */ @Component("quartzJobFactory") - public class QuartzJobFactory extends AdaptableJobFactory { + public static class QuartzJobFactory extends AdaptableJobFactory { + + private final AutowireCapableBeanFactory capableBeanFactory; @Autowired - private AutowireCapableBeanFactory capableBeanFactory; + public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) { + this.capableBeanFactory = capableBeanFactory; + } + @NonNull @Override - protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { - - //调用父类的方法 - Object jobInstance = super.createJobInstance(bundle); - capableBeanFactory.autowireBean(jobInstance); - return jobInstance; + protected Object createJobInstance(@NonNull TriggerFiredBundle bundle) throws Exception { + try { + // 调用父类的方法,把Job注入到spring中 + Object jobInstance = super.createJobInstance(bundle); + capableBeanFactory.autowireBean(jobInstance); + log.debug("Job instance created and autowired: {}", jobInstance.getClass().getName()); + return jobInstance; + } catch (Exception e) { + log.error("Error creating job instance for bundle: {}", bundle, e); + throw e; + } } } - - /** - * 注入scheduler到spring - * @param quartzJobFactory - * @return - * @throws Exception - */ - @Bean(name = "scheduler") - public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception { - SchedulerFactoryBean factoryBean=new SchedulerFactoryBean(); - factoryBean.setJobFactory(quartzJobFactory); - factoryBean.afterPropertiesSet(); - Scheduler scheduler=factoryBean.getScheduler(); - scheduler.start(); - return scheduler; - } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzJob.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzJob.java index 65692d07b..2b013fa41 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzJob.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzJob.java @@ -1,82 +1,85 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.domain; -import lombok.Data; -import org.hibernate.annotations.UpdateTimestamp; - +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.io.Serializable; -import java.sql.Timestamp; /** * @author Zheng Jie * @date 2019-01-07 */ -@Data +@Getter +@Setter @Entity -@Table(name = "quartz_job") -public class QuartzJob implements Serializable { +@Table(name = "sys_quartz_job") +public class QuartzJob extends BaseEntity implements Serializable { public static final String JOB_KEY = "JOB_KEY"; @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "job_id") @NotNull(groups = {Update.class}) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 定时器名称 - */ - @Column(name = "job_name") + @Transient + @ApiModelProperty(value = "用于子任务唯一标识", hidden = true) + private String uuid; + + @ApiModelProperty(value = "定时器名称") private String jobName; - /** - * Bean名称 - */ - @Column(name = "bean_name") @NotBlank + @ApiModelProperty(value = "Bean名称") private String beanName; - /** - * 方法名称 - */ - @Column(name = "method_name") @NotBlank + @ApiModelProperty(value = "方法名称") private String methodName; - /** - * 参数 - */ - @Column(name = "params") + @ApiModelProperty(value = "参数") private String params; - /** - * cron表达式 - */ - @Column(name = "cron_expression") @NotBlank + @ApiModelProperty(value = "cron表达式") private String cronExpression; - /** - * 状态 - */ - @Column(name = "is_pause") + @ApiModelProperty(value = "状态,暂时或启动") private Boolean isPause = false; - /** - * 备注 - */ - @Column(name = "remark") - @NotBlank - private String remark; + @ApiModelProperty(value = "负责人") + private String personInCharge; + + @ApiModelProperty(value = "报警邮箱") + private String email; - /** - * 创建日期 - */ - @UpdateTimestamp - @Column(name = "update_time") - private Timestamp updateTime; + @ApiModelProperty(value = "子任务") + private String subTask; - public interface Update{} + @ApiModelProperty(value = "失败后暂停") + private Boolean pauseAfterFailure; + + @NotBlank + @ApiModelProperty(value = "备注") + private String description; } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzLog.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzLog.java index 9bd347a6f..a5697778f 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzLog.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/domain/QuartzLog.java @@ -1,8 +1,23 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.domain; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.hibernate.annotations.CreationTimestamp; - import javax.persistence.*; import java.io.Serializable; import java.sql.Timestamp; @@ -13,64 +28,40 @@ */ @Entity @Data -@Table(name = "quartz_log") +@Table(name = "sys_quartz_log") public class QuartzLog implements Serializable { @Id + @Column(name = "log_id") + @ApiModelProperty(value = "ID", hidden = true) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 任务名称 - */ - @Column(name = "job_name") + @ApiModelProperty(value = "任务名称", hidden = true) private String jobName; - /** - * Bean名称 - */ - @Column(name = "baen_name") + @ApiModelProperty(value = "bean名称", hidden = true) private String beanName; - /** - * 方法名称 - */ - @Column(name = "method_name") + @ApiModelProperty(value = "方法名称", hidden = true) private String methodName; - /** - * 参数 - */ - @Column(name = "params") + @ApiModelProperty(value = "参数", hidden = true) private String params; - /** - * cron表达式 - */ - @Column(name = "cron_expression") + @ApiModelProperty(value = "cron表达式", hidden = true) private String cronExpression; - /** - * 状态 - */ - @Column(name = "is_success") + @ApiModelProperty(value = "状态", hidden = true) private Boolean isSuccess; - /** - * 异常详细 - */ - @Column(name = "exception_detail",columnDefinition = "text") + @ApiModelProperty(value = "异常详情", hidden = true) private String exceptionDetail; - /** - * 耗时(毫秒) - */ + @ApiModelProperty(value = "执行耗时", hidden = true) private Long time; - /** - * 创建日期 - */ @CreationTimestamp - @Column(name = "create_time") + @ApiModelProperty(value = "创建时间", hidden = true) private Timestamp createTime; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzJobRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzJobRepository.java index 087f0617f..1334b581b 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzJobRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzJobRepository.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.repository; import me.zhengjie.modules.quartz.domain.QuartzJob; @@ -9,11 +24,11 @@ * @author Zheng Jie * @date 2019-01-07 */ -public interface QuartzJobRepository extends JpaRepository, JpaSpecificationExecutor { +public interface QuartzJobRepository extends JpaRepository, JpaSpecificationExecutor { /** * 查询启用的任务 - * @return + * @return List */ List findByIsPauseIsFalse(); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzLogRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzLogRepository.java index 6b78d4606..9198a6425 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzLogRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/repository/QuartzLogRepository.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.repository; import me.zhengjie.modules.quartz.domain.QuartzLog; @@ -8,6 +23,6 @@ * @author Zheng Jie * @date 2019-01-07 */ -public interface QuartzLogRepository extends JpaRepository, JpaSpecificationExecutor { +public interface QuartzLogRepository extends JpaRepository, JpaSpecificationExecutor { } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/rest/QuartzJobController.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/rest/QuartzJobController.java index 1a6049303..edbb6a167 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/rest/QuartzJobController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/rest/QuartzJobController.java @@ -1,18 +1,41 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.rest; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import me.zhengjie.aop.log.Log; +import me.zhengjie.annotation.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.quartz.domain.QuartzJob; +import me.zhengjie.modules.quartz.domain.QuartzLog; import me.zhengjie.modules.quartz.service.QuartzJobService; import me.zhengjie.modules.quartz.service.dto.JobQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.PageResult; +import me.zhengjie.utils.SpringBeanHolder; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; /** * @author Zheng Jie @@ -20,66 +43,99 @@ */ @Slf4j @RestController -@RequestMapping("/api") +@RequiredArgsConstructor +@RequestMapping("/api/jobs") +@Api(tags = "系统:定时任务管理") public class QuartzJobController { private static final String ENTITY_NAME = "quartzJob"; + private final QuartzJobService quartzJobService; + + @ApiOperation("查询定时任务") + @GetMapping + @PreAuthorize("@el.check('timing:list')") + public ResponseEntity> queryQuartzJob(JobQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(quartzJobService.queryAll(criteria,pageable), HttpStatus.OK); + } - @Autowired - private QuartzJobService quartzJobService; + @ApiOperation("导出任务数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('timing:list')") + public void exportQuartzJob(HttpServletResponse response, JobQueryCriteria criteria) throws IOException { + quartzJobService.download(quartzJobService.queryAll(criteria), response); + } - @Log("查询定时任务") - @GetMapping(value = "/jobs") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_SELECT')") - public ResponseEntity getJobs(JobQueryCriteria criteria, Pageable pageable){ - return new ResponseEntity(quartzJobService.queryAll(criteria,pageable), HttpStatus.OK); + @ApiOperation("导出日志数据") + @GetMapping(value = "/logs/download") + @PreAuthorize("@el.check('timing:list')") + public void exportQuartzJobLog(HttpServletResponse response, JobQueryCriteria criteria) throws IOException { + quartzJobService.downloadLog(quartzJobService.queryAllLog(criteria), response); } - @GetMapping(value = "/jobLogs") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_SELECT')") - public ResponseEntity getJobLogs(JobQueryCriteria criteria, Pageable pageable){ - return new ResponseEntity(quartzJobService.queryAllLog(criteria,pageable), HttpStatus.OK); + @ApiOperation("查询任务执行日志") + @GetMapping(value = "/logs") + @PreAuthorize("@el.check('timing:list')") + public ResponseEntity> queryQuartzJobLog(JobQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(quartzJobService.queryAllLog(criteria,pageable), HttpStatus.OK); } @Log("新增定时任务") - @PostMapping(value = "/jobs") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_CREATE')") - public ResponseEntity create(@Validated @RequestBody QuartzJob resources){ + @ApiOperation("新增定时任务") + @PostMapping + @PreAuthorize("@el.check('timing:add')") + public ResponseEntity createQuartzJob(@Validated @RequestBody QuartzJob resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(quartzJobService.create(resources),HttpStatus.CREATED); + // 验证Bean是不是合法的,合法的定时任务 Bean 需要用 @Service 定义 + checkBean(resources.getBeanName()); + quartzJobService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改定时任务") - @PutMapping(value = "/jobs") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_EDIT')") - public ResponseEntity update(@Validated(QuartzJob.Update.class) @RequestBody QuartzJob resources){ + @ApiOperation("修改定时任务") + @PutMapping + @PreAuthorize("@el.check('timing:edit')") + public ResponseEntity updateQuartzJob(@Validated(QuartzJob.Update.class) @RequestBody QuartzJob resources){ + // 验证Bean是不是合法的,合法的定时任务 Bean 需要用 @Service 定义 + checkBean(resources.getBeanName()); quartzJobService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("更改定时任务状态") - @PutMapping(value = "/jobs/{id}") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_EDIT')") - public ResponseEntity updateIsPause(@PathVariable Long id){ + @ApiOperation("更改定时任务状态") + @PutMapping(value = "/{id}") + @PreAuthorize("@el.check('timing:edit')") + public ResponseEntity updateQuartzJobStatus(@PathVariable Long id){ quartzJobService.updateIsPause(quartzJobService.findById(id)); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("执行定时任务") - @PutMapping(value = "/jobs/exec/{id}") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_EDIT')") - public ResponseEntity execution(@PathVariable Long id){ + @ApiOperation("执行定时任务") + @PutMapping(value = "/exec/{id}") + @PreAuthorize("@el.check('timing:edit')") + public ResponseEntity executionQuartzJob(@PathVariable Long id){ quartzJobService.execution(quartzJobService.findById(id)); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除定时任务") - @DeleteMapping(value = "/jobs/{id}") - @PreAuthorize("hasAnyRole('ADMIN','JOB_ALL','JOB_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - quartzJobService.delete(quartzJobService.findById(id)); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("删除定时任务") + @DeleteMapping + @PreAuthorize("@el.check('timing:del')") + public ResponseEntity deleteQuartzJob(@RequestBody Set ids){ + quartzJobService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } + + private void checkBean(String beanName){ + // 避免调用攻击者可以从SpringContextHolder获得控制jdbcTemplate类 + // 并使用getDeclaredMethod调用jdbcTemplate的queryForMap函数,执行任意sql命令。 + if(!SpringBeanHolder.getAllServiceBeanName().contains(beanName)){ + throw new BadRequestException("非法的 Bean,请重新输入!"); + } } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/QuartzJobService.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/QuartzJobService.java index 4acd76fb5..0a6a109b8 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/QuartzJobService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/QuartzJobService.java @@ -1,78 +1,123 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.service; import me.zhengjie.modules.quartz.domain.QuartzJob; import me.zhengjie.modules.quartz.domain.QuartzLog; import me.zhengjie.modules.quartz.service.dto.JobQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; +import me.zhengjie.utils.PageResult; import org.springframework.data.domain.Pageable; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; /** * @author Zheng Jie * @date 2019-01-07 */ -@CacheConfig(cacheNames = "quartzJob") public interface QuartzJobService { /** - * queryAll quartzJob - * @param criteria - * @param pageable - * @return + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(JobQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部 + * @param criteria 条件 + * @return / */ - @Cacheable(keyGenerator = "keyGenerator") - Object queryAll(JobQueryCriteria criteria, Pageable pageable); + List queryAll(JobQueryCriteria criteria); /** - * queryAll quartzLog - * @param criteria - * @param pageable - * @return + * 分页查询日志 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / */ - Object queryAllLog(JobQueryCriteria criteria, Pageable pageable); + PageResult queryAllLog(JobQueryCriteria criteria, Pageable pageable); /** - * create - * @param resources - * @return + * 查询全部 + * @param criteria 条件 + * @return / */ - @CacheEvict(allEntries = true) - QuartzJob create(QuartzJob resources); + List queryAllLog(JobQueryCriteria criteria); /** - * update - * @param resources - * @return + * 创建 + * @param resources / + */ + void create(QuartzJob resources); + + /** + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) void update(QuartzJob resources); /** - * del - * @param quartzJob + * 删除任务 + * @param ids / */ - @CacheEvict(allEntries = true) - void delete(QuartzJob quartzJob); + void delete(Set ids); /** - * findById - * @param id - * @return + * 根据ID查询 + * @param id ID + * @return / */ - @Cacheable(key = "#p0") QuartzJob findById(Long id); /** * 更改定时任务状态 - * @param quartzJob + * @param quartzJob / */ - @CacheEvict(allEntries = true) void updateIsPause(QuartzJob quartzJob); /** * 立即执行定时任务 - * @param quartzJob + * @param quartzJob / */ void execution(QuartzJob quartzJob); + + /** + * 导出定时任务 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 导出定时任务日志 + * @param queryAllLog 待导出的数据 + * @param response / + * @throws IOException / + */ + void downloadLog(List queryAllLog, HttpServletResponse response) throws IOException; + + /** + * 执行子任务 + * @param tasks / + * @throws InterruptedException / + */ + void executionSubJob(String[] tasks) throws InterruptedException; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/dto/JobQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/dto/JobQueryCriteria.java index 182d8f7cc..bb2c67e5b 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/dto/JobQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/dto/JobQueryCriteria.java @@ -1,7 +1,25 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; /** * @author Zheng Jie @@ -10,9 +28,15 @@ @Data public class JobQueryCriteria { + @ApiModelProperty(value = "任务名称") @Query(type = Query.Type.INNER_LIKE) private String jobName; @Query + @ApiModelProperty(value = "是否成功") private Boolean isSuccess; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/impl/QuartzJobServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/impl/QuartzJobServiceImpl.java index b38c6c59d..20707f07e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/impl/QuartzJobServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/service/impl/QuartzJobServiceImpl.java @@ -1,86 +1,109 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.service.impl; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.quartz.domain.QuartzJob; +import me.zhengjie.modules.quartz.domain.QuartzLog; import me.zhengjie.modules.quartz.repository.QuartzJobRepository; import me.zhengjie.modules.quartz.repository.QuartzLogRepository; import me.zhengjie.modules.quartz.service.QuartzJobService; import me.zhengjie.modules.quartz.service.dto.JobQueryCriteria; import me.zhengjie.modules.quartz.utils.QuartzManage; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.*; import org.quartz.CronExpression; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; /** * @author Zheng Jie * @date 2019-01-07 */ +@RequiredArgsConstructor @Service(value = "quartzJobService") -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) public class QuartzJobServiceImpl implements QuartzJobService { - @Autowired - private QuartzJobRepository quartzJobRepository; - - @Autowired - private QuartzLogRepository quartzLogRepository; - - @Autowired - private QuartzManage quartzManage; + private final QuartzJobRepository quartzJobRepository; + private final QuartzLogRepository quartzLogRepository; + private final QuartzManage quartzManage; + private final RedisUtils redisUtils; @Override - public Object queryAll(JobQueryCriteria criteria, Pageable pageable){ + public PageResult queryAll(JobQueryCriteria criteria, Pageable pageable){ return PageUtil.toPage(quartzJobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable)); } @Override - public Object queryAllLog(JobQueryCriteria criteria, Pageable pageable){ + public PageResult queryAllLog(JobQueryCriteria criteria, Pageable pageable){ return PageUtil.toPage(quartzLogRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable)); } + @Override + public List queryAll(JobQueryCriteria criteria) { + return quartzJobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + } + + @Override + public List queryAllLog(JobQueryCriteria criteria) { + return quartzLogRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + } + @Override public QuartzJob findById(Long id) { - Optional quartzJob = quartzJobRepository.findById(id); - ValidationUtil.isNull(quartzJob,"QuartzJob","id",id); - return quartzJob.get(); + QuartzJob quartzJob = quartzJobRepository.findById(id).orElseGet(QuartzJob::new); + ValidationUtil.isNull(quartzJob.getId(),"QuartzJob","id",id); + return quartzJob; } @Override @Transactional(rollbackFor = Exception.class) - public QuartzJob create(QuartzJob resources) { + public void create(QuartzJob resources) { if (!CronExpression.isValidExpression(resources.getCronExpression())){ throw new BadRequestException("cron表达式格式错误"); } resources = quartzJobRepository.save(resources); quartzManage.addJob(resources); - return resources; } @Override @Transactional(rollbackFor = Exception.class) public void update(QuartzJob resources) { - if(resources.getId().equals(1L)){ - throw new BadRequestException("该任务不可操作"); - } if (!CronExpression.isValidExpression(resources.getCronExpression())){ throw new BadRequestException("cron表达式格式错误"); } + if(StringUtils.isNotBlank(resources.getSubTask())){ + List tasks = Arrays.asList(resources.getSubTask().split("[,,]")); + if (tasks.contains(resources.getId().toString())) { + throw new BadRequestException("子任务中不能添加当前任务ID"); + } + } resources = quartzJobRepository.save(resources); quartzManage.updateJobCron(resources); } @Override public void updateIsPause(QuartzJob quartzJob) { - if(quartzJob.getId().equals(1L)){ - throw new BadRequestException("该任务不可操作"); - } + // 置换暂停状态 if (quartzJob.getIsPause()) { quartzManage.resumeJob(quartzJob); quartzJob.setIsPause(false); @@ -93,19 +116,81 @@ public void updateIsPause(QuartzJob quartzJob) { @Override public void execution(QuartzJob quartzJob) { - if(quartzJob.getId().equals(1L)){ - throw new BadRequestException("该任务不可操作"); + quartzManage.runJobNow(quartzJob); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (Long id : ids) { + QuartzJob quartzJob = findById(id); + quartzManage.deleteJob(quartzJob); + quartzJobRepository.delete(quartzJob); } - quartzManage.runAJobNow(quartzJob); } @Override @Transactional(rollbackFor = Exception.class) - public void delete(QuartzJob quartzJob) { - if(quartzJob.getId().equals(1L)){ - throw new BadRequestException("该任务不可操作"); + public void executionSubJob(String[] tasks) throws InterruptedException { + for (String id : tasks) { + if (StrUtil.isBlank(id)) { + // 如果是手动清除子任务id,会出现id为空字符串的问题 + continue; + } + QuartzJob quartzJob = findById(Long.parseLong(id)); + // 执行任务 + String uuid = IdUtil.simpleUUID(); + quartzJob.setUuid(uuid); + // 执行任务 + execution(quartzJob); + // 获取执行状态,如果执行失败则停止后面的子任务执行 + Boolean result = redisUtils.get(uuid, Boolean.class); + while (result == null) { + // 休眠5秒,再次获取子任务执行情况 + Thread.sleep(5000); + result = redisUtils.get(uuid, Boolean.class); + } + if(!result){ + redisUtils.del(uuid); + break; + } + } + } + + @Override + public void download(List quartzJobs, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (QuartzJob quartzJob : quartzJobs) { + Map map = new LinkedHashMap<>(); + map.put("任务名称", quartzJob.getJobName()); + map.put("Bean名称", quartzJob.getBeanName()); + map.put("执行方法", quartzJob.getMethodName()); + map.put("参数", quartzJob.getParams()); + map.put("表达式", quartzJob.getCronExpression()); + map.put("状态", quartzJob.getIsPause() ? "暂停中" : "运行中"); + map.put("描述", quartzJob.getDescription()); + map.put("创建日期", quartzJob.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + @Override + public void downloadLog(List queryAllLog, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (QuartzLog quartzLog : queryAllLog) { + Map map = new LinkedHashMap<>(); + map.put("任务名称", quartzLog.getJobName()); + map.put("Bean名称", quartzLog.getBeanName()); + map.put("执行方法", quartzLog.getMethodName()); + map.put("参数", quartzLog.getParams()); + map.put("表达式", quartzLog.getCronExpression()); + map.put("异常详情", quartzLog.getExceptionDetail()); + map.put("耗时/毫秒", quartzLog.getTime()); + map.put("状态", quartzLog.getIsSuccess() ? "成功" : "失败"); + map.put("创建日期", quartzLog.getCreateTime()); + list.add(map); } - quartzManage.deleteJob(quartzJob); - quartzJobRepository.delete(quartzJob); + FileUtil.downloadExcel(list, response); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/TestTask.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/TestTask.java index 757703bb7..c530a0077 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/TestTask.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/TestTask.java @@ -1,7 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.task; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; /** * 测试用 @@ -9,14 +24,18 @@ * @date 2019-01-08 */ @Slf4j -@Component +@Service public class TestTask { public void run(){ - log.info("执行成功"); + log.info("run 执行成功"); } public void run1(String str){ - log.info("执行成功,参数为: {}" + str); + log.info("run1 执行成功,参数为: {}", str); + } + + public void run2(){ + log.info("run2 执行成功"); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/VisitsTask.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/VisitsTask.java deleted file mode 100644 index 01e238e25..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/task/VisitsTask.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.zhengjie.modules.quartz.task; - -import me.zhengjie.modules.monitor.service.VisitsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * @author Zheng Jie - * @date 2018-12-25 - */ -@Component -public class VisitsTask { - - @Autowired - private VisitsService visitsService; - - public void run(){ - visitsService.save(); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/ExecutionJob.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/ExecutionJob.java index 756c0fe23..28c3f64c4 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/ExecutionJob.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/ExecutionJob.java @@ -1,39 +1,66 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.utils; +import cn.hutool.extra.template.Template; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import cn.hutool.extra.template.TemplateUtil; +import me.zhengjie.domain.vo.EmailVo; import me.zhengjie.modules.quartz.domain.QuartzJob; import me.zhengjie.modules.quartz.domain.QuartzLog; import me.zhengjie.modules.quartz.repository.QuartzLogRepository; import me.zhengjie.modules.quartz.service.QuartzJobService; -import me.zhengjie.utils.SpringContextHolder; +import me.zhengjie.service.EmailService; +import me.zhengjie.utils.RedisUtils; +import me.zhengjie.utils.SpringBeanHolder; +import me.zhengjie.utils.StringUtils; import me.zhengjie.utils.ThrowableUtil; import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.quartz.QuartzJobBean; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.*; +import java.util.concurrent.*; /** - * 参考人人开源,https://gitee.com/renrenio/renren-security - * @author + * 参考人人开源,... + * @author / * @date 2019-01-07 */ -@Async public class ExecutionJob extends QuartzJobBean { - private Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + + // 此处仅供参考,可根据任务执行情况自定义线程池参数 + private final ThreadPoolTaskExecutor executor = SpringBeanHolder.getBean("taskAsync"); - private ExecutorService executorService = Executors.newSingleThreadExecutor(); @Override - protected void executeInternal(JobExecutionContext context) { + public void executeInternal(JobExecutionContext context) { + // 获取任务 QuartzJob quartzJob = (QuartzJob) context.getMergedJobDataMap().get(QuartzJob.JOB_KEY); // 获取spring bean - QuartzLogRepository quartzLogRepository = SpringContextHolder.getBean("quartzLogRepository"); - QuartzJobService quartzJobService = SpringContextHolder.getBean("quartzJobService"); - QuartzManage quartzManage = SpringContextHolder.getBean("quartzManage"); + QuartzLogRepository quartzLogRepository = SpringBeanHolder.getBean(QuartzLogRepository.class); + QuartzJobService quartzJobService = SpringBeanHolder.getBean(QuartzJobService.class); + RedisUtils redisUtils = SpringBeanHolder.getBean(RedisUtils.class); + + String uuid = quartzJob.getUuid(); QuartzLog log = new QuartzLog(); log.setJobName(quartzJob.getJobName()); @@ -44,29 +71,63 @@ protected void executeInternal(JobExecutionContext context) { log.setCronExpression(quartzJob.getCronExpression()); try { // 执行任务 - logger.info("任务准备执行,任务名称:{}", quartzJob.getJobName()); - QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), - quartzJob.getParams()); - Future future = executorService.submit(task); + QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams()); + Future future = executor.submit(task); future.get(); long times = System.currentTimeMillis() - startTime; log.setTime(times); + if(StringUtils.isNotBlank(uuid)) { + redisUtils.set(uuid, true); + } // 任务状态 log.setIsSuccess(true); - logger.info("任务执行完毕,任务名称:{} 总共耗时:{} 毫秒", quartzJob.getJobName(), times); + logger.info("任务执行成功,任务名称:{}, 执行时间:{}毫秒", quartzJob.getJobName(), times); + // 判断是否存在子任务 + if(StringUtils.isNotBlank(quartzJob.getSubTask())){ + String[] tasks = quartzJob.getSubTask().split("[,,]"); + // 执行子任务 + quartzJobService.executionSubJob(tasks); + } } catch (Exception e) { - logger.error("任务执行失败,任务名称:{}" + quartzJob.getJobName(), e); + if(StringUtils.isNotBlank(uuid)) { + redisUtils.set(uuid, false); + } + logger.error("任务执行失败,任务名称:{}", quartzJob.getJobName()); long times = System.currentTimeMillis() - startTime; log.setTime(times); // 任务状态 0:成功 1:失败 log.setIsSuccess(false); log.setExceptionDetail(ThrowableUtil.getStackTrace(e)); - //出错就暂停任务 - quartzManage.pauseJob(quartzJob); - //更新状态 - quartzJobService.updateIsPause(quartzJob); + // 任务如果失败了则暂停 + if(quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure()){ + //更新状态 + quartzJob.setIsPause(false); + quartzJobService.updateIsPause(quartzJob); + } + if(quartzJob.getEmail() != null){ + EmailService emailService = SpringBeanHolder.getBean(EmailService.class); + // 邮箱报警 + if(StringUtils.isNoneBlank(quartzJob.getEmail())){ + EmailVo emailVo = taskAlarm(quartzJob, ThrowableUtil.getStackTrace(e)); + emailService.send(emailVo, emailService.find()); + } + } } finally { quartzLogRepository.save(log); } } + + private EmailVo taskAlarm(QuartzJob quartzJob, String msg) { + EmailVo emailVo = new EmailVo(); + emailVo.setSubject("定时任务【"+ quartzJob.getJobName() +"】执行失败,请尽快处理!"); + Map data = new HashMap<>(16); + data.put("task", quartzJob); + data.put("msg", msg); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + Template template = engine.getTemplate("taskAlarm.ftl"); + emailVo.setContent(template.render(data)); + List emails = Arrays.asList(quartzJob.getEmail().split("[,,]")); + emailVo.setTos(emails); + return emailVo; + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzManage.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzManage.java index a017df199..e46206252 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzManage.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzManage.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.utils; import lombok.extern.slf4j.Slf4j; @@ -20,7 +35,7 @@ public class QuartzManage { private static final String JOB_NAME = "TASK_"; - @Resource(name = "scheduler") + @Resource private Scheduler scheduler; public void addJob(QuartzJob quartzJob){ @@ -41,8 +56,12 @@ public void addJob(QuartzJob quartzJob){ //重置启动时间 ((CronTriggerImpl)cronTrigger).setStartTime(new Date()); - //执行定时任务 - scheduler.scheduleJob(jobDetail,cronTrigger); + //执行定时任务,如果是持久化的,这里会报错,捕获输出 + try { + scheduler.scheduleJob(jobDetail,cronTrigger); + } catch (ObjectAlreadyExistsException e) { + log.warn("定时任务已存在,跳过加载"); + } // 暂停任务 if (quartzJob.getIsPause()) { @@ -56,8 +75,7 @@ public void addJob(QuartzJob quartzJob){ /** * 更新job cron表达式 - * @param quartzJob - * @throws SchedulerException + * @param quartzJob / */ public void updateJobCron(QuartzJob quartzJob){ try { @@ -88,8 +106,7 @@ public void updateJobCron(QuartzJob quartzJob){ /** * 删除一个job - * @param quartzJob - * @throws SchedulerException + * @param quartzJob / */ public void deleteJob(QuartzJob quartzJob){ try { @@ -104,16 +121,16 @@ public void deleteJob(QuartzJob quartzJob){ /** * 恢复一个job - * @param quartzJob - * @throws SchedulerException + * @param quartzJob / */ public void resumeJob(QuartzJob quartzJob){ try { TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 如果不存在则创建一个定时任务 - if(trigger == null) + if(trigger == null) { addJob(quartzJob); + } JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); scheduler.resumeJob(jobKey); } catch (Exception e){ @@ -124,16 +141,16 @@ public void resumeJob(QuartzJob quartzJob){ /** * 立即执行job - * @param quartzJob - * @throws SchedulerException + * @param quartzJob / */ - public void runAJobNow(QuartzJob quartzJob){ + public void runJobNow(QuartzJob quartzJob){ try { TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 如果不存在则创建一个定时任务 - if(trigger == null) + if(trigger == null) { addJob(quartzJob); + } JobDataMap dataMap = new JobDataMap(); dataMap.put(QuartzJob.JOB_KEY, quartzJob); JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); @@ -146,8 +163,7 @@ public void runAJobNow(QuartzJob quartzJob){ /** * 暂停一个job - * @param quartzJob - * @throws SchedulerException + * @param quartzJob / */ public void pauseJob(QuartzJob quartzJob){ try { diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzRunnable.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzRunnable.java index 810cc8f27..0b4751a44 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzRunnable.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/utils/QuartzRunnable.java @@ -1,27 +1,42 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.quartz.utils; import lombok.extern.slf4j.Slf4j; -import me.zhengjie.utils.SpringContextHolder; +import me.zhengjie.utils.SpringBeanHolder; import org.apache.commons.lang3.StringUtils; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; +import java.util.concurrent.Callable; /** * 执行定时任务 - * @author + * @author / */ @Slf4j -public class QuartzRunnable implements Runnable { +public class QuartzRunnable implements Callable { - private Object target; - private Method method; - private String params; + private final Object target; + private final Method method; + private final String params; QuartzRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException { - this.target = SpringContextHolder.getBean(beanName); + this.target = SpringBeanHolder.getBean(beanName); this.params = params; - if (StringUtils.isNotBlank(params)) { this.method = target.getClass().getDeclaredMethod(methodName, String.class); } else { @@ -30,17 +45,14 @@ public class QuartzRunnable implements Runnable { } @Override - public void run() { - try { - ReflectionUtils.makeAccessible(method); - if (StringUtils.isNotBlank(params)) { - method.invoke(target, params); - } else { - method.invoke(target); - } - } catch (Exception e) { - log.error("定时任务执行失败",e); + @SuppressWarnings({"unchecked","all"}) + public Object call() throws Exception { + ReflectionUtils.makeAccessible(method); + if (StringUtils.isNotBlank(params)) { + method.invoke(target, params); + } else { + method.invoke(target); } + return null; } - } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/CaptchaConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/CaptchaConfig.java new file mode 100644 index 000000000..94fdf2ce9 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/CaptchaConfig.java @@ -0,0 +1,136 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version loginCode.length.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-loginCode.length.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.modules.security.config; + +import com.wf.captcha.*; +import com.wf.captcha.base.Captcha; +import lombok.Data; +import lombok.Getter; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.security.config.enums.LoginCodeEnum; +import me.zhengjie.utils.StringUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.awt.*; + +/** + * 登录验证码配置信息 + * @author liaojinlong + * @date 2025-01-13 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "login.code") +public class CaptchaConfig { + + /** + * 验证码配置 + */ + @Getter + private LoginCodeEnum codeType; + + /** + * 验证码有效期 分钟 + */ + private Long expiration = 5L; + + /** + * 验证码内容长度 + */ + private int length = 4; + + /** + * 验证码宽度 + */ + private int width = 111; + + /** + * 验证码高度 + */ + private int height = 36; + + /** + * 验证码字体 + */ + private String fontName; + + /** + * 字体大小 + */ + private int fontSize = 25; + + /** + * 依据配置信息生产验证码 + * @return / + */ + public Captcha getCaptcha() { + Captcha captcha; + switch (codeType) { + case ARITHMETIC: + // 算术类型 https://gitee.com/whvse/EasyCaptcha + captcha = new FixedArithmeticCaptcha(width, height); + // 几位数运算,默认是两位 + captcha.setLen(length); + break; + case CHINESE: + captcha = new ChineseCaptcha(width, height); + captcha.setLen(length); + break; + case CHINESE_GIF: + captcha = new ChineseGifCaptcha(width, height); + captcha.setLen(length); + break; + case GIF: + captcha = new GifCaptcha(width, height); + captcha.setLen(length); + break; + case SPEC: + captcha = new SpecCaptcha(width, height); + captcha.setLen(length); + break; + default: + throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum "); + } + if(StringUtils.isNotBlank(fontName)){ + captcha.setFont(new Font(fontName, Font.PLAIN, fontSize)); + } + return captcha; + } + + static class FixedArithmeticCaptcha extends ArithmeticCaptcha { + public FixedArithmeticCaptcha(int width, int height) { + super(width, height); + } + + @Override + protected char[] alphas() { + // 生成随机数字和运算符 + int n1 = num(1, 10), n2 = num(1, 10); + int opt = num(3); + + // 计算结果 + int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt]; + // 转换为字符运算符 + char optChar = "+-x".charAt(opt); + + this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2)); + this.chars = String.valueOf(res); + + return chars.toCharArray(); + } + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/LoginProperties.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/LoginProperties.java new file mode 100644 index 000000000..b38aabdf7 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/LoginProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version loginCode.length.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-loginCode.length.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.modules.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 配置文件读取 + * + * @author liaojinlong + * @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "login") +public class LoginProperties { + + /** + * 账号单用户 登录 + */ + private boolean singleLogin = false; + + public static final String cacheKey = "user-login-cache:"; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java deleted file mode 100644 index d7f006c1b..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java +++ /dev/null @@ -1,120 +0,0 @@ -package me.zhengjie.modules.security.config; - -import me.zhengjie.modules.security.security.JwtAuthenticationEntryPoint; -import me.zhengjie.modules.security.security.JwtAuthorizationTokenFilter; -import me.zhengjie.modules.security.service.JwtUserDetailsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -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.WebSecurityConfigurerAdapter; -import org.springframework.security.config.core.GrantedAuthorityDefaults; -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.authentication.UsernamePasswordAuthenticationFilter; - -@Configuration -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - @Autowired - private JwtAuthenticationEntryPoint unauthorizedHandler; - - @Autowired - private JwtUserDetailsService jwtUserDetailsService; - - /** - * 自定义基于JWT的安全过滤器 - */ - @Autowired - JwtAuthorizationTokenFilter authenticationTokenFilter; - - @Value("${jwt.header}") - private String tokenHeader; - - @Value("${jwt.auth.path}") - private String loginPath; - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth - .userDetailsService(jwtUserDetailsService) - .passwordEncoder(passwordEncoderBean()); - } - - @Bean - GrantedAuthorityDefaults grantedAuthorityDefaults() { - // Remove the ROLE_ prefix - return new GrantedAuthorityDefaults(""); - } - - @Bean - public PasswordEncoder passwordEncoderBean() { - return new BCryptPasswordEncoder(); - } - - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Override - protected void configure(HttpSecurity httpSecurity) throws Exception { - - httpSecurity - - // 禁用 CSRF - .csrf().disable() - - // 授权异常 - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - - // 不创建会话 - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - - // 过滤请求 - .authorizeRequests() - .antMatchers( - HttpMethod.GET, - "/*.html", - "/**/*.html", - "/**/*.css", - "/**/*.js" - ).anonymous() - - .antMatchers( HttpMethod.POST,"/auth/"+loginPath).anonymous() - .antMatchers("/auth/vCode").anonymous() - // 支付宝回调 - .antMatchers("/api/aliPay/return").anonymous() - .antMatchers("/api/aliPay/notify").anonymous() - - // swagger start - .antMatchers("/swagger-ui.html").anonymous() - .antMatchers("/swagger-resources/**").anonymous() - .antMatchers("/webjars/**").anonymous() - .antMatchers("/*/api-docs").anonymous() - // swagger end - - // 接口限流测试 - .antMatchers("/test/**").anonymous() - .antMatchers(HttpMethod.OPTIONS, "/**").anonymous() - - .antMatchers("/druid/**").anonymous() - // 所有请求都需要认证 - .anyRequest().authenticated() - // 防止iframe 造成跨域 - .and().headers().frameOptions().disable(); - - httpSecurity - .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java new file mode 100644 index 000000000..53e945283 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Jwt参数配置 + * + * @author Zheng Jie + * @date 2019年11月28日 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "jwt") +public class SecurityProperties { + + /** + * Request Headers : Authorization + */ + private String header; + + /** + * 令牌前缀,最后留个空格 Bearer + */ + private String tokenStartWith; + + /** + * 必须使用最少88位的Base64对该令牌进行编码 + */ + private String base64Secret; + + /** + * 令牌过期时间 此处单位/毫秒 + */ + private Long tokenValidityInSeconds; + + /** + * 在线用户 key,根据 key 查询 redis 中在线用户的数据 + */ + private String onlineKey; + + /** + * 验证码 key + */ + private String codeKey; + + /** + * token 续期检查 + */ + private Long detect; + + /** + * 续期时间 + */ + private Long renew; + + public String getTokenStartWith() { + return tokenStartWith + " "; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SpringSecurityConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SpringSecurityConfig.java new file mode 100644 index 000000000..f085281b2 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SpringSecurityConfig.java @@ -0,0 +1,131 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.config; + +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.security.security.*; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.utils.AnonTagUtils; +import me.zhengjie.utils.enums.RequestMethodEnum; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.core.GrantedAuthorityDefaults; +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.web.filter.CorsFilter; +import java.util.*; + +/** + * @author Zheng Jie + */ +@Configuration +@RequiredArgsConstructor +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SpringSecurityConfig { + + private final TokenProvider tokenProvider; + private final CorsFilter corsFilter; + private final JwtAuthenticationEntryPoint authenticationErrorHandler; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final ApplicationContext applicationContext; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + + @Bean + GrantedAuthorityDefaults grantedAuthorityDefaults() { + // 去除 ROLE_ 前缀 + return new GrantedAuthorityDefaults(""); + } + + @Bean + public PasswordEncoder passwordEncoder() { + // 密码加密方式 + return new BCryptPasswordEncoder(); + } + + @Bean + protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + // 获取匿名标记 + Map> anonymousUrls = AnonTagUtils.getAnonymousUrl(applicationContext); + return httpSecurity + // 禁用 CSRF + .csrf().disable() + .addFilter(corsFilter) + // 授权异常 + .exceptionHandling() + .authenticationEntryPoint(authenticationErrorHandler) + .accessDeniedHandler(jwtAccessDeniedHandler) + // 防止iframe 造成跨域 + .and() + .headers() + .frameOptions() + .disable() + // 不创建会话 + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + // 静态资源等等 + .antMatchers( + HttpMethod.GET, + "/*.html", + "/**/*.html", + "/**/*.css", + "/**/*.js", + "/webSocket/**" + ).permitAll() + // swagger 文档 + .antMatchers("/swagger-ui.html").permitAll() + .antMatchers("/swagger-resources/**").permitAll() + .antMatchers("/webjars/**").permitAll() + .antMatchers("/*/api-docs").permitAll() + // 文件 + .antMatchers("/avatar/**").permitAll() + .antMatchers("/file/**").permitAll() + // 阿里巴巴 druid + .antMatchers("/druid/**").permitAll() + // 放行OPTIONS请求 + .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() + // 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型 + // GET + .antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll() + // POST + .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll() + // PUT + .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll() + // PATCH + .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll() + // DELETE + .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll() + // 所有类型的接口都放行 + .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll() + // 所有请求都需要认证 + .anyRequest().authenticated() + .and().apply(securityConfigurerAdapter()) + .and().build(); + } + + private TokenConfigurer securityConfigurerAdapter() { + return new TokenConfigurer(tokenProvider, properties, onlineUserService); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/enums/LoginCodeEnum.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/enums/LoginCodeEnum.java new file mode 100644 index 000000000..f3ad553df --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/enums/LoginCodeEnum.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ +package me.zhengjie.modules.security.config.enums; + +/** + * 验证码配置枚举 + * + * @author liaojinlong + * @date 2020/6/10 17:40 + */ + +public enum LoginCodeEnum { + /** + * 算数 + */ + ARITHMETIC, + /** + * 中文 + */ + CHINESE, + /** + * 中文闪图 + */ + CHINESE_GIF, + /** + * 闪图 + */ + GIF, + /** + * 静态 + */ + SPEC +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java new file mode 100644 index 000000000..5244508f8 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java @@ -0,0 +1,152 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.rest; + +import cn.hutool.core.util.IdUtil; +import com.wf.captcha.base.Captcha; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.Log; +import me.zhengjie.annotation.rest.AnonymousDeleteMapping; +import me.zhengjie.annotation.rest.AnonymousGetMapping; +import me.zhengjie.annotation.rest.AnonymousPostMapping; +import me.zhengjie.config.properties.RsaProperties; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.security.config.CaptchaConfig; +import me.zhengjie.modules.security.config.enums.LoginCodeEnum; +import me.zhengjie.modules.security.config.LoginProperties; +import me.zhengjie.modules.security.config.SecurityProperties; +import me.zhengjie.modules.security.security.TokenProvider; +import me.zhengjie.modules.security.service.UserDetailsServiceImpl; +import me.zhengjie.modules.security.service.dto.AuthUserDto; +import me.zhengjie.modules.security.service.dto.JwtUserDto; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.utils.RsaUtils; +import me.zhengjie.utils.RedisUtils; +import me.zhengjie.utils.SecurityUtils; +import me.zhengjie.utils.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author Zheng Jie + * @date 2018-11-23 + * 授权、根据token获取用户详细信息 + */ +@Slf4j +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +@Api(tags = "系统:系统授权接口") +public class AuthController { + private final SecurityProperties properties; + private final RedisUtils redisUtils; + private final OnlineUserService onlineUserService; + private final TokenProvider tokenProvider; + private final LoginProperties loginProperties; + private final CaptchaConfig captchaConfig; + private final PasswordEncoder passwordEncoder; + private final UserDetailsServiceImpl userDetailsService; + + @Log("用户登录") + @ApiOperation("登录授权") + @AnonymousPostMapping(value = "/login") + public ResponseEntity login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception { + // 密码解密 + String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword()); + // 查询验证码 + String code = redisUtils.get(authUser.getUuid(), String.class); + // 清除验证码 + redisUtils.del(authUser.getUuid()); + if (StringUtils.isBlank(code)) { + throw new BadRequestException("验证码不存在或已过期"); + } + if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) { + throw new BadRequestException("验证码错误"); + } + // 获取用户信息 + JwtUserDto jwtUser = userDetailsService.loadUserByUsername(authUser.getUsername()); + // 验证用户密码 + if (!passwordEncoder.matches(password, jwtUser.getPassword())) { + throw new BadRequestException("登录密码错误"); + } + Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + // 生成令牌 + String token = tokenProvider.createToken(jwtUser); + // 返回 token 与 用户信息 + Map authInfo = new HashMap(2) {{ + put("token", properties.getTokenStartWith() + token); + put("user", jwtUser); + }}; + if (loginProperties.isSingleLogin()) { + // 踢掉之前已经登录的token + onlineUserService.kickOutForUsername(authUser.getUsername()); + } + // 保存在线信息 + onlineUserService.save(jwtUser, token, request); + // 返回登录信息 + return ResponseEntity.ok(authInfo); + } + + @ApiOperation("获取用户信息") + @GetMapping(value = "/info") + public ResponseEntity getUserInfo() { + JwtUserDto jwtUser = (JwtUserDto) SecurityUtils.getCurrentUser(); + return ResponseEntity.ok(jwtUser); + } + + @ApiOperation("获取验证码") + @AnonymousGetMapping(value = "/code") + public ResponseEntity getCode() { + // 获取运算的结果 + Captcha captcha = captchaConfig.getCaptcha(); + String uuid = properties.getCodeKey() + IdUtil.simpleUUID(); + //当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型 + String captchaValue = captcha.text(); + if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) { + captchaValue = captchaValue.split("\\.")[0]; + } + // 保存 + redisUtils.set(uuid, captchaValue, captchaConfig.getExpiration(), TimeUnit.MINUTES); + // 验证码信息 + Map imgResult = new HashMap(2) {{ + put("img", captcha.toBase64()); + put("uuid", uuid); + }}; + return ResponseEntity.ok(imgResult); + } + + @ApiOperation("退出登录") + @AnonymousDeleteMapping(value = "/logout") + public ResponseEntity logout(HttpServletRequest request) { + onlineUserService.logout(tokenProvider.getToken(request)); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java deleted file mode 100644 index baf991593..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java +++ /dev/null @@ -1,122 +0,0 @@ -package me.zhengjie.modules.security.rest; - -import cn.hutool.core.codec.Base64; -import cn.hutool.core.util.IdUtil; -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.aop.log.Log; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.modules.monitor.service.RedisService; -import me.zhengjie.modules.security.security.AuthenticationInfo; -import me.zhengjie.modules.security.security.AuthorizationUser; -import me.zhengjie.modules.security.security.ImgResult; -import me.zhengjie.modules.security.security.JwtUser; -import me.zhengjie.modules.security.utils.VerifyCodeUtils; -import me.zhengjie.utils.EncryptUtils; -import me.zhengjie.modules.security.utils.JwtTokenUtil; -import me.zhengjie.utils.SecurityUtils; -import me.zhengjie.utils.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AccountExpiredException; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * @author Zheng Jie - * @date 2018-11-23 - * 授权、根据token获取用户详细信息 - */ -@Slf4j -@RestController -@RequestMapping("auth") -public class AuthenticationController { - - @Value("${jwt.header}") - private String tokenHeader; - - @Autowired - private JwtTokenUtil jwtTokenUtil; - - @Autowired - private RedisService redisService; - - @Autowired - @Qualifier("jwtUserDetailsService") - private UserDetailsService userDetailsService; - - /** - * 登录授权 - * @param authorizationUser - * @return - */ - @Log("用户登录") - @PostMapping(value = "${jwt.auth.path}") - public ResponseEntity login(@Validated @RequestBody AuthorizationUser authorizationUser){ - - // 查询验证码 - String code = redisService.getCodeVal(authorizationUser.getUuid()); - // 清除验证码 - redisService.delete(authorizationUser.getUuid()); - if (StringUtils.isBlank(code)) { - throw new BadRequestException("验证码已过期"); - } - if (StringUtils.isBlank(authorizationUser.getCode()) || !authorizationUser.getCode().equalsIgnoreCase(code)) { - throw new BadRequestException("验证码错误"); - } - final JwtUser jwtUser = (JwtUser) userDetailsService.loadUserByUsername(authorizationUser.getUsername()); - - if(!jwtUser.getPassword().equals(EncryptUtils.encryptPassword(authorizationUser.getPassword()))){ - throw new AccountExpiredException("密码错误"); - } - - if(!jwtUser.isEnabled()){ - throw new AccountExpiredException("账号已停用,请联系管理员"); - } - - // 生成令牌 - final String token = jwtTokenUtil.generateToken(jwtUser); - - // 返回 token - return ResponseEntity.ok(new AuthenticationInfo(token,jwtUser)); - } - - /** - * 获取用户信息 - * @return - */ - @GetMapping(value = "${jwt.auth.account}") - public ResponseEntity getUserInfo(){ - JwtUser jwtUser = (JwtUser)userDetailsService.loadUserByUsername(SecurityUtils.getUsername()); - return ResponseEntity.ok(jwtUser); - } - - /** - * 获取验证码 - */ - @GetMapping(value = "vCode") - public ImgResult getCode(HttpServletResponse response) throws IOException { - - //生成随机字串 - String verifyCode = VerifyCodeUtils.generateVerifyCode(4); - String uuid = IdUtil.simpleUUID(); - redisService.saveCode(uuid,verifyCode); - // 生成图片 - int w = 111, h = 36; - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - VerifyCodeUtils.outputImage(w, h, stream, verifyCode); - try { - return new ImgResult(Base64.encode(stream.toByteArray()),uuid); - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - stream.close(); - } - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java new file mode 100644 index 000000000..9f767f673 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java @@ -0,0 +1,70 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.modules.security.service.dto.OnlineUserDto; +import me.zhengjie.utils.EncryptUtils; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +/** + * @author Zheng Jie + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth/online") +@Api(tags = "系统:在线用户管理") +public class OnlineController { + + private final OnlineUserService onlineUserService; + + @ApiOperation("查询在线用户") + @GetMapping + @PreAuthorize("@el.check()") + public ResponseEntity> queryOnlineUser(String username, Pageable pageable){ + return new ResponseEntity<>(onlineUserService.getAll(username, pageable),HttpStatus.OK); + } + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check()") + public void exportOnlineUser(HttpServletResponse response, String username) throws IOException { + onlineUserService.download(onlineUserService.getAll(username), response); + } + + @ApiOperation("踢出用户") + @DeleteMapping + @PreAuthorize("@el.check()") + public ResponseEntity deleteOnlineUser(@RequestBody Set keys) throws Exception { + for (String token : keys) { + // 解密Key + token = EncryptUtils.desDecrypt(token); + onlineUserService.logout(token); + } + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthenticationInfo.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthenticationInfo.java deleted file mode 100644 index 65fa72322..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthenticationInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.zhengjie.modules.security.security; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import java.io.Serializable; - -/** - * @author Zheng Jie - * @date 2018-11-23 - * 返回token - */ -@Getter -@AllArgsConstructor -public class AuthenticationInfo implements Serializable { - - private final String token; - - private final JwtUser user; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthorizationUser.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthorizationUser.java deleted file mode 100644 index 1b93aca54..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthorizationUser.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.zhengjie.modules.security.security; - -import lombok.Getter; -import lombok.Setter; - -import javax.validation.constraints.NotBlank; - -/** - * @author Zheng Jie - * @date 2018-11-30 - */ -@Getter -@Setter -public class AuthorizationUser { - - @NotBlank - private String username; - - @NotBlank - private String password; - - private String code; - - private String uuid = ""; - - @Override - public String toString() { - return "{username=" + username + ", password= ******}"; - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/ImgResult.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/ImgResult.java deleted file mode 100644 index 488ff9e78..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/ImgResult.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.zhengjie.modules.security.security; - -import lombok.AllArgsConstructor; -import lombok.Data; - -/** - * @author Zheng Jie - * @date 2019-6-5 17:29:57 - */ -@Data -@AllArgsConstructor -public class ImgResult { - - private String img; - - private String uuid; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java new file mode 100644 index 000000000..31165f78d --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import me.zhengjie.exception.handler.ApiError; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Zheng Jie + */ +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + //当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应 + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.setContentType("application/json;charset=UTF-8"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.FORBIDDEN.value(), "禁止访问,您没有权限访问此资源")); + response.getWriter().write(jsonResponse); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java index f15046711..f6884f4e1 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java @@ -1,26 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.security.security; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.exception.handler.ApiError; +import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.Serializable; +/** + * @author Zheng Jie + */ +@Slf4j @Component -public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { - - private static final long serialVersionUID = -8970718410437077606L; +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override - public void commence(HttpServletRequest request, - HttpServletResponse response, - AuthenticationException authException) throws IOException { - /** - * 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 - */ - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage()); + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { + // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 + int code = HttpStatus.UNAUTHORIZED.value(); + response.setStatus(code); + response.setContentType("application/json;charset=UTF-8"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.UNAUTHORIZED.value(), "登录状态已过期,请重新登录")); + response.getWriter().write(jsonResponse); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthorizationTokenFilter.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthorizationTokenFilter.java deleted file mode 100644 index 1693bcc1d..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthorizationTokenFilter.java +++ /dev/null @@ -1,67 +0,0 @@ -package me.zhengjie.modules.security.security; - -import io.jsonwebtoken.ExpiredJwtException; -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.modules.security.utils.JwtTokenUtil; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Slf4j -@Component -public class JwtAuthorizationTokenFilter extends OncePerRequestFilter { - - private final UserDetailsService userDetailsService; - private final JwtTokenUtil jwtTokenUtil; - private final String tokenHeader; - - public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) { - this.userDetailsService = userDetailsService; - this.jwtTokenUtil = jwtTokenUtil; - this.tokenHeader = tokenHeader; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - - final String requestHeader = request.getHeader(this.tokenHeader); - - String username = null; - String authToken = null; - if (requestHeader != null && requestHeader.startsWith("Bearer ")) { - authToken = requestHeader.substring(7); - try { - username = jwtTokenUtil.getUsernameFromToken(authToken); - } catch (ExpiredJwtException e) { - log.error(e.getMessage()); - } - } - - if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { - - // It is not compelling necessary to load the use details from the database. You could also store the information - // in the token and read it from it. It's up to you ;) - JwtUser userDetails = (JwtUser)this.userDetailsService.loadUserByUsername(username); - - // For simple validation it is completely sufficient to just check the token integrity. You don't have to call - // the database compellingly. Again it's up to you ;) - if (jwtTokenUtil.validateToken(authToken, userDetails)) { - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } - chain.doFilter(request, response); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtUser.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtUser.java deleted file mode 100644 index bc39e9a11..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtUser.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.zhengjie.modules.security.security; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import java.sql.Timestamp; -import java.util.Collection; -import java.util.Date; -import java.util.stream.Collectors; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Getter -@AllArgsConstructor -public class JwtUser implements UserDetails { - - @JsonIgnore - private final Long id; - - private final String username; - - @JsonIgnore - private final String password; - - private final String avatar; - - private final String email; - - private final String phone; - - private final String dept; - - private final String job; - - @JsonIgnore - private final Collection authorities; - - private final boolean enabled; - - private Timestamp createTime; - - @JsonIgnore - private final Date lastPasswordResetDate; - - @JsonIgnore - @Override - public boolean isAccountNonExpired() { - return true; - } - - @JsonIgnore - @Override - public boolean isAccountNonLocked() { - return true; - } - - @JsonIgnore - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @JsonIgnore - @Override - public String getPassword() { - return password; - } - - @Override - public boolean isEnabled() { - return enabled; - } - - public Collection getRoles() { - return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java new file mode 100644 index 000000000..da4369047 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.security; + +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.security.config.SecurityProperties; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.modules.security.service.UserCacheManager; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +/** + * @author / + */ +@RequiredArgsConstructor +public class TokenConfigurer extends SecurityConfigurerAdapter { + + private final TokenProvider tokenProvider; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + + @Override + public void configure(HttpSecurity http) { + TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java new file mode 100644 index 000000000..c2829b2a9 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.security; + +import cn.hutool.core.util.StrUtil; +import me.zhengjie.modules.security.config.SecurityProperties; +import me.zhengjie.modules.security.service.dto.OnlineUserDto; +import me.zhengjie.modules.security.service.OnlineUserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * @author / + */ +public class TokenFilter extends GenericFilterBean { + private static final Logger log = LoggerFactory.getLogger(TokenFilter.class); + + + private final TokenProvider tokenProvider; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + + /** + * @param tokenProvider Token + * @param properties JWT + * @param onlineUserService 用户在线 + */ + public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService) { + this.properties = properties; + this.onlineUserService = onlineUserService; + this.tokenProvider = tokenProvider; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String token = resolveToken(httpServletRequest); + // 对于 Token 为空的不需要去查 Redis + if(StrUtil.isNotBlank(token)){ + // 获取用户Token的Key + String loginKey = tokenProvider.loginKey(token); + OnlineUserDto onlineUserDto = onlineUserService.getOne(loginKey); + // 判断用户在线信息是否为空 + if (onlineUserDto != null) { + // Token 续期判断 + tokenProvider.checkRenewal(token); + // 获取认证信息,设置上下文 + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + filterChain.doFilter(servletRequest, servletResponse); + } + + /** + * 初步检测Token + * + * @param request / + * @return / + */ + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(properties.getHeader()); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) { + // 去掉令牌前缀 + return bearerToken.replace(properties.getTokenStartWith(), ""); + } else { + log.debug("非法Token:{}", bearerToken); + } + return null; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java new file mode 100644 index 000000000..e1eb0c0e2 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java @@ -0,0 +1,155 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.security; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.modules.security.config.SecurityProperties; +import me.zhengjie.modules.security.service.dto.JwtUserDto; +import me.zhengjie.utils.RedisUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; +import javax.servlet.http.HttpServletRequest; +import java.security.Key; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author / + */ +@Slf4j +@Component +public class TokenProvider implements InitializingBean { + + private Key signingKey; + private JwtParser jwtParser; + private final RedisUtils redisUtils; + private final SecurityProperties properties; + public static final String AUTHORITIES_UUID_KEY = "uid"; + public static final String AUTHORITIES_UID_KEY = "userId"; + + public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) { + this.properties = properties; + this.redisUtils = redisUtils; + } + + @Override + public void afterPropertiesSet() { + // 解码Base64密钥并创建签名密钥 + byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret()); + this.signingKey = Keys.hmacShaKeyFor(keyBytes); + // 初始化 JwtParser + jwtParser = Jwts.parserBuilder() + .setSigningKey(signingKey) // 使用预生成的签名密钥 + .build(); + } + + /** + * 创建Token 设置永不过期, + * Token 的时间有效性转到Redis 维护 + * + * @param user / + * @return / + */ + public String createToken(JwtUserDto user) { + // 设置参数 + Map claims = new HashMap<>(6); + // 设置用户ID + claims.put(AUTHORITIES_UID_KEY, user.getUser().getId()); + // 设置UUID,确保每次Token不一样 + claims.put(AUTHORITIES_UUID_KEY, IdUtil.simpleUUID()); + // 直接调用 Jwts.builder() 创建新实例 + return Jwts.builder() + // 设置自定义 Claims + .setClaims(claims) + // 设置主题 + .setSubject(user.getUsername()) + // 使用预生成的签名密钥和算法签名 + .signWith(signingKey, SignatureAlgorithm.HS512) + .compact(); + } + + /** + * 依据Token 获取鉴权信息 + * + * @param token / + * @return / + */ + Authentication getAuthentication(String token) { + Claims claims = getClaims(token); + User principal = new User(claims.getSubject(), "******", new ArrayList<>()); + return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>()); + } + + public Claims getClaims(String token) { + return jwtParser + .parseClaimsJws(token) + .getBody(); + } + + /** + * @param token 需要检查的token + */ + public void checkRenewal(String token) { + // 判断是否续期token,计算token的过期时间 + String loginKey = loginKey(token); + long time = redisUtils.getExpire(loginKey) * 1000; + Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time); + // 判断当前时间与过期时间的时间差 + long differ = expireDate.getTime() - System.currentTimeMillis(); + // 如果在续期检查的范围内,则续期 + if (differ <= properties.getDetect()) { + long renew = time + properties.getRenew(); + redisUtils.expire(loginKey, renew, TimeUnit.MILLISECONDS); + } + } + + public String getToken(HttpServletRequest request) { + final String requestHeader = request.getHeader(properties.getHeader()); + if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) { + return requestHeader.substring(7); + } + return null; + } + + /** + * 获取登录用户RedisKey + * @param token / + * @return key + */ + public String loginKey(String token) { + Claims claims = getClaims(token); + return properties.getOnlineKey() + claims.getSubject() + ":" + getId(token); + } + + /** + * 获取登录用户TokenKey + * @param token / + * @return / + */ + public String getId(String token) { + Claims claims = getClaims(token); + return claims.get(AUTHORITIES_UUID_KEY).toString(); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtPermissionService.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtPermissionService.java deleted file mode 100644 index c653d2059..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtPermissionService.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.zhengjie.modules.security.service; - -import me.zhengjie.modules.system.domain.Role; -import me.zhengjie.modules.system.repository.RoleRepository; -import me.zhengjie.modules.system.service.dto.UserDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Service; -import java.util.Collection; -import java.util.Set; -import java.util.stream.Collectors; - -@Service -@CacheConfig(cacheNames = "role") -public class JwtPermissionService { - - @Autowired - private RoleRepository roleRepository; - - /** - * key的名称如有修改,请同步修改 UserServiceImpl 中的 update 方法 - * @param user - * @return - */ - @Cacheable(key = "'loadPermissionByUser:' + #p0.username") - public Collection mapToGrantedAuthorities(UserDTO user) { - - System.out.println("--------------------loadPermissionByUser:" + user.getUsername() + "---------------------"); - - Set roles = roleRepository.findByUsers_Id(user.getId()); - - return roles.stream().flatMap(role -> role.getPermissions().stream()) - .map(permission -> new SimpleGrantedAuthority(permission.getName())) - .collect(Collectors.toList()); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtUserDetailsService.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtUserDetailsService.java deleted file mode 100644 index 91239a048..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtUserDetailsService.java +++ /dev/null @@ -1,56 +0,0 @@ -package me.zhengjie.modules.security.service; - -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.modules.security.security.JwtUser; -import me.zhengjie.modules.system.service.UserService; -import me.zhengjie.modules.system.service.dto.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - -/** - * @author Zheng Jie - * @date 2018-11-22 - */ -@Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class JwtUserDetailsService implements UserDetailsService { - - @Autowired - private UserService userService; - - @Autowired - private JwtPermissionService permissionService; - - @Override - public UserDetails loadUserByUsername(String username){ - - UserDTO user = userService.findByName(username); - if (user == null) { - throw new BadRequestException("账号不存在"); - } else { - return createJwtUser(user); - } - } - - public UserDetails createJwtUser(UserDTO user) { - return new JwtUser( - user.getId(), - user.getUsername(), - user.getPassword(), - user.getAvatar(), - user.getEmail(), - user.getPhone(), - Optional.ofNullable(user.getDept()).map(DeptSmallDTO::getName).orElse(null), - Optional.ofNullable(user.getJob()).map(JobSmallDTO::getName).orElse(null), - permissionService.mapToGrantedAuthorities(user), - user.getEnabled(), - user.getCreateTime(), - user.getLastPasswordResetTime() - ); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java new file mode 100644 index 000000000..553c26cc9 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java @@ -0,0 +1,148 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.modules.security.security.TokenProvider; +import me.zhengjie.utils.PageResult; +import me.zhengjie.modules.security.config.SecurityProperties; +import me.zhengjie.modules.security.service.dto.JwtUserDto; +import me.zhengjie.modules.security.service.dto.OnlineUserDto; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author Zheng Jie + * @date 2019年10月26日21:56:27 + */ +@Service +@Slf4j +@AllArgsConstructor +public class OnlineUserService { + + private final SecurityProperties properties; + private final TokenProvider tokenProvider; + private final RedisUtils redisUtils; + + /** + * 保存在线用户信息 + * @param jwtUserDto / + * @param token / + * @param request / + */ + public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){ + String dept = jwtUserDto.getUser().getDept().getName(); + String ip = StringUtils.getIp(request); + String id = tokenProvider.getId(token); + String browser = StringUtils.getBrowser(request); + String address = StringUtils.getCityInfo(ip); + OnlineUserDto onlineUserDto = null; + try { + onlineUserDto = new OnlineUserDto(id, jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date()); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + String loginKey = tokenProvider.loginKey(token); + redisUtils.set(loginKey, onlineUserDto, properties.getTokenValidityInSeconds(), TimeUnit.MILLISECONDS); + } + + /** + * 查询全部数据 + * @param username / + * @param pageable / + * @return / + */ + public PageResult getAll(String username, Pageable pageable){ + List onlineUserDtos = getAll(username); + return PageUtil.toPage( + PageUtil.paging(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos), + onlineUserDtos.size() + ); + } + + /** + * 查询全部数据,不分页 + * @param username / + * @return / + */ + public List getAll(String username){ + String loginKey = properties.getOnlineKey() + + (StringUtils.isBlank(username) ? "" : "*" + username); + List keys = redisUtils.scan(loginKey + "*"); + Collections.reverse(keys); + List onlineUserDtos = new ArrayList<>(); + for (String key : keys) { + onlineUserDtos.add(redisUtils.get(key, OnlineUserDto.class)); + } + onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime())); + return onlineUserDtos; + } + + /** + * 退出登录 + * @param token / + */ + public void logout(String token) { + String loginKey = tokenProvider.loginKey(token); + redisUtils.del(loginKey); + } + + /** + * 导出 + * @param all / + * @param response / + * @throws IOException / + */ + public void download(List all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (OnlineUserDto user : all) { + Map map = new LinkedHashMap<>(); + map.put("用户名", user.getUserName()); + map.put("部门", user.getDept()); + map.put("登录IP", user.getIp()); + map.put("登录地点", user.getAddress()); + map.put("浏览器", user.getBrowser()); + map.put("登录日期", user.getLoginTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + /** + * 查询用户 + * @param key / + * @return / + */ + public OnlineUserDto getOne(String key) { + return redisUtils.get(key, OnlineUserDto.class); + } + + /** + * 根据用户名强退用户 + * @param username / + */ + public void kickOutForUsername(String username) { + String loginKey = properties.getOnlineKey() + username + "*"; + redisUtils.scanDel(loginKey); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java new file mode 100644 index 000000000..1333ed213 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service; + +import cn.hutool.core.util.RandomUtil; +import me.zhengjie.modules.security.config.LoginProperties; +import me.zhengjie.modules.security.service.dto.JwtUserDto; +import me.zhengjie.utils.RedisUtils; +import me.zhengjie.utils.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import javax.annotation.Resource; + +/** + * @author Zheng Jie + * @description 用户缓存管理 + * @date 2022-05-26 + **/ +@Component +public class UserCacheManager { + + @Resource + private RedisUtils redisUtils; + @Value("${login.user-cache.idle-time}") + private long idleTime; + + /** + * 返回用户缓存 + * @param userName 用户名 + * @return JwtUserDto + */ + public JwtUserDto getUserCache(String userName) { + // 转小写 + userName = StringUtils.lowerCase(userName); + if (StringUtils.isNotEmpty(userName)) { + // 获取数据 + return redisUtils.get(LoginProperties.cacheKey + userName, JwtUserDto.class); + } + return null; + } + + /** + * 添加缓存到Redis + * @param userName 用户名 + */ + @Async + public void addUserCache(String userName, JwtUserDto user) { + // 转小写 + userName = StringUtils.lowerCase(userName); + if (StringUtils.isNotEmpty(userName)) { + // 添加数据, 避免数据同时过期 + long time = idleTime + RandomUtil.randomInt(900, 1800); + redisUtils.set(LoginProperties.cacheKey + userName, user, time); + } + } + + /** + * 清理用户缓存信息 + * 用户信息变更时 + * @param userName 用户名 + */ + @Async + public void cleanUserCache(String userName) { + // 转小写 + userName = StringUtils.lowerCase(userName); + if (StringUtils.isNotEmpty(userName)) { + // 清除数据 + redisUtils.del(LoginProperties.cacheKey + userName); + } + } +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java new file mode 100644 index 000000000..a28c9e7c1 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.security.service.dto.AuthorityDto; +import me.zhengjie.modules.security.service.dto.JwtUserDto; +import me.zhengjie.modules.system.service.DataService; +import me.zhengjie.modules.system.service.RoleService; +import me.zhengjie.modules.system.service.UserService; +import me.zhengjie.modules.system.service.dto.UserDto; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; +import java.util.List; + +/** + * @author Zheng Jie + * @date 2018-11-22 + */ +@Slf4j +@RequiredArgsConstructor +@Service("userDetailsService") +public class UserDetailsServiceImpl implements UserDetailsService { + private final UserService userService; + private final RoleService roleService; + private final DataService dataService; + private final UserCacheManager userCacheManager; + + @Override + public JwtUserDto loadUserByUsername(String username) { + JwtUserDto jwtUserDto = userCacheManager.getUserCache(username); + if(jwtUserDto == null){ + UserDto user = userService.getLoginData(username); + if (user == null) { + throw new BadRequestException("用户不存在"); + } else { + if (!user.getEnabled()) { + throw new BadRequestException("账号未激活!"); + } + // 获取用户的权限 + List authorities = roleService.buildPermissions(user); + // 初始化JwtUserDto + jwtUserDto = new JwtUserDto(user, dataService.getDeptIds(user), authorities); + // 添加缓存数据 + userCacheManager.addUserCache(username, jwtUserDto); + } + } + return jwtUserDto; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthUserDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthUserDto.java new file mode 100644 index 000000000..a34c83cd2 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthUserDto.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import javax.validation.constraints.NotBlank; + +/** + * @author Zheng Jie + * @date 2018-11-30 + */ +@Getter +@Setter +public class AuthUserDto { + + @NotBlank + @ApiModelProperty(value = "用户名") + private String username; + + @NotBlank + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "验证码") + private String code; + + @ApiModelProperty(value = "验证码的key") + private String uuid = ""; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthorityDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthorityDto.java new file mode 100644 index 000000000..824f9e77c --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/AuthorityDto.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; + +/** + * 避免序列化问题 + * @author Zheng Jie + * @date 2018-11-30 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AuthorityDto implements GrantedAuthority { + + @ApiModelProperty(value = "角色名") + private String authority; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/JwtUserDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/JwtUserDto.java new file mode 100644 index 000000000..9cc062709 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/JwtUserDto.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import me.zhengjie.modules.system.service.dto.UserDto; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Getter +@AllArgsConstructor +public class JwtUserDto implements UserDetails { + + @ApiModelProperty(value = "用户") + private final UserDto user; + + @ApiModelProperty(value = "数据权限") + private final List dataScopes; + + @ApiModelProperty(value = "角色权限") + private final List authorities; + + public Set getRoles() { + return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet()); + } + + @Override + @JSONField(serialize = false) + public String getPassword() { + return user.getPassword(); + } + + @Override + @JSONField(serialize = false) + public String getUsername() { + return user.getUsername(); + } + + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() { + return true; + } + + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() { + return true; + } + + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + @JSONField(serialize = false) + public boolean isEnabled() { + return user.getEnabled(); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/OnlineUserDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/OnlineUserDto.java new file mode 100644 index 000000000..354ee066b --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/dto/OnlineUserDto.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.security.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.Date; + +/** + * 在线用户 + * @author Zheng Jie + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OnlineUserDto { + + @ApiModelProperty(value = "Token编号") + private String uid; + + @ApiModelProperty(value = "用户名") + private String userName; + + @ApiModelProperty(value = "昵称") + private String nickName; + + @ApiModelProperty(value = "岗位") + private String dept; + + @ApiModelProperty(value = "浏览器") + private String browser; + + @ApiModelProperty(value = "IP") + private String ip; + + @ApiModelProperty(value = "地址") + private String address; + + @ApiModelProperty(value = "token") + private String key; + + @ApiModelProperty(value = "登录时间") + private Date loginTime; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/JwtTokenUtil.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/JwtTokenUtil.java deleted file mode 100644 index ee7517a74..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/JwtTokenUtil.java +++ /dev/null @@ -1,120 +0,0 @@ -package me.zhengjie.modules.security.utils; - -import io.jsonwebtoken.*; -import io.jsonwebtoken.impl.DefaultClock; -import me.zhengjie.modules.security.security.JwtUser; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -@Component -public class JwtTokenUtil implements Serializable { - - private static final long serialVersionUID = -3301605591108950415L; - private Clock clock = DefaultClock.INSTANCE; - - @Value("${jwt.secret}") - private String secret; - - @Value("${jwt.expiration}") - private Long expiration; - - @Value("${jwt.header}") - private String tokenHeader; - - public String getUsernameFromToken(String token) { - return getClaimFromToken(token, Claims::getSubject); - } - - public Date getIssuedAtDateFromToken(String token) { - return getClaimFromToken(token, Claims::getIssuedAt); - } - - public Date getExpirationDateFromToken(String token) { - return getClaimFromToken(token, Claims::getExpiration); - } - - public T getClaimFromToken(String token, Function claimsResolver) { - final Claims claims = getAllClaimsFromToken(token); - return claimsResolver.apply(claims); - } - - private Claims getAllClaimsFromToken(String token) { - return Jwts.parser() - .setSigningKey(secret) - .parseClaimsJws(token) - .getBody(); - } - - private Boolean isTokenExpired(String token) { - final Date expiration = getExpirationDateFromToken(token); - return expiration.before(clock.now()); - } - - private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { - return (lastPasswordReset != null && created.before(lastPasswordReset)); - } - - private Boolean ignoreTokenExpiration(String token) { - // here you specify tokens, for that the expiration is ignored - return false; - } - - public String generateToken(UserDetails userDetails) { - Map claims = new HashMap<>(); - return doGenerateToken(claims, userDetails.getUsername()); - } - - private String doGenerateToken(Map claims, String subject) { - final Date createdDate = clock.now(); - final Date expirationDate = calculateExpirationDate(createdDate); - - return Jwts.builder() - .setClaims(claims) - .setSubject(subject) - .setIssuedAt(createdDate) - .setExpiration(expirationDate) - .signWith(SignatureAlgorithm.HS512, secret) - .compact(); - } - - public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { - final Date created = getIssuedAtDateFromToken(token); - return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) - && (!isTokenExpired(token) || ignoreTokenExpiration(token)); - } - - public String refreshToken(String token) { - final Date createdDate = clock.now(); - final Date expirationDate = calculateExpirationDate(createdDate); - - final Claims claims = getAllClaimsFromToken(token); - claims.setIssuedAt(createdDate); - claims.setExpiration(expirationDate); - - return Jwts.builder() - .setClaims(claims) - .signWith(SignatureAlgorithm.HS512, secret) - .compact(); - } - - public Boolean validateToken(String token, UserDetails userDetails) { - JwtUser user = (JwtUser) userDetails; - final Date created = getIssuedAtDateFromToken(token); -// final Date expiration = getExpirationDateFromToken(token); -// 如果token存在,且token创建日期 > 最后修改密码的日期 则代表token有效 - return (!isTokenExpired(token) - && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()) - ); - } - - private Date calculateExpirationDate(Date createdDate) { - return new Date(createdDate.getTime() + expiration); - } -} - diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/VerifyCodeUtils.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/VerifyCodeUtils.java deleted file mode 100644 index 74eb04a66..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/VerifyCodeUtils.java +++ /dev/null @@ -1,203 +0,0 @@ -package me.zhengjie.modules.security.utils; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.Random; -import javax.imageio.ImageIO; - -/** - * @author https://blog.csdn.net/ruixue0117/article/details/22829557 - * @date 2019-6-20 17:28:53 - */ -public class VerifyCodeUtils{ - - //使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 - public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; - private static Random random = new Random(); - - - /** - * 使用系统默认字符源生成验证码 - * @param verifySize 验证码长度 - * @return - */ - public static String generateVerifyCode(int verifySize){ - return generateVerifyCode(verifySize, VERIFY_CODES); - } - /** - * 使用指定源生成验证码 - * @param verifySize 验证码长度 - * @param sources 验证码字符源 - * @return - */ - public static String generateVerifyCode(int verifySize, String sources){ - if(sources == null || sources.length() == 0){ - sources = VERIFY_CODES; - } - int codesLen = sources.length(); - Random rand = new Random(System.currentTimeMillis()); - StringBuilder verifyCode = new StringBuilder(verifySize); - for(int i = 0; i < verifySize; i++){ - verifyCode.append(sources.charAt(rand.nextInt(codesLen-1))); - } - return verifyCode.toString(); - } - - /** - * 输出指定验证码图片流 - * @param w - * @param h - * @param os - * @param code - * @throws IOException - */ - public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{ - int verifySize = code.length(); - BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - Random rand = new Random(); - Graphics2D g2 = image.createGraphics(); - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); - Color[] colors = new Color[5]; - Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN, - Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, - Color.PINK, Color.YELLOW }; - float[] fractions = new float[colors.length]; - for(int i = 0; i < colors.length; i++){ - colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)]; - fractions[i] = rand.nextFloat(); - } - Arrays.sort(fractions); - - g2.setColor(Color.GRAY);// 设置边框色 - g2.fillRect(0, 0, w, h); - - Color c = getRandColor(200, 250); - g2.setColor(c);// 设置背景色 - g2.fillRect(0, 2, w, h-4); - - //绘制干扰线 - Random random = new Random(); - g2.setColor(getRandColor(160, 200));// 设置线条的颜色 - for (int i = 0; i < 20; i++) { - int x = random.nextInt(w - 1); - int y = random.nextInt(h - 1); - int xl = random.nextInt(6) + 1; - int yl = random.nextInt(12) + 1; - g2.drawLine(x, y, x + xl + 40, y + yl + 20); - } - - // 添加噪点 - float yawpRate = 0.05f;// 噪声率 - int area = (int) (yawpRate * w * h); - for (int i = 0; i < area; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); - int rgb = getRandomIntColor(); - image.setRGB(x, y, rgb); - } - - shear(g2, w, h, c);// 使图片扭曲 - - g2.setColor(getRandColor(100, 160)); - int fontSize = h-4; - Font font = new Font("Algerian", Font.ITALIC, fontSize); - g2.setFont(font); - char[] chars = code.toCharArray(); - for(int i = 0; i < verifySize; i++){ - AffineTransform affine = new AffineTransform(); - affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2); - g2.setTransform(affine); - g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10); - } - - g2.dispose(); - ImageIO.write(image, "jpg", os); - } - - private static Color getRandColor(int fc, int bc) { - if (fc > 255) - fc = 255; - if (bc > 255) - bc = 255; - int r = fc + random.nextInt(bc - fc); - int g = fc + random.nextInt(bc - fc); - int b = fc + random.nextInt(bc - fc); - return new Color(r, g, b); - } - - private static int getRandomIntColor() { - int[] rgb = getRandomRgb(); - int color = 0; - for (int c : rgb) { - color = color << 8; - color = color | c; - } - return color; - } - - private static int[] getRandomRgb() { - int[] rgb = new int[3]; - for (int i = 0; i < 3; i++) { - rgb[i] = random.nextInt(255); - } - return rgb; - } - - private static void shear(Graphics g, int w1, int h1, Color color) { - shearX(g, w1, h1, color); - shearY(g, w1, h1, color); - } - - private static void shearX(Graphics g, int w1, int h1, Color color) { - - int period = random.nextInt(2); - - boolean borderGap = true; - int frames = 1; - int phase = random.nextInt(2); - - for (int i = 0; i < h1; i++) { - double d = (double) (period >> 1) - * Math.sin((double) i / (double) period - + (6.2831853071795862D * (double) phase) - / (double) frames); - g.copyArea(0, i, w1, 1, (int) d, 0); - if (borderGap) { - g.setColor(color); - g.drawLine((int) d, i, 0, i); - g.drawLine((int) d + w1, i, w1, i); - } - } - - } - - private static void shearY(Graphics g, int w1, int h1, Color color) { - - int period = random.nextInt(40) + 10; // 50; - - boolean borderGap = true; - int frames = 20; - int phase = 7; - for (int i = 0; i < w1; i++) { - double d = (double) (period >> 1) - * Math.sin((double) i / (double) period - + (6.2831853071795862D * (double) phase) - / (double) frames); - g.copyArea(i, 0, 1, h1, 0, (int) d); - if (borderGap) { - g.setColor(color); - g.drawLine(i, (int) d, i, 0); - g.drawLine(i, (int) d + h1, i, h1); - } - - } - } -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dept.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dept.java index 5fe611c8f..4f9a669b3 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dept.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dept.java @@ -1,13 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Data; -import org.hibernate.annotations.CreationTimestamp; +import com.alibaba.fastjson2.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import java.sql.Timestamp; import java.io.Serializable; +import java.util.Objects; import java.util.Set; /** @@ -15,43 +32,55 @@ * @date 2019-03-25 */ @Entity -@Data -@Table(name="dept") -public class Dept implements Serializable { +@Getter +@Setter +@Table(name="sys_dept") +public class Dept extends BaseEntity implements Serializable { - /** - * ID - */ @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") + @Column(name = "dept_id") @NotNull(groups = Update.class) + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 名称 - */ - @Column(name = "name",nullable = false) + @JSONField(serialize = false) + @ManyToMany(mappedBy = "depts") + @ApiModelProperty(value = "角色") + private Set roles; + + @ApiModelProperty(value = "排序") + private Integer deptSort; + @NotBlank + @ApiModelProperty(value = "部门名称") private String name; @NotNull + @ApiModelProperty(value = "是否启用") private Boolean enabled; - /** - * 上级部门 - */ - @Column(name = "pid",nullable = false) - @NotNull + @ApiModelProperty(value = "上级部门") private Long pid; - @JsonIgnore - @ManyToMany(mappedBy = "depts") - private Set roles; + @ApiModelProperty(value = "子节点数目", hidden = true) + private Integer subCount = 0; - @Column(name = "create_time") - @CreationTimestamp - private Timestamp createTime; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Dept dept = (Dept) o; + return Objects.equals(id, dept.id) && + Objects.equals(name, dept.name); + } - public @interface Update {} + @Override + public int hashCode() { + return Objects.hash(id, name); + } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dict.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dict.java index 83183d3ef..859daf781 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dict.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Dict.java @@ -1,6 +1,24 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; -import lombok.Data; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; @@ -12,31 +30,25 @@ * @date 2019-04-10 */ @Entity -@Data -@Table(name="dict") -public class Dict implements Serializable { +@Getter +@Setter +@Table(name="sys_dict") +public class Dict extends BaseEntity implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") + @Column(name = "dict_id") @NotNull(groups = Update.class) + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 字典名称 - */ - @Column(name = "name",nullable = false,unique = true) - @NotBlank - private String name; - - /** - * 描述 - */ - @Column(name = "remark") - private String remark; - @OneToMany(mappedBy = "dict",cascade={CascadeType.PERSIST,CascadeType.REMOVE}) private List dictDetails; - public @interface Update {} + @NotBlank + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/DictDetail.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/DictDetail.java index a3799a31b..5287c960a 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/DictDetail.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/DictDetail.java @@ -1,6 +1,24 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; -import lombok.Data; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; import javax.validation.constraints.NotNull; import java.io.Serializable; @@ -10,40 +28,29 @@ * @date 2019-04-10 */ @Entity -@Data -@Table(name="dict_detail") -public class DictDetail implements Serializable { +@Getter +@Setter +@Table(name="sys_dict_detail") +public class DictDetail extends BaseEntity implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") + @Column(name = "detail_id") @NotNull(groups = Update.class) + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 字典标签 - */ - @Column(name = "label",nullable = false) + @JoinColumn(name = "dict_id") + @ManyToOne(fetch=FetchType.LAZY) + @ApiModelProperty(value = "字典", hidden = true) + private Dict dict; + + @ApiModelProperty(value = "字典标签") private String label; - /** - * 字典值 - */ - @Column(name = "value",nullable = false) + @ApiModelProperty(value = "字典值") private String value; - /** - * 排序 - */ - @Column(name = "sort") - private String sort = "999"; - - /** - * 字典id - */ - @ManyToOne(fetch=FetchType.LAZY) - @JoinColumn(name = "dict_id") - private Dict dict; - - public @interface Update {} + @ApiModelProperty(value = "排序") + private Integer dictSort = 999; } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Job.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Job.java index 58f3fd95c..343411cb4 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Job.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Job.java @@ -1,61 +1,73 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; -import lombok.Data; -import org.hibernate.annotations.*; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; -import javax.persistence.Entity; -import javax.persistence.Table; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import java.sql.Timestamp; import java.io.Serializable; +import java.util.Objects; /** * @author Zheng Jie * @date 2019-03-29 */ @Entity -@Data -@Table(name="job") -public class Job implements Serializable { +@Getter +@Setter +@Table(name="sys_job") +public class Job extends BaseEntity implements Serializable { - /** - * ID - */ @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") + @Column(name = "job_id") @NotNull(groups = Update.class) + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 名称 - */ - @Column(name = "name",nullable = false) @NotBlank + @ApiModelProperty(value = "岗位名称") private String name; - @Column(unique = true) @NotNull - private Long sort; + @ApiModelProperty(value = "岗位排序") + private Long jobSort; - /** - * 状态 - */ - @Column(name = "enabled",nullable = false) @NotNull + @ApiModelProperty(value = "是否启用") private Boolean enabled; - @OneToOne - @JoinColumn(name = "dept_id") - private Dept dept; - - /** - * 创建日期 - */ - @Column(name = "create_time") - @CreationTimestamp - private Timestamp createTime; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Job job = (Job) o; + return Objects.equals(id, job.id); + } - public @interface Update {} + @Override + public int hashCode() { + return Objects.hash(id); + } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Menu.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Menu.java index b9f684f0a..34734e2e7 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Menu.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Menu.java @@ -1,14 +1,28 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.alibaba.fastjson2.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.io.Serializable; -import java.sql.Timestamp; import java.util.Objects; import java.util.Set; @@ -19,54 +33,71 @@ @Entity @Getter @Setter -@Table(name = "menu") -public class Menu implements Serializable { +@Table(name = "sys_menu") +public class Menu extends BaseEntity implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "menu_id") @NotNull(groups = {Update.class}) + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotBlank - private String name; + @JSONField(serialize = false) + @ManyToMany(mappedBy = "menus") + @ApiModelProperty(value = "菜单角色") + private Set roles; - @Column(unique = true) - @NotNull - private Long sort; + @ApiModelProperty(value = "菜单标题") + private String title; - @Column(name = "path") - private String path; + @Column(name = "name") + @ApiModelProperty(value = "菜单组件名称") + private String componentName; + + @ApiModelProperty(value = "排序") + private Integer menuSort = 999; + @ApiModelProperty(value = "组件路径") private String component; + @ApiModelProperty(value = "路由地址") + private String path; + + @ApiModelProperty(value = "菜单类型,目录、菜单、按钮") + private Integer type; + + @ApiModelProperty(value = "权限标识") + private String permission; + + @ApiModelProperty(value = "菜单图标") private String icon; - /** - * 上级菜单ID - */ - @Column(name = "pid",nullable = false) - private Long pid; + @Column(columnDefinition = "bit(1) default 0") + @ApiModelProperty(value = "缓存") + private Boolean cache; - /** - * 是否为外链 true/false - */ - @Column(name = "i_frame") - private Boolean iFrame; + @Column(columnDefinition = "bit(1) default 0") + @ApiModelProperty(value = "是否隐藏") + private Boolean hidden; - @ManyToMany(mappedBy = "menus") - @JsonIgnore - private Set roles; + @ApiModelProperty(value = "上级菜单") + private Long pid; - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; + @ApiModelProperty(value = "子节点数目", hidden = true) + private Integer subCount = 0; - public interface Update{} + @ApiModelProperty(value = "外链菜单") + private Boolean iFrame; @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Menu menu = (Menu) o; return Objects.equals(id, menu.id); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Permission.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Permission.java deleted file mode 100644 index 692721867..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Permission.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.zhengjie.modules.system.domain; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; -import javax.persistence.*; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import java.io.Serializable; -import java.sql.Timestamp; -import java.util.Set; - -/** - * @author Zheng Jie - * @date 2018-12-03 - */ -@Entity -@Getter -@Setter -@Table(name = "permission") -public class Permission implements Serializable{ - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @NotNull(groups = {Update.class}) - private Long id; - - @NotBlank - private String name; - - /** - * 上级类目 - */ - @NotNull - @Column(name = "pid",nullable = false) - private Long pid; - - @NotBlank - private String alias; - - @JsonIgnore - @ManyToMany(mappedBy = "permissions") - private Set roles; - - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; - - @Override - public String toString() { - return "Permission{" + - "id=" + id + - ", name='" + name + '\'' + - ", pid=" + pid + - ", alias='" + alias + '\'' + - ", createTime=" + createTime + - '}'; - } - - public interface Update{} -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Role.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Role.java index c44c97fb3..1225e5ea8 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Role.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/Role.java @@ -1,14 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.alibaba.fastjson2.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; +import me.zhengjie.base.BaseEntity; +import me.zhengjie.utils.enums.DataScopeEnum; + import javax.persistence.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.io.Serializable; -import java.sql.Timestamp; import java.util.Objects; import java.util.Set; @@ -17,66 +34,60 @@ * @author Zheng Jie * @date 2018-11-22 */ -@Entity -@Table(name = "role") @Getter @Setter -public class Role implements Serializable { +@Entity +@Table(name = "sys_role") +public class Role extends BaseEntity implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "role_id") @NotNull(groups = {Update.class}) + @GeneratedValue(strategy = GenerationType.IDENTITY) + @ApiModelProperty(value = "ID", hidden = true) private Long id; - @Column(nullable = false) - @NotBlank - private String name; - - // 数据权限类型 全部 、 本级 、 自定义 - @Column(name = "data_scope") - private String dataScope = "本级"; - - // 数值越小,级别越大 - @Column(name = "level") - private Integer level = 3; - - @Column - private String remark; - - @JsonIgnore + @JSONField(serialize = false) @ManyToMany(mappedBy = "roles") + @ApiModelProperty(value = "用户", hidden = true) private Set users; - @ManyToMany - @JoinTable(name = "roles_permissions", joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id",referencedColumnName = "id")}) - private Set permissions; - - @ManyToMany - @JoinTable(name = "roles_menus", joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "menu_id",referencedColumnName = "id")}) + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "sys_roles_menus", + joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")}, + inverseJoinColumns = {@JoinColumn(name = "menu_id",referencedColumnName = "menu_id")}) + @ApiModelProperty(value = "菜单", hidden = true) private Set menus; @ManyToMany - @JoinTable(name = "roles_depts", joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "dept_id",referencedColumnName = "id")}) + @JoinTable(name = "sys_roles_depts", + joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")}, + inverseJoinColumns = {@JoinColumn(name = "dept_id",referencedColumnName = "dept_id")}) + @ApiModelProperty(value = "部门", hidden = true) private Set depts; - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; + @NotBlank + @ApiModelProperty(value = "名称", hidden = true) + private String name; - @Override - public String toString() { - return "Role{" + - "id=" + id + - ", name='" + name + '\'' + - ", remark='" + remark + '\'' + - ", createDateTime=" + createTime + - '}'; - } + @ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义") + private String dataScope = DataScopeEnum.THIS_LEVEL.getValue(); + + @Column(name = "level") + @ApiModelProperty(value = "级别,数值越小,级别越大") + private Integer level = 3; + + @ApiModelProperty(value = "描述") + private String description; @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Role role = (Role) o; return Objects.equals(id, role.id); } @@ -85,6 +96,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(id); } - - public interface Update{} } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/User.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/User.java index b801b5584..7e9b0aa66 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/User.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/User.java @@ -1,15 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain; +import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; +import me.zhengjie.base.BaseEntity; import javax.persistence.*; +import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; import java.io.Serializable; -import java.sql.Timestamp; import java.util.Date; +import java.util.Objects; import java.util.Set; /** @@ -19,64 +35,91 @@ @Entity @Getter @Setter -@Table(name="user") -public class User implements Serializable { +@Table(name="sys_user") +public class User extends BaseEntity implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") @NotNull(groups = Update.class) + @GeneratedValue(strategy = GenerationType.IDENTITY) + @ApiModelProperty(value = "ID", hidden = true) private Long id; + @ManyToMany(fetch = FetchType.EAGER) + @ApiModelProperty(value = "用户角色") + @JoinTable(name = "sys_users_roles", + joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")}, + inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")}) + private Set roles; + + @ManyToMany(fetch = FetchType.EAGER) + @ApiModelProperty(value = "用户岗位") + @JoinTable(name = "sys_users_jobs", + joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")}, + inverseJoinColumns = {@JoinColumn(name = "job_id",referencedColumnName = "job_id")}) + private Set jobs; + + @OneToOne + @JoinColumn(name = "dept_id") + @ApiModelProperty(value = "用户部门") + private Dept dept; + @NotBlank @Column(unique = true) + @ApiModelProperty(value = "用户名称") private String username; - private String avatar; + @NotBlank + @ApiModelProperty(value = "用户昵称") + private String nickName; + @Email @NotBlank - @Pattern(regexp = "([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}",message = "格式错误") + @ApiModelProperty(value = "邮箱") private String email; @NotBlank + @ApiModelProperty(value = "电话号码") private String phone; - @NotNull - private Boolean enabled; + @ApiModelProperty(value = "用户性别") + private String gender; - private String password; + @ApiModelProperty(value = "头像真实名称",hidden = true) + private String avatarName; - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; + @ApiModelProperty(value = "头像存储的路径", hidden = true) + private String avatarPath; - @Column(name = "last_password_reset_time") - private Date lastPasswordResetTime; + @ApiModelProperty(value = "密码") + private String password; - @ManyToMany - @JoinTable(name = "users_roles", joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")}) - private Set roles; + @NotNull + @ApiModelProperty(value = "是否启用") + private Boolean enabled; - @OneToOne - @JoinColumn(name = "job_id") - private Job job; + @ApiModelProperty(value = "是否为admin账号", hidden = true) + private Boolean isAdmin = false; - @OneToOne - @JoinColumn(name = "dept_id") - private Dept dept; + @Column(name = "pwd_reset_time") + @ApiModelProperty(value = "最后修改密码的时间", hidden = true) + private Date pwdResetTime; @Override - public String toString() { - return "User{" + - "id=" + id + - ", username='" + username + '\'' + - ", avatar='" + avatar + '\'' + - ", email='" + email + '\'' + - ", enabled=" + enabled + - ", password='" + password + '\'' + - ", createTime=" + createTime + - ", lastPasswordResetTime=" + lastPasswordResetTime + - '}'; + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + User user = (User) o; + return Objects.equals(id, user.id) && + Objects.equals(username, user.username); } - public @interface Update {} + @Override + public int hashCode() { + return Objects.hash(id, username); + } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuMetaVo.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuMetaVo.java index d6fc9ddd2..a0a45fbfc 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuMetaVo.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuMetaVo.java @@ -1,5 +1,21 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain.vo; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @@ -12,7 +28,12 @@ @AllArgsConstructor public class MenuMetaVo implements Serializable { + @ApiModelProperty(value = "菜单标题") private String title; + @ApiModelProperty(value = "菜单图标") private String icon; + + @ApiModelProperty(value = "缓存") + private Boolean noCache; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuVo.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuVo.java index ea0610ba1..d9c0aec15 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuVo.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/MenuVo.java @@ -1,8 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain.vo; -import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; - import java.io.Serializable; import java.util.List; @@ -12,20 +26,29 @@ * @date 2018-12-20 */ @Data -@JsonInclude(JsonInclude.Include.NON_EMPTY) public class MenuVo implements Serializable { + @ApiModelProperty(value = "菜单名称") private String name; + @ApiModelProperty(value = "路径") private String path; + @ApiModelProperty(value = "隐藏状态") + private Boolean hidden; + + @ApiModelProperty(value = "重定向") private String redirect; + @ApiModelProperty(value = "组件") private String component; + @ApiModelProperty(value = "总是显示") private Boolean alwaysShow; + @ApiModelProperty(value = "元数据") private MenuMetaVo meta; + @ApiModelProperty(value = "子路由") private List children; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/UserPassVo.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/UserPassVo.java index 86288673a..4cb1e2ccd 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/UserPassVo.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/domain/vo/UserPassVo.java @@ -1,5 +1,21 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.domain.vo; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** @@ -10,7 +26,9 @@ @Data public class UserPassVo { + @ApiModelProperty(value = "旧密码") private String oldPass; + @ApiModelProperty(value = "新密码") private String newPass; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DeptRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DeptRepository.java index 12feee127..fa9ed98cc 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DeptRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DeptRepository.java @@ -1,10 +1,25 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.Dept; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; - import java.util.List; import java.util.Set; @@ -12,17 +27,43 @@ * @author Zheng Jie * @date 2019-03-25 */ -public interface DeptRepository extends JpaRepository, JpaSpecificationExecutor { +public interface DeptRepository extends JpaRepository, JpaSpecificationExecutor { /** - * findByPid - * @param id - * @return + * 根据 PID 查询 + * @param id pid + * @return / */ List findByPid(Long id); - @Query(value = "select name from dept where id = ?1",nativeQuery = true) - String findNameById(Long id); + /** + * 获取顶级部门 + * @return / + */ + List findByPidIsNull(); + + /** + * 根据角色ID 查询 + * @param roleId 角色ID + * @return / + */ + @Query(value = "select d.* from sys_dept d, sys_roles_depts r where " + + "d.dept_id = r.dept_id and r.role_id = ?1", nativeQuery = true) + Set findByRoleId(Long roleId); - Set findByRoles_Id(Long id); + /** + * 判断是否存在子节点 + * @param pid / + * @return / + */ + int countByPid(Long pid); + + /** + * 根据ID更新sub_count + * @param count / + * @param id / + */ + @Modifying + @Query(value = " update sys_dept set sub_count = ?1 where dept_id = ?2 ",nativeQuery = true) + void updateSubCntById(Integer count, Long id); } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictDetailRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictDetailRepository.java index 6a8a036f0..4b73eb7bf 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictDetailRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictDetailRepository.java @@ -1,12 +1,36 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.DictDetail; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.List; + /** * @author Zheng Jie * @date 2019-04-10 */ -public interface DictDetailRepository extends JpaRepository, JpaSpecificationExecutor { +public interface DictDetailRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据字典名称查询 + * @param name / + * @return / + */ + List findByDictName(String name); } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictRepository.java index 699f130b8..6622b428a 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/DictRepository.java @@ -1,12 +1,43 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.Dict; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.List; +import java.util.Set; + /** * @author Zheng Jie * @date 2019-04-10 */ -public interface DictRepository extends JpaRepository, JpaSpecificationExecutor { +public interface DictRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 删除 + * @param ids / + */ + void deleteByIdIn(Set ids); + + /** + * 查询 + * @param ids / + * @return / + */ + List findByIdIn(Set ids); } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/JobRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/JobRepository.java index 38908a28a..3454efcb8 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/JobRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/JobRepository.java @@ -1,12 +1,42 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.Job; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.Set; + /** * @author Zheng Jie * @date 2019-03-29 */ -public interface JobRepository extends JpaRepository, JpaSpecificationExecutor { +public interface JobRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据名称查询 + * @param name 名称 + * @return / + */ + Job findByName(String name); + + /** + * 根据Id删除 + * @param ids / + */ + void deleteAllByIdIn(Set ids); } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java index 220ca0612..56a84cdd5 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java @@ -1,11 +1,25 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.Menu; -import me.zhengjie.modules.system.domain.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -import java.util.Arrays; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -14,21 +28,58 @@ * @author Zheng Jie * @date 2018-12-17 */ -public interface MenuRepository extends JpaRepository, JpaSpecificationExecutor { +public interface MenuRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据菜单标题查询 + * @param title 菜单标题 + * @return / + */ + Menu findByTitle(String title); + + /** + * 根据组件名称查询 + * @param name 组件名称 + * @return / + */ + Menu findByComponentName(String name); /** - * findByName - * @param name - * @return + * 根据菜单的 PID 查询 + * @param pid / + * @return / */ - Menu findByName(String name); + List findByPidOrderByMenuSort(long pid); /** - * findByPid - * @param pid - * @return + * 查询顶级菜单 + * @return / */ - List findByPid(long pid); + List findByPidIsNullOrderByMenuSort(); - LinkedHashSet findByRoles_IdOrderBySortAsc(Long id); + /** + * 根据角色ID与菜单类型查询菜单 + * @param roleIds roleIDs + * @param type 类型 + * @return / + */ + @Query(value = "SELECT m.* FROM sys_menu m, sys_roles_menus r WHERE " + + "m.menu_id = r.menu_id AND r.role_id IN ?1 AND type != ?2 order by m.menu_sort asc",nativeQuery = true) + LinkedHashSet findByRoleIdsAndTypeNot(Set roleIds, int type); + + /** + * 获取节点数量 + * @param id / + * @return / + */ + int countByPid(Long id); + + /** + * 更新节点数目 + * @param count / + * @param menuId / + */ + @Modifying + @Query(value = " update sys_menu set sub_count = ?1 where menu_id = ?2 ",nativeQuery = true) + void updateSubCntById(int count, Long menuId); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/PermissionRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/PermissionRepository.java deleted file mode 100644 index 3d586fcd2..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/PermissionRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.zhengjie.modules.system.repository; - -import me.zhengjie.modules.system.domain.Permission; -import me.zhengjie.modules.system.domain.Role; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -/** - * @author Zheng Jie - * @date 2018-12-03 - */ -public interface PermissionRepository extends JpaRepository, JpaSpecificationExecutor { - - /** - * findByName - * @param name - * @return - */ - Permission findByName(String name); - - /** - * findByPid - * @param pid - * @return - */ - List findByPid(long pid); -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/RoleRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/RoleRepository.java index 1cd840b79..7e3793296 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/RoleRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/RoleRepository.java @@ -1,25 +1,80 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import java.util.List; import java.util.Set; /** * @author Zheng Jie * @date 2018-12-03 */ -public interface RoleRepository extends JpaRepository, JpaSpecificationExecutor { +public interface RoleRepository extends JpaRepository, JpaSpecificationExecutor { /** - * findByName - * @param name - * @return + * 根据名称查询 + * @param name / + * @return / */ Role findByName(String name); - Set findByUsers_Id(Long id); + /** + * 删除多个角色 + * @param ids / + */ + void deleteAllByIdIn(Set ids); + + /** + * 根据用户ID查询 + * @param id 用户ID + * @return / + */ + @Query(value = "SELECT r.* FROM sys_role r, sys_users_roles u WHERE " + + "r.role_id = u.role_id AND u.user_id = ?1",nativeQuery = true) + Set findByUserId(Long id); + + /** + * 解绑角色菜单 + * @param id 菜单ID + */ + @Modifying + @Query(value = "delete from sys_roles_menus where menu_id = ?1",nativeQuery = true) + void untiedMenu(Long id); - Set findByMenus_Id(Long id); + /** + * 根据部门查询 + * @param deptIds / + * @return / + */ + @Query(value = "select count(1) from sys_role r, sys_roles_depts d where " + + "r.role_id = d.role_id and d.dept_id in ?1",nativeQuery = true) + int countByDepts(Set deptIds); + + /** + * 根据菜单Id查询 + * @param menuIds / + * @return / + */ + @Query(value = "SELECT r.* FROM sys_role r, sys_roles_menus m WHERE " + + "r.role_id = m.role_id AND m.menu_id in ?1",nativeQuery = true) + List findInMenuId(List menuIds); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/UserRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/UserRepository.java index 6ccf3fdb5..979f0f319 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/UserRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/UserRepository.java @@ -1,60 +1,139 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.repository; import me.zhengjie.modules.system.domain.User; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import java.util.Date; import java.util.List; +import java.util.Set; /** * @author Zheng Jie * @date 2018-11-22 */ -public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { +public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { /** - * findByUsername - * @param username - * @return + * 根据用户名查询 + * @param username 用户名 + * @return / */ User findByUsername(String username); /** - * findByEmail - * @param email - * @return + * 根据邮箱查询 + * @param email 邮箱 + * @return / */ User findByEmail(String email); + /** + * 根据手机号查询 + * @param phone 手机号 + * @return / + */ + User findByPhone(String phone); + /** * 修改密码 - * @param username - * @param pass + * @param username 用户名 + * @param pass 密码 + * @param lastPasswordResetTime / */ @Modifying - @Query(value = "update user set password = ?2 , last_password_reset_time = ?3 where username = ?1",nativeQuery = true) + @Query(value = "update sys_user set password = ?2 , pwd_reset_time = ?3 where username = ?1",nativeQuery = true) void updatePass(String username, String pass, Date lastPasswordResetTime); /** - * 修改头像 - * @param username - * @param url + * 修改邮箱 + * @param username 用户名 + * @param email 邮箱 */ @Modifying - @Query(value = "update user set avatar = ?2 where username = ?1",nativeQuery = true) - void updateAvatar(String username, String url); + @Query(value = "update sys_user set email = ?2 where username = ?1",nativeQuery = true) + void updateEmail(String username, String email); /** - * 修改邮箱 - * @param username - * @param email + * 根据角色查询用户 + * @param roleId / + * @return / + */ + @Query(value = "SELECT u.* FROM sys_user u, sys_users_roles r WHERE" + + " u.user_id = r.user_id AND r.role_id = ?1", nativeQuery = true) + List findByRoleId(Long roleId); + + /** + * 根据角色中的部门查询 + * @param deptId / + * @return / + */ + @Query(value = "SELECT u.* FROM sys_user u, sys_users_roles r, sys_roles_depts d WHERE " + + "u.user_id = r.user_id AND r.role_id = d.role_id AND d.dept_id = ?1 group by u.user_id", nativeQuery = true) + List findByRoleDeptId(Long deptId); + + /** + * 根据菜单查询 + * @param id 菜单ID + * @return / + */ + @Query(value = "SELECT u.* FROM sys_user u, sys_users_roles ur, sys_roles_menus rm WHERE\n" + + "u.user_id = ur.user_id AND ur.role_id = rm.role_id AND rm.menu_id = ?1 group by u.user_id", nativeQuery = true) + List findByMenuId(Long id); + + /** + * 根据Id删除 + * @param ids / + */ + void deleteAllByIdIn(Set ids); + + /** + * 根据岗位查询 + * @param ids / + * @return / + */ + @Query(value = "SELECT count(1) FROM sys_user u, sys_users_jobs j WHERE u.user_id = j.user_id AND j.job_id IN ?1", nativeQuery = true) + int countByJobs(Set ids); + + /** + * 根据部门查询 + * @param deptIds / + * @return / + */ + @Query(value = "SELECT count(1) FROM sys_user u WHERE u.dept_id IN ?1", nativeQuery = true) + int countByDepts(Set deptIds); + + /** + * 根据角色查询 + * @param ids / + * @return / + */ + @Query(value = "SELECT count(1) FROM sys_user u, sys_users_roles r WHERE " + + "u.user_id = r.user_id AND r.role_id in ?1", nativeQuery = true) + int countByRoles(Set ids); + + /** + * 重置密码 + * @param ids 、 + * @param pwd 、 */ @Modifying - @Query(value = "update user set email = ?2 where username = ?1",nativeQuery = true) - void updateEmail(String username, String email); + @Query(value = "update sys_user set password = ?2 where user_id in ?1",nativeQuery = true) + void resetPwd(Set ids, String pwd); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DeptController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DeptController.java index 39d90436e..6cd96b395 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DeptController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DeptController.java @@ -1,69 +1,129 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; -import me.zhengjie.aop.log.Log; -import me.zhengjie.config.DataScope; +import cn.hutool.core.collection.CollectionUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.domain.Dept; import me.zhengjie.modules.system.service.DeptService; -import me.zhengjie.modules.system.service.dto.DeptDTO; +import me.zhengjie.modules.system.service.dto.DeptDto; import me.zhengjie.modules.system.service.dto.DeptQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.PageResult; +import me.zhengjie.utils.PageUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.stream.Collectors; /** * @author Zheng Jie * @date 2019-03-25 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@Api(tags = "系统:部门管理") +@RequestMapping("/api/dept") public class DeptController { - @Autowired - private DeptService deptService; + private final DeptService deptService; + private static final String ENTITY_NAME = "dept"; - @Autowired - private DataScope dataScope; + @ApiOperation("导出部门数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('dept:list')") + public void exportDept(HttpServletResponse response, DeptQueryCriteria criteria) throws Exception { + deptService.download(deptService.queryAll(criteria, false), response); + } - private static final String ENTITY_NAME = "dept"; + @ApiOperation("查询部门") + @GetMapping + @PreAuthorize("@el.check('user:list','dept:list')") + public ResponseEntity> queryDept(DeptQueryCriteria criteria) throws Exception { + List depts = deptService.queryAll(criteria, true); + return new ResponseEntity<>(PageUtil.toPage(depts, depts.size()),HttpStatus.OK); + } - @Log("查询部门") - @GetMapping(value = "/dept") - @PreAuthorize("hasAnyRole('ADMIN','USER_ALL','USER_SELECT','DEPT_ALL','DEPT_SELECT')") - public ResponseEntity getDepts(DeptQueryCriteria criteria){ - // 数据权限 - criteria.setIds(dataScope.getDeptIds()); - List deptDTOS = deptService.queryAll(criteria); - return new ResponseEntity(deptService.buildTree(deptDTOS),HttpStatus.OK); + @ApiOperation("查询部门:根据ID获取同级与上级数据") + @PostMapping("/superior") + @PreAuthorize("@el.check('user:list','dept:list')") + public ResponseEntity getDeptSuperior(@RequestBody List ids, + @RequestParam(defaultValue = "false") Boolean exclude) { + Set deptSet = new LinkedHashSet<>(); + for (Long id : ids) { + DeptDto deptDto = deptService.findById(id); + List depts = deptService.getSuperior(deptDto, new ArrayList<>()); + if(exclude){ + for (DeptDto dept : depts) { + if(dept.getId().equals(deptDto.getPid())) { + dept.setSubCount(dept.getSubCount() - 1); + } + } + // 编辑部门时不显示自己以及自己下级的数据,避免出现PID数据环形问题 + depts = depts.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toList()); + } + deptSet.addAll(depts); + } + return new ResponseEntity<>(deptService.buildTree(new ArrayList<>(deptSet)),HttpStatus.OK); } @Log("新增部门") - @PostMapping(value = "/dept") - @PreAuthorize("hasAnyRole('ADMIN','DEPT_ALL','DEPT_CREATE')") - public ResponseEntity create(@Validated @RequestBody Dept resources){ + @ApiOperation("新增部门") + @PostMapping + @PreAuthorize("@el.check('dept:add')") + public ResponseEntity createDept(@Validated @RequestBody Dept resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(deptService.create(resources),HttpStatus.CREATED); + deptService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改部门") - @PutMapping(value = "/dept") - @PreAuthorize("hasAnyRole('ADMIN','DEPT_ALL','DEPT_EDIT')") - public ResponseEntity update(@Validated(Dept.Update.class) @RequestBody Dept resources){ + @ApiOperation("修改部门") + @PutMapping + @PreAuthorize("@el.check('dept:edit')") + public ResponseEntity updateDept(@Validated(Dept.Update.class) @RequestBody Dept resources){ deptService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除部门") - @DeleteMapping(value = "/dept/{id}") - @PreAuthorize("hasAnyRole('ADMIN','DEPT_ALL','DEPT_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - deptService.delete(id); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("删除部门") + @DeleteMapping + @PreAuthorize("@el.check('dept:del')") + public ResponseEntity deleteDept(@RequestBody Set ids){ + Set deptDtos = new HashSet<>(); + for (Long id : ids) { + List deptList = deptService.findByPid(id); + deptDtos.add(deptService.findById(id)); + if(CollectionUtil.isNotEmpty(deptList)){ + deptDtos = deptService.getDeleteDepts(deptList, deptDtos); + } + } + // 验证是否被角色或用户关联 + deptService.verification(deptDtos); + deptService.delete(deptDtos); + return new ResponseEntity<>(HttpStatus.OK); } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictController.java index 372bfe099..85faf15ac 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictController.java @@ -1,62 +1,103 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; -import me.zhengjie.aop.log.Log; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.domain.Dict; import me.zhengjie.modules.system.service.DictService; -import me.zhengjie.modules.system.service.dto.DictDTO; +import me.zhengjie.modules.system.service.dto.DictDto; import me.zhengjie.modules.system.service.dto.DictQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.PageResult; +import me.zhengjie.utils.PageUtil; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; /** * @author Zheng Jie * @date 2019-04-10 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@Api(tags = "系统:字典管理") +@RequestMapping("/api/dict") public class DictController { - @Autowired - private DictService dictService; - + private final DictService dictService; private static final String ENTITY_NAME = "dict"; - @Log("查询字典") - @GetMapping(value = "/dict") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_SELECT')") - public ResponseEntity getDicts(DictQueryCriteria resources, Pageable pageable){ - return new ResponseEntity(dictService.queryAll(resources,pageable),HttpStatus.OK); + @ApiOperation("导出字典数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('dict:list')") + public void exportDict(HttpServletResponse response, DictQueryCriteria criteria) throws IOException { + dictService.download(dictService.queryAll(criteria), response); + } + + @ApiOperation("查询字典") + @GetMapping(value = "/all") + @PreAuthorize("@el.check('dict:list')") + public ResponseEntity> queryAllDict(){ + return new ResponseEntity<>(dictService.queryAll(new DictQueryCriteria()),HttpStatus.OK); + } + + @ApiOperation("查询字典") + @GetMapping + @PreAuthorize("@el.check('dict:list')") + public ResponseEntity> queryDict(DictQueryCriteria resources, Pageable pageable){ + return new ResponseEntity<>(dictService.queryAll(resources,pageable),HttpStatus.OK); } @Log("新增字典") - @PostMapping(value = "/dict") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_CREATE')") - public ResponseEntity create(@Validated @RequestBody Dict resources){ + @ApiOperation("新增字典") + @PostMapping + @PreAuthorize("@el.check('dict:add')") + public ResponseEntity createDict(@Validated @RequestBody Dict resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(dictService.create(resources),HttpStatus.CREATED); + dictService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改字典") - @PutMapping(value = "/dict") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_EDIT')") - public ResponseEntity update(@Validated(Dict.Update.class) @RequestBody Dict resources){ + @ApiOperation("修改字典") + @PutMapping + @PreAuthorize("@el.check('dict:edit')") + public ResponseEntity updateDict(@Validated(Dict.Update.class) @RequestBody Dict resources){ dictService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除字典") - @DeleteMapping(value = "/dict/{id}") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - dictService.delete(id); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("删除字典") + @DeleteMapping + @PreAuthorize("@el.check('dict:del')") + public ResponseEntity deleteDict(@RequestBody Set ids){ + dictService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictDetailController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictDetailController.java index a15107fa7..a53fdf01e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictDetailController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/DictDetailController.java @@ -1,11 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; -import me.zhengjie.aop.log.Log; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.domain.DictDetail; import me.zhengjie.modules.system.service.DictDetailService; +import me.zhengjie.modules.system.service.dto.DictDetailDto; import me.zhengjie.modules.system.service.dto.DictDetailQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.PageResult; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; @@ -14,69 +33,68 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; - -import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * @author Zheng Jie * @date 2019-04-10 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@Api(tags = "系统:字典详情管理") +@RequestMapping("/api/dictDetail") public class DictDetailController { - @Autowired - private DictDetailService dictDetailService; - + private final DictDetailService dictDetailService; private static final String ENTITY_NAME = "dictDetail"; - @Log("查询字典详情") - @GetMapping(value = "/dictDetail") - public ResponseEntity getDictDetails(DictDetailQueryCriteria criteria, - @PageableDefault(value = 10, sort = {"sort"}, direction = Sort.Direction.ASC) Pageable pageable){ - String[] names = criteria.getDictName().split(","); - return new ResponseEntity(dictDetailService.queryAll(criteria,pageable),HttpStatus.OK); + @ApiOperation("查询字典详情") + @GetMapping + public ResponseEntity> queryDictDetail(DictDetailQueryCriteria criteria, + @PageableDefault(sort = {"dictSort"}, direction = Sort.Direction.ASC) Pageable pageable){ + return new ResponseEntity<>(dictDetailService.queryAll(criteria,pageable),HttpStatus.OK); } - @Log("查询多个字典详情") - @GetMapping(value = "/dictDetail/map") - public ResponseEntity getDictDetailMaps(DictDetailQueryCriteria criteria, - @PageableDefault(value = 10, sort = {"sort"}, direction = Sort.Direction.ASC) Pageable pageable){ - String[] names = criteria.getDictName().split(","); - Map map = new HashMap(names.length); + @ApiOperation("查询多个字典详情") + @GetMapping(value = "/map") + public ResponseEntity getDictDetailMaps(@RequestParam String dictName){ + String[] names = dictName.split("[,,]"); + Map> dictMap = new HashMap<>(16); for (String name : names) { - criteria.setDictName(name); - map.put(name,dictDetailService.queryAll(criteria,pageable).get("content")); + dictMap.put(name, dictDetailService.getDictByName(name)); } - return new ResponseEntity(map,HttpStatus.OK); + return new ResponseEntity<>(dictMap, HttpStatus.OK); } @Log("新增字典详情") - @PostMapping(value = "/dictDetail") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_CREATE')") - public ResponseEntity create(@Validated @RequestBody DictDetail resources){ + @ApiOperation("新增字典详情") + @PostMapping + @PreAuthorize("@el.check('dict:add')") + public ResponseEntity createDictDetail(@Validated @RequestBody DictDetail resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(dictDetailService.create(resources),HttpStatus.CREATED); + dictDetailService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改字典详情") - @PutMapping(value = "/dictDetail") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_EDIT')") - public ResponseEntity update(@Validated(DictDetail.Update.class) @RequestBody DictDetail resources){ + @ApiOperation("修改字典详情") + @PutMapping + @PreAuthorize("@el.check('dict:edit')") + public ResponseEntity updateDictDetail(@Validated(DictDetail.Update.class) @RequestBody DictDetail resources){ dictDetailService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除字典详情") - @DeleteMapping(value = "/dictDetail/{id}") - @PreAuthorize("hasAnyRole('ADMIN','DICT_ALL','DICT_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ + @ApiOperation("删除字典详情") + @DeleteMapping(value = "/{id}") + @PreAuthorize("@el.check('dict:del')") + public ResponseEntity deleteDictDetail(@PathVariable Long id){ dictDetailService.delete(id); - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/JobController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/JobController.java index 76d948860..729bfc45e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/JobController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/JobController.java @@ -1,19 +1,38 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; -import me.zhengjie.aop.log.Log; -import me.zhengjie.config.DataScope; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.domain.Job; import me.zhengjie.modules.system.service.JobService; +import me.zhengjie.modules.system.service.dto.JobDto; import me.zhengjie.modules.system.service.dto.JobQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.PageResult; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; - +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.Set; /** @@ -21,50 +40,57 @@ * @date 2019-03-29 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@Api(tags = "系统:岗位管理") +@RequestMapping("/api/job") public class JobController { - @Autowired - private JobService jobService; - - @Autowired - private DataScope dataScope; - + private final JobService jobService; private static final String ENTITY_NAME = "job"; - @Log("查询岗位") - @GetMapping(value = "/job") - @PreAuthorize("hasAnyRole('ADMIN','USERJOB_ALL','USERJOB_SELECT','USER_ALL','USER_SELECT')") - public ResponseEntity getJobs(JobQueryCriteria criteria, - Pageable pageable){ - // 数据权限 - criteria.setDeptIds(dataScope.getDeptIds()); - return new ResponseEntity(jobService.queryAll(criteria, pageable),HttpStatus.OK); + @ApiOperation("导出岗位数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('job:list')") + public void exportJob(HttpServletResponse response, JobQueryCriteria criteria) throws IOException { + jobService.download(jobService.queryAll(criteria), response); + } + + @ApiOperation("查询岗位") + @GetMapping + @PreAuthorize("@el.check('job:list','user:list')") + public ResponseEntity> queryJob(JobQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(jobService.queryAll(criteria, pageable),HttpStatus.OK); } @Log("新增岗位") - @PostMapping(value = "/job") - @PreAuthorize("hasAnyRole('ADMIN','USERJOB_ALL','USERJOB_CREATE')") - public ResponseEntity create(@Validated @RequestBody Job resources){ + @ApiOperation("新增岗位") + @PostMapping + @PreAuthorize("@el.check('job:add')") + public ResponseEntity createJob(@Validated @RequestBody Job resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(jobService.create(resources),HttpStatus.CREATED); + jobService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改岗位") - @PutMapping(value = "/job") - @PreAuthorize("hasAnyRole('ADMIN','USERJOB_ALL','USERJOB_EDIT')") - public ResponseEntity update(@Validated(Job.Update.class) @RequestBody Job resources){ + @ApiOperation("修改岗位") + @PutMapping + @PreAuthorize("@el.check('job:edit')") + public ResponseEntity updateJob(@Validated(Job.Update.class) @RequestBody Job resources){ jobService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除岗位") - @DeleteMapping(value = "/job/{id}") - @PreAuthorize("hasAnyRole('ADMIN','USERJOB_ALL','USERJOB_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - jobService.delete(id); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("删除岗位") + @DeleteMapping + @PreAuthorize("@el.check('job:del')") + public ResponseEntity deleteJob(@RequestBody Set ids){ + // 验证是否被用户关联 + jobService.verification(ids); + jobService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); } } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/LimitController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/LimitController.java new file mode 100644 index 000000000..aa26d8c23 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/LimitController.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.zhengjie.annotation.Limit; +import me.zhengjie.annotation.rest.AnonymousGetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author / + * 接口限流测试类 + */ +@RestController +@RequestMapping("/api/limit") +@Api(tags = "系统:限流测试管理") +public class LimitController { + + private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(); + + /** + * 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test, + */ + @AnonymousGetMapping + @ApiOperation("测试") + @Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit") + public int testLimit() { + return ATOMIC_INTEGER.incrementAndGet(); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MenuController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MenuController.java index 6d3f06c1f..786f5f312 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MenuController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MenuController.java @@ -1,103 +1,157 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; -import me.zhengjie.aop.log.Log; +import cn.hutool.core.collection.CollectionUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.modules.system.domain.Menu; import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.system.domain.vo.MenuVo; import me.zhengjie.modules.system.service.MenuService; -import me.zhengjie.modules.system.service.RoleService; -import me.zhengjie.modules.system.service.UserService; -import me.zhengjie.modules.system.service.dto.MenuDTO; +import me.zhengjie.modules.system.service.dto.DeptDto; +import me.zhengjie.modules.system.service.dto.MenuDto; import me.zhengjie.modules.system.service.dto.MenuQueryCriteria; -import me.zhengjie.modules.system.service.dto.UserDTO; +import me.zhengjie.modules.system.service.mapstruct.MenuMapper; +import me.zhengjie.utils.PageResult; +import me.zhengjie.utils.PageUtil; import me.zhengjie.utils.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.stream.Collectors; /** * @author Zheng Jie * @date 2018-12-03 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@Api(tags = "系统:菜单管理") +@RequestMapping("/api/menus") public class MenuController { - @Autowired - private MenuService menuService; + private final MenuService menuService; + private final MenuMapper menuMapper; + private static final String ENTITY_NAME = "menu"; - @Autowired - private UserService userService; + @ApiOperation("导出菜单数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('menu:list')") + public void exportMenu(HttpServletResponse response, MenuQueryCriteria criteria) throws Exception { + menuService.download(menuService.queryAll(criteria, false), response); + } - @Autowired - private RoleService roleService; + @GetMapping(value = "/build") + @ApiOperation("获取前端所需菜单") + public ResponseEntity> buildMenus(){ + List menuDtoList = menuService.findByUser(SecurityUtils.getCurrentUserId()); + List menus = menuService.buildTree(menuDtoList); + return new ResponseEntity<>(menuService.buildMenus(menus),HttpStatus.OK); + } - private static final String ENTITY_NAME = "menu"; + @ApiOperation("返回全部的菜单") + @GetMapping(value = "/lazy") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity> queryAllMenu(@RequestParam Long pid){ + return new ResponseEntity<>(menuService.getMenus(pid),HttpStatus.OK); + } - /** - * 构建前端路由所需要的菜单 - * @return - */ - @GetMapping(value = "/menus/build") - public ResponseEntity buildMenus(){ - UserDTO user = userService.findByName(SecurityUtils.getUsername()); - List menuDTOList = menuService.findByRoles(roleService.findByUsers_Id(user.getId())); - List menuDTOTree = (List)menuService.buildTree(menuDTOList).get("content"); - return new ResponseEntity(menuService.buildMenus(menuDTOTree),HttpStatus.OK); + @ApiOperation("根据菜单ID返回所有子节点ID,包含自身ID") + @GetMapping(value = "/child") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity childMenu(@RequestParam Long id){ + Set menuSet = new HashSet<>(); + List menuList = menuService.getMenus(id); + menuSet.add(menuService.findOne(id)); + menuSet = menuService.getChildMenus(menuMapper.toEntity(menuList), menuSet); + Set ids = menuSet.stream().map(Menu::getId).collect(Collectors.toSet()); + return new ResponseEntity<>(ids,HttpStatus.OK); } - /** - * 返回全部的菜单 - * @return - */ - @GetMapping(value = "/menus/tree") - @PreAuthorize("hasAnyRole('ADMIN','MENU_ALL','MENU_CREATE','MENU_EDIT','ROLES_SELECT','ROLES_ALL')") - public ResponseEntity getMenuTree(){ - return new ResponseEntity(menuService.getMenuTree(menuService.findByPid(0L)),HttpStatus.OK); + @GetMapping + @ApiOperation("查询菜单") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> queryMenu(MenuQueryCriteria criteria) throws Exception { + List menuDtoList = menuService.queryAll(criteria, true); + return new ResponseEntity<>(PageUtil.toPage(menuDtoList, menuDtoList.size()),HttpStatus.OK); } - @Log("查询菜单") - @GetMapping(value = "/menus") - @PreAuthorize("hasAnyRole('ADMIN','MENU_ALL','MENU_SELECT')") - public ResponseEntity getMenus(MenuQueryCriteria criteria){ - List menuDTOList = menuService.queryAll(criteria); - return new ResponseEntity(menuService.buildTree(menuDTOList),HttpStatus.OK); + @ApiOperation("查询菜单:根据ID获取同级与上级数据") + @PostMapping("/superior") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> getMenuSuperior(@RequestBody List ids) { + Set menuDtos = new LinkedHashSet<>(); + if(CollectionUtil.isNotEmpty(ids)){ + for (Long id : ids) { + MenuDto menuDto = menuService.findById(id); + List menuDtoList = menuService.getSuperior(menuDto, new ArrayList<>()); + for (MenuDto menu : menuDtoList) { + if(menu.getId().equals(menuDto.getPid())) { + menu.setSubCount(menu.getSubCount() - 1); + } + } + menuDtos.addAll(menuDtoList); + } + // 编辑菜单时不显示自己以及自己下级的数据,避免出现PID数据环形问题 + menuDtos = menuDtos.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toSet()); + return new ResponseEntity<>(menuService.buildTree(new ArrayList<>(menuDtos)),HttpStatus.OK); + } + return new ResponseEntity<>(menuService.getMenus(null),HttpStatus.OK); } @Log("新增菜单") - @PostMapping(value = "/menus") - @PreAuthorize("hasAnyRole('ADMIN','MENU_ALL','MENU_CREATE')") - public ResponseEntity create(@Validated @RequestBody Menu resources){ + @ApiOperation("新增菜单") + @PostMapping + @PreAuthorize("@el.check('menu:add')") + public ResponseEntity createMenu(@Validated @RequestBody Menu resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(menuService.create(resources),HttpStatus.CREATED); + menuService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改菜单") - @PutMapping(value = "/menus") - @PreAuthorize("hasAnyRole('ADMIN','MENU_ALL','MENU_EDIT')") - public ResponseEntity update(@Validated(Menu.Update.class) @RequestBody Menu resources){ + @ApiOperation("修改菜单") + @PutMapping + @PreAuthorize("@el.check('menu:edit')") + public ResponseEntity updateMenu(@Validated(Menu.Update.class) @RequestBody Menu resources){ menuService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除菜单") - @DeleteMapping(value = "/menus/{id}") - @PreAuthorize("hasAnyRole('ADMIN','MENU_ALL','MENU_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - List menuList = menuService.findByPid(id); - - // 特殊情况,对级联删除进行处理 - for (Menu menu : menuList) { - roleService.untiedMenu(menu); - menuService.delete(menu.getId()); + @ApiOperation("删除菜单") + @DeleteMapping + @PreAuthorize("@el.check('menu:del')") + public ResponseEntity deleteMenu(@RequestBody Set ids){ + Set menuSet = new HashSet<>(); + for (Long id : ids) { + List menuList = menuService.getMenus(id); + menuSet.add(menuService.findOne(id)); + menuSet = menuService.getChildMenus(menuMapper.toEntity(menuList), menuSet); } - roleService.untiedMenu(menuService.findOne(id)); - menuService.delete(id); - return new ResponseEntity(HttpStatus.OK); + menuService.delete(menuSet); + return new ResponseEntity<>(HttpStatus.OK); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MonitorController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MonitorController.java new file mode 100644 index 000000000..b360f9fad --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/MonitorController.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.system.service.MonitorService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +/** + * @author Zheng Jie + * @date 2020-05-02 + */ +@RestController +@RequiredArgsConstructor +@Api(tags = "系统-服务监控管理") +@RequestMapping("/api/monitor") +public class MonitorController { + + private final MonitorService serverService; + + @GetMapping + @ApiOperation("查询服务监控") + @PreAuthorize("@el.check('monitor:list')") + public ResponseEntity queryMonitor(){ + return new ResponseEntity<>(serverService.getServers(),HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/PermissionController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/PermissionController.java deleted file mode 100644 index b27ea7c34..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/PermissionController.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.zhengjie.modules.system.rest; - -import me.zhengjie.aop.log.Log; -import me.zhengjie.modules.system.domain.Permission; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.modules.system.service.PermissionService; -import me.zhengjie.modules.system.service.dto.PermissionDTO; -import me.zhengjie.modules.system.service.dto.PermissionQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -/** - * @author Zheng Jie - * @date 2018-12-03 - */ -@RestController -@RequestMapping("api") -public class PermissionController { - - @Autowired - private PermissionService permissionService; - - private static final String ENTITY_NAME = "permission"; - - /** - * 返回全部的权限,新增角色时下拉选择 - * @return - */ - @GetMapping(value = "/permissions/tree") - @PreAuthorize("hasAnyRole('ADMIN','PERMISSION_ALL','PERMISSION_CREATE','PERMISSION_EDIT','ROLES_SELECT','ROLES_ALL')") - public ResponseEntity getTree(){ - return new ResponseEntity(permissionService.getPermissionTree(permissionService.findByPid(0L)),HttpStatus.OK); - } - - @Log("查询权限") - @GetMapping(value = "/permissions") - @PreAuthorize("hasAnyRole('ADMIN','PERMISSION_ALL','PERMISSION_SELECT')") - public ResponseEntity getPermissions(PermissionQueryCriteria criteria){ - List permissionDTOS = permissionService.queryAll(criteria); - return new ResponseEntity(permissionService.buildTree(permissionDTOS),HttpStatus.OK); - } - - @Log("新增权限") - @PostMapping(value = "/permissions") - @PreAuthorize("hasAnyRole('ADMIN','PERMISSION_ALL','PERMISSION_CREATE')") - public ResponseEntity create(@Validated @RequestBody Permission resources){ - if (resources.getId() != null) { - throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); - } - return new ResponseEntity(permissionService.create(resources),HttpStatus.CREATED); - } - - @Log("修改权限") - @PutMapping(value = "/permissions") - @PreAuthorize("hasAnyRole('ADMIN','PERMISSION_ALL','PERMISSION_EDIT')") - public ResponseEntity update(@Validated(Permission.Update.class) @RequestBody Permission resources){ - permissionService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); - } - - @Log("删除权限") - @DeleteMapping(value = "/permissions/{id}") - @PreAuthorize("hasAnyRole('ADMIN','PERMISSION_ALL','PERMISSION_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - permissionService.delete(id); - return new ResponseEntity(HttpStatus.OK); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java index ce28bca36..91a087e4f 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java @@ -1,25 +1,44 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; import cn.hutool.core.lang.Dict; -import me.zhengjie.aop.log.Log; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.modules.system.domain.Role; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.service.RoleService; -import me.zhengjie.modules.system.service.dto.MenuQueryCriteria; +import me.zhengjie.modules.system.service.dto.RoleDto; import me.zhengjie.modules.system.service.dto.RoleQueryCriteria; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.utils.PageResult; import me.zhengjie.utils.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -27,87 +46,110 @@ * @date 2018-12-03 */ @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@Api(tags = "系统:角色管理") +@RequestMapping("/api/roles") public class RoleController { - @Autowired - private RoleService roleService; + private final RoleService roleService; private static final String ENTITY_NAME = "role"; - /** - * 获取单个role - * @param id - * @return - */ - @GetMapping(value = "/roles/{id}") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_SELECT')") - public ResponseEntity getRoles(@PathVariable Long id){ - return new ResponseEntity(roleService.findById(id), HttpStatus.OK); + @ApiOperation("获取单个role") + @GetMapping(value = "/{id}") + @PreAuthorize("@el.check('roles:list')") + public ResponseEntity findRoleById(@PathVariable Long id){ + return new ResponseEntity<>(roleService.findById(id), HttpStatus.OK); } - /** - * 返回全部的角色,新增用户时下拉选择 - * @return - */ - @GetMapping(value = "/roles/all") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','USER_ALL','USER_CREATE','USER_EDIT')") - public ResponseEntity getAll(@PageableDefault(value = 2000, sort = {"level"}, direction = Sort.Direction.ASC) Pageable pageable){ - return new ResponseEntity(roleService.queryAll(pageable),HttpStatus.OK); + @ApiOperation("导出角色数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('role:list')") + public void exportRole(HttpServletResponse response, RoleQueryCriteria criteria) throws IOException { + roleService.download(roleService.queryAll(criteria), response); + } + + @ApiOperation("返回全部的角色") + @GetMapping(value = "/all") + @PreAuthorize("@el.check('roles:list','user:add','user:edit')") + public ResponseEntity> queryAllRole(){ + return new ResponseEntity<>(roleService.queryAll(),HttpStatus.OK); } - @Log("查询角色") - @GetMapping(value = "/roles") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_SELECT')") - public ResponseEntity getRoles(RoleQueryCriteria criteria, Pageable pageable){ - return new ResponseEntity(roleService.queryAll(criteria,pageable),HttpStatus.OK); + @ApiOperation("查询角色") + @GetMapping + @PreAuthorize("@el.check('roles:list')") + public ResponseEntity> queryRole(RoleQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(roleService.queryAll(criteria,pageable),HttpStatus.OK); } - @GetMapping(value = "/roles/level") - public ResponseEntity getLevel(){ - List levels = roleService.findByUsers_Id(SecurityUtils.getUserId()).stream().map(RoleSmallDTO::getLevel).collect(Collectors.toList()); - return new ResponseEntity(Dict.create().set("level", Collections.min(levels)),HttpStatus.OK); + @ApiOperation("获取用户级别") + @GetMapping(value = "/level") + public ResponseEntity getRoleLevel(){ + return new ResponseEntity<>(Dict.create().set("level", getLevels(null)),HttpStatus.OK); } @Log("新增角色") - @PostMapping(value = "/roles") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_CREATE')") - public ResponseEntity create(@Validated @RequestBody Role resources){ + @ApiOperation("新增角色") + @PostMapping + @PreAuthorize("@el.check('roles:add')") + public ResponseEntity createRole(@Validated @RequestBody Role resources){ if (resources.getId() != null) { throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID"); } - return new ResponseEntity(roleService.create(resources),HttpStatus.CREATED); + getLevels(resources.getLevel()); + roleService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改角色") - @PutMapping(value = "/roles") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_EDIT')") - public ResponseEntity update(@Validated(Role.Update.class) @RequestBody Role resources){ + @ApiOperation("修改角色") + @PutMapping + @PreAuthorize("@el.check('roles:edit')") + public ResponseEntity updateRole(@Validated(Role.Update.class) @RequestBody Role resources){ + getLevels(resources.getLevel()); roleService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); - } - - @Log("修改角色权限") - @PutMapping(value = "/roles/permission") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_EDIT')") - public ResponseEntity updatePermission(@RequestBody Role resources){ - roleService.updatePermission(resources,roleService.findById(resources.getId())); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("修改角色菜单") - @PutMapping(value = "/roles/menu") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_EDIT')") - public ResponseEntity updateMenu(@RequestBody Role resources){ - roleService.updateMenu(resources,roleService.findById(resources.getId())); - return new ResponseEntity(HttpStatus.NO_CONTENT); + @ApiOperation("修改角色菜单") + @PutMapping(value = "/menu") + @PreAuthorize("@el.check('roles:edit')") + public ResponseEntity updateRoleMenu(@RequestBody Role resources){ + RoleDto role = roleService.findById(resources.getId()); + getLevels(role.getLevel()); + roleService.updateMenu(resources,role); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Log("删除角色") - @DeleteMapping(value = "/roles/{id}") - @PreAuthorize("hasAnyRole('ADMIN','ROLES_ALL','ROLES_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - roleService.delete(id); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("删除角色") + @DeleteMapping + @PreAuthorize("@el.check('roles:del')") + public ResponseEntity deleteRole(@RequestBody Set ids){ + for (Long id : ids) { + RoleDto role = roleService.findById(id); + getLevels(role.getLevel()); + } + // 验证是否被用户关联 + roleService.verification(ids); + roleService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } + + /** + * 获取用户的角色级别 + * @return / + */ + private int getLevels(Integer level){ + List levels = roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()); + int min = Collections.min(levels); + if(level != null){ + if(level < min){ + throw new BadRequestException("权限不足,你的角色级别:" + min + ",低于操作的角色级别:" + level); + } + } + return min; } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java index d5634e48d..4b76782e6 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java @@ -1,31 +1,53 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.rest; -import me.zhengjie.aop.log.Log; -import me.zhengjie.config.DataScope; -import me.zhengjie.domain.Picture; -import me.zhengjie.domain.VerificationCode; +import cn.hutool.core.collection.CollectionUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.utils.PageResult; +import me.zhengjie.config.properties.RsaProperties; +import me.zhengjie.modules.system.domain.Dept; +import me.zhengjie.modules.system.service.DataService; import me.zhengjie.modules.system.domain.User; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.domain.vo.UserPassVo; import me.zhengjie.modules.system.service.DeptService; import me.zhengjie.modules.system.service.RoleService; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; import me.zhengjie.modules.system.service.dto.UserQueryCriteria; -import me.zhengjie.service.PictureService; -import me.zhengjie.service.VerificationCodeService; +import me.zhengjie.modules.system.service.VerifyService; import me.zhengjie.utils.*; import me.zhengjie.modules.system.service.UserService; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.utils.enums.CodeEnum; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -33,152 +55,153 @@ * @author Zheng Jie * @date 2018-11-23 */ +@Api(tags = "系统:用户管理") @RestController -@RequestMapping("api") +@RequestMapping("/api/users") +@RequiredArgsConstructor public class UserController { - @Autowired - private UserService userService; - - @Autowired - private PictureService pictureService; - - @Autowired - private DataScope dataScope; - - @Autowired - private DeptService deptService; - - @Autowired - private RoleService roleService; - - @Autowired - private VerificationCodeService verificationCodeService; - - @Log("查询用户") - @GetMapping(value = "/users") - @PreAuthorize("hasAnyRole('ADMIN','USER_ALL','USER_SELECT')") - public ResponseEntity getUsers(UserQueryCriteria criteria, Pageable pageable){ - Set deptSet = new HashSet<>(); - Set result = new HashSet<>(); + private final PasswordEncoder passwordEncoder; + private final UserService userService; + private final DataService dataService; + private final DeptService deptService; + private final RoleService roleService; + private final VerifyService verificationCodeService; + + @ApiOperation("导出用户数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('user:list')") + public void exportUser(HttpServletResponse response, UserQueryCriteria criteria) throws IOException { + userService.download(userService.queryAll(criteria), response); + } + @ApiOperation("查询用户") + @GetMapping + @PreAuthorize("@el.check('user:list')") + public ResponseEntity> queryUser(UserQueryCriteria criteria, Pageable pageable){ if (!ObjectUtils.isEmpty(criteria.getDeptId())) { - deptSet.add(criteria.getDeptId()); - deptSet.addAll(dataScope.getDeptChildren(deptService.findByPid(criteria.getDeptId()))); + criteria.getDeptIds().add(criteria.getDeptId()); + // 先查找是否存在子节点 + List data = deptService.findByPid(criteria.getDeptId()); + // 然后把子节点的ID都加入到集合中 + criteria.getDeptIds().addAll(deptService.getDeptChildren(data)); } - // 数据权限 - Set deptIds = dataScope.getDeptIds(); - - // 查询条件不为空并且数据权限不为空则取交集 - if (!CollectionUtils.isEmpty(deptIds) && !CollectionUtils.isEmpty(deptSet)){ - + List dataScopes = dataService.getDeptIds(userService.findByName(SecurityUtils.getCurrentUsername())); + // criteria.getDeptIds() 不为空并且数据权限不为空则取交集 + if (!CollectionUtils.isEmpty(criteria.getDeptIds()) && !CollectionUtils.isEmpty(dataScopes)){ // 取交集 - result.addAll(deptSet); - result.retainAll(deptIds); - - // 若无交集,则代表无数据权限 - criteria.setDeptIds(result); - if(result.size() == 0){ - return new ResponseEntity(PageUtil.toPage(null,0),HttpStatus.OK); - } else return new ResponseEntity(userService.queryAll(criteria,pageable),HttpStatus.OK); - // 否则取并集 + criteria.getDeptIds().retainAll(dataScopes); + if(!CollectionUtil.isEmpty(criteria.getDeptIds())){ + return new ResponseEntity<>(userService.queryAll(criteria,pageable),HttpStatus.OK); + } } else { - result.addAll(deptSet); - result.addAll(deptIds); - criteria.setDeptIds(result); - return new ResponseEntity(userService.queryAll(criteria,pageable),HttpStatus.OK); + // 否则取并集 + criteria.getDeptIds().addAll(dataScopes); + return new ResponseEntity<>(userService.queryAll(criteria,pageable),HttpStatus.OK); } + return new ResponseEntity<>(PageUtil.noData(),HttpStatus.OK); } @Log("新增用户") - @PostMapping(value = "/users") - @PreAuthorize("hasAnyRole('ADMIN','USER_ALL','USER_CREATE')") - public ResponseEntity create(@Validated @RequestBody User resources){ + @ApiOperation("新增用户") + @PostMapping + @PreAuthorize("@el.check('user:add')") + public ResponseEntity createUser(@Validated @RequestBody User resources){ checkLevel(resources); - return new ResponseEntity(userService.create(resources),HttpStatus.CREATED); + // 默认密码 123456 + resources.setPassword(passwordEncoder.encode("123456")); + userService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); } @Log("修改用户") - @PutMapping(value = "/users") - @PreAuthorize("hasAnyRole('ADMIN','USER_ALL','USER_EDIT')") - public ResponseEntity update(@Validated(User.Update.class) @RequestBody User resources){ + @ApiOperation("修改用户") + @PutMapping + @PreAuthorize("@el.check('user:edit')") + public ResponseEntity updateUser(@Validated(User.Update.class) @RequestBody User resources) throws Exception { checkLevel(resources); userService.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } - @Log("删除用户") - @DeleteMapping(value = "/users/{id}") - @PreAuthorize("hasAnyRole('ADMIN','USER_ALL','USER_DELETE')") - public ResponseEntity delete(@PathVariable Long id){ - Integer currentLevel = Collections.min(roleService.findByUsers_Id(SecurityUtils.getUserId()).stream().map(RoleSmallDTO::getLevel).collect(Collectors.toList())); - Integer optLevel = Collections.min(roleService.findByUsers_Id(id).stream().map(RoleSmallDTO::getLevel).collect(Collectors.toList())); + @Log("修改用户:个人中心") + @ApiOperation("修改用户:个人中心") + @PutMapping(value = "center") + public ResponseEntity centerUser(@Validated(User.Update.class) @RequestBody User resources){ + if(!resources.getId().equals(SecurityUtils.getCurrentUserId())){ + throw new BadRequestException("不能修改他人资料"); + } + userService.updateCenter(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } - if (currentLevel > optLevel) { - throw new BadRequestException("角色权限不足"); + @Log("删除用户") + @ApiOperation("删除用户") + @DeleteMapping + @PreAuthorize("@el.check('user:del')") + public ResponseEntity deleteUser(@RequestBody Set ids){ + for (Long id : ids) { + Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + Integer optLevel = Collections.min(roleService.findByUsersId(id).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + if (currentLevel > optLevel) { + throw new BadRequestException("角色权限不足,不能删除:" + userService.findById(id).getUsername()); + } } - userService.delete(id); - return new ResponseEntity(HttpStatus.OK); + userService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); } - /** - * 修改密码 - * @param user - * @return - */ - @PostMapping(value = "/users/updatePass") - public ResponseEntity updatePass(@RequestBody UserPassVo user){ - UserDetails userDetails = SecurityUtils.getUserDetails(); - if(!userDetails.getPassword().equals(EncryptUtils.encryptPassword(user.getOldPass()))){ + @ApiOperation("修改密码") + @PostMapping(value = "/updatePass") + public ResponseEntity updateUserPass(@RequestBody UserPassVo passVo) throws Exception { + String oldPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getOldPass()); + String newPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getNewPass()); + UserDto user = userService.findByName(SecurityUtils.getCurrentUsername()); + if(!passwordEncoder.matches(oldPass, user.getPassword())){ throw new BadRequestException("修改失败,旧密码错误"); } - if(userDetails.getPassword().equals(EncryptUtils.encryptPassword(user.getNewPass()))){ + if(passwordEncoder.matches(newPass, user.getPassword())){ throw new BadRequestException("新密码不能与旧密码相同"); } - userService.updatePass(userDetails.getUsername(),EncryptUtils.encryptPassword(user.getNewPass())); - return new ResponseEntity(HttpStatus.OK); + userService.updatePass(user.getUsername(),passwordEncoder.encode(newPass)); + return new ResponseEntity<>(HttpStatus.OK); } - /** - * 修改头像 - * @param file - * @return - */ - @PostMapping(value = "/users/updateAvatar") - public ResponseEntity updateAvatar(@RequestParam MultipartFile file){ - Picture picture = pictureService.upload(file, SecurityUtils.getUsername()); - userService.updateAvatar(SecurityUtils.getUsername(),picture.getUrl()); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("重置密码") + @PutMapping(value = "/resetPwd") + public ResponseEntity resetPwd(@RequestBody Set ids) { + String pwd = passwordEncoder.encode("123456"); + userService.resetPwd(ids, pwd); + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation("修改头像") + @PostMapping(value = "/updateAvatar") + public ResponseEntity updateUserAvatar(@RequestParam MultipartFile avatar){ + return new ResponseEntity<>(userService.updateAvatar(avatar), HttpStatus.OK); } - /** - * 修改邮箱 - * @param user - * @param user - * @return - */ @Log("修改邮箱") - @PostMapping(value = "/users/updateEmail/{code}") - public ResponseEntity updateEmail(@PathVariable String code,@RequestBody User user){ - UserDetails userDetails = SecurityUtils.getUserDetails(); - if(!userDetails.getPassword().equals(EncryptUtils.encryptPassword(user.getPassword()))){ + @ApiOperation("修改邮箱") + @PostMapping(value = "/updateEmail/{code}") + public ResponseEntity updateUserEmail(@PathVariable String code,@RequestBody User user) throws Exception { + String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,user.getPassword()); + UserDto userDto = userService.findByName(SecurityUtils.getCurrentUsername()); + if(!passwordEncoder.matches(password, userDto.getPassword())){ throw new BadRequestException("密码错误"); } - VerificationCode verificationCode = new VerificationCode(code, ElAdminConstant.RESET_MAIL,"email",user.getEmail()); - verificationCodeService.validated(verificationCode); - userService.updateEmail(userDetails.getUsername(),user.getEmail()); - return new ResponseEntity(HttpStatus.OK); + verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + user.getEmail(), code); + userService.updateEmail(userDto.getUsername(),user.getEmail()); + return new ResponseEntity<>(HttpStatus.OK); } - - /** * 如果当前用户的角色级别低于创建用户的角色级别,则抛出权限不足的错误 - * @param resources + * @param resources / */ private void checkLevel(User resources) { - Integer currentLevel = Collections.min(roleService.findByUsers_Id(SecurityUtils.getUserId()).stream().map(RoleSmallDTO::getLevel).collect(Collectors.toList())); + Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); Integer optLevel = roleService.findByRoles(resources.getRoles()); if (currentLevel > optLevel) { throw new BadRequestException("角色权限不足"); diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/VerifyController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/VerifyController.java new file mode 100644 index 000000000..b66da8492 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/VerifyController.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.domain.vo.EmailVo; +import me.zhengjie.service.EmailService; +import me.zhengjie.modules.system.service.VerifyService; +import me.zhengjie.utils.enums.CodeBiEnum; +import me.zhengjie.utils.enums.CodeEnum; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.Objects; + +/** + * @author Zheng Jie + * @date 2018-12-26 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/code") +@Api(tags = "系统:验证码管理") +public class VerifyController { + + private final VerifyService verificationCodeService; + private final EmailService emailService; + + @PostMapping(value = "/resetEmail") + @ApiOperation("重置邮箱,发送验证码") + public ResponseEntity resetEmail(@RequestParam String email){ + EmailVo emailVo = verificationCodeService.sendEmail(email, CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey()); + emailService.send(emailVo,emailService.find()); + return new ResponseEntity<>(HttpStatus.OK); + } + + @PostMapping(value = "/email/resetPass") + @ApiOperation("重置密码,发送验证码") + public ResponseEntity resetPass(@RequestParam String email){ + EmailVo emailVo = verificationCodeService.sendEmail(email, CodeEnum.EMAIL_RESET_PWD_CODE.getKey()); + emailService.send(emailVo,emailService.find()); + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping(value = "/validated") + @ApiOperation("验证码验证") + public ResponseEntity validated(@RequestParam String email, @RequestParam String code, @RequestParam Integer codeBi){ + CodeBiEnum biEnum = CodeBiEnum.find(codeBi); + switch (Objects.requireNonNull(biEnum)){ + case ONE: + verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + email ,code); + break; + case TWO: + verificationCodeService.validated(CodeEnum.EMAIL_RESET_PWD_CODE.getKey() + email ,code); + break; + default: + break; + } + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DataService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DataService.java new file mode 100644 index 000000000..77c7cd89a --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DataService.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service; + +import me.zhengjie.modules.system.service.dto.UserDto; +import java.util.List; + +/** + * 数据权限服务类 + * @author Zheng Jie + * @date 2020-05-07 + */ +public interface DataService { + + /** + * 获取数据权限 + * @param user / + * @return / + */ + List getDeptIds(UserDto user); +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DeptService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DeptService.java index 4ed093fb4..fdfe8da4e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DeptService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DeptService.java @@ -1,12 +1,25 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; import me.zhengjie.modules.system.domain.Dept; -import me.zhengjie.modules.system.service.dto.DeptDTO; +import me.zhengjie.modules.system.service.dto.DeptDto; import me.zhengjie.modules.system.service.dto.DeptQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; - +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.List; import java.util.Set; @@ -14,62 +27,98 @@ * @author Zheng Jie * @date 2019-03-25 */ -@CacheConfig(cacheNames = "dept") public interface DeptService { /** - * queryAll - * @param criteria - * @return + * 查询所有数据 + * @param criteria 条件 + * @param isQuery / + * @throws Exception / + * @return / */ - @Cacheable(keyGenerator = "keyGenerator") - List queryAll(DeptQueryCriteria criteria); + List queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception; /** - * findById - * @param id - * @return + * 根据ID查询 + * @param id / + * @return / */ - @Cacheable(key = "#p0") - DeptDTO findById(Long id); + DeptDto findById(Long id); /** - * create - * @param resources - * @return + * 创建 + * @param resources / */ - @CacheEvict(allEntries = true) - DeptDTO create(Dept resources); + void create(Dept resources); /** - * update - * @param resources + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) void update(Dept resources); /** - * delete - * @param id + * 删除 + * @param deptDtos / + * */ - @CacheEvict(allEntries = true) - void delete(Long id); + void delete(Set deptDtos); /** - * buildTree - * @param deptDTOS - * @return + * 根据PID查询 + * @param pid / + * @return / */ - @Cacheable(keyGenerator = "keyGenerator") - Object buildTree(List deptDTOS); + List findByPid(long pid); /** - * findByPid - * @param pid - * @return + * 根据角色ID查询 + * @param id / + * @return / */ - @Cacheable(keyGenerator = "keyGenerator") - List findByPid(long pid); + Set findByRoleId(Long id); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 获取待删除的部门 + * @param deptList / + * @param deptDtos / + * @return / + */ + Set getDeleteDepts(List deptList, Set deptDtos); + + /** + * 根据ID获取同级与上级数据 + * @param deptDto / + * @param depts / + * @return / + */ + List getSuperior(DeptDto deptDto, List depts); + + /** + * 构建树形数据 + * @param deptDtos / + * @return / + */ + Object buildTree(List deptDtos); - Set findByRoleIds(Long id); + /** + * 获取 + * @param deptList / + * @return / + */ + List getDeptChildren(List deptList); + + /** + * 验证是否被角色或用户关联 + * @param deptDtos / + */ + void verification(Set deptDtos); } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictDetailService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictDetailService.java index 2566984bb..ecbaa9184 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictDetailService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictDetailService.java @@ -1,52 +1,63 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; +import me.zhengjie.utils.PageResult; import me.zhengjie.modules.system.domain.DictDetail; -import me.zhengjie.modules.system.service.dto.DictDetailDTO; +import me.zhengjie.modules.system.service.dto.DictDetailDto; import me.zhengjie.modules.system.service.dto.DictDetailQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Pageable; - -import java.util.Map; +import java.util.List; /** * @author Zheng Jie * @date 2019-04-10 */ -@CacheConfig(cacheNames = "dictDetail") public interface DictDetailService { /** - * findById - * @param id - * @return + * 创建 + * @param resources / */ - @Cacheable(key = "#p0") - DictDetailDTO findById(Long id); + void create(DictDetail resources); /** - * create - * @param resources - * @return + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) - DictDetailDTO create(DictDetail resources); + void update(DictDetail resources); /** - * update - * @param resources + * 删除 + * @param id / */ - @CacheEvict(allEntries = true) - void update(DictDetail resources); + void delete(Long id); /** - * delete - * @param id + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / */ - @CacheEvict(allEntries = true) - void delete(Long id); + PageResult queryAll(DictDetailQueryCriteria criteria, Pageable pageable); - @Cacheable(keyGenerator = "keyGenerator") - Map queryAll(DictDetailQueryCriteria criteria, Pageable pageable); -} \ No newline at end of file + /** + * 根据字典名称获取字典详情 + * @param name 字典名称 + * @return / + */ + List getDictByName(String name); +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictService.java index 6b4f595a9..83569f9cf 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/DictService.java @@ -1,56 +1,75 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; +import me.zhengjie.utils.PageResult; import me.zhengjie.modules.system.domain.Dict; -import me.zhengjie.modules.system.service.dto.DictDTO; +import me.zhengjie.modules.system.service.dto.DictDto; import me.zhengjie.modules.system.service.dto.DictQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Pageable; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; /** * @author Zheng Jie * @date 2019-04-10 */ -@CacheConfig(cacheNames = "dict") public interface DictService { /** - * 查询 - * @param dict - * @param pageable - * @return + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / */ - @Cacheable(keyGenerator = "keyGenerator") - Object queryAll(DictQueryCriteria dict, Pageable pageable); + PageResult queryAll(DictQueryCriteria criteria, Pageable pageable); /** - * findById - * @param id - * @return + * 查询全部数据 + * @param dict / + * @return / */ - @Cacheable(key = "#p0") - DictDTO findById(Long id); + List queryAll(DictQueryCriteria dict); /** - * create - * @param resources - * @return + * 创建 + * @param resources / + * @return / */ - @CacheEvict(allEntries = true) - DictDTO create(Dict resources); + void create(Dict resources); /** - * update - * @param resources + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) void update(Dict resources); /** - * delete - * @param id + * 删除 + * @param ids / */ - @CacheEvict(allEntries = true) - void delete(Long id); -} \ No newline at end of file + void delete(Set ids); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/JobService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/JobService.java index ecd92a04c..5543ede69 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/JobService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/JobService.java @@ -1,55 +1,88 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; +import me.zhengjie.utils.PageResult; import me.zhengjie.modules.system.domain.Job; -import me.zhengjie.modules.system.service.dto.JobDTO; +import me.zhengjie.modules.system.service.dto.JobDto; import me.zhengjie.modules.system.service.dto.JobQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Pageable; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; /** * @author Zheng Jie * @date 2019-03-29 */ -@CacheConfig(cacheNames = "job") public interface JobService { /** - * findById - * @param id - * @return + * 根据ID查询 + * @param id / + * @return / */ - @Cacheable(key = "#p0") - JobDTO findById(Long id); + JobDto findById(Long id); /** - * create - * @param resources - * @return + * 创建 + * @param resources / + * @return / */ - @CacheEvict(allEntries = true) - JobDTO create(Job resources); + void create(Job resources); /** - * update - * @param resources + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) void update(Job resources); /** - * delete - * @param id + * 删除 + * @param ids / */ - @CacheEvict(allEntries = true) - void delete(Long id); + void delete(Set ids); /** - * queryAll - * @param criteria - * @param pageable - * @return + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / */ - Object queryAll(JobQueryCriteria criteria, Pageable pageable); -} \ No newline at end of file + PageResult queryAll(JobQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria / + * @return / + */ + List queryAll(JobQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 验证是否被用户关联 + * @param ids / + */ + void verification(Set ids); +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MenuService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MenuService.java index 057b3755e..894849de2 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MenuService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MenuService.java @@ -1,95 +1,126 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; import me.zhengjie.modules.system.domain.Menu; -import me.zhengjie.modules.system.service.dto.MenuDTO; +import me.zhengjie.modules.system.domain.vo.MenuVo; +import me.zhengjie.modules.system.service.dto.MenuDto; import me.zhengjie.modules.system.service.dto.MenuQueryCriteria; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.List; -import java.util.Map; +import java.util.Set; /** * @author Zheng Jie * @date 2018-12-17 */ -@CacheConfig(cacheNames = "menu") public interface MenuService { /** - * queryAll - * @param criteria - * @return + * 查询全部数据 + * @param criteria 条件 + * @param isQuery / + * @throws Exception / + * @return / */ - @Cacheable(keyGenerator = "keyGenerator") - List queryAll(MenuQueryCriteria criteria); + List queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception; /** - * get - * @param id - * @return + * 根据ID查询 + * @param id / + * @return / */ - @Cacheable(key = "#p0") - MenuDTO findById(long id); + MenuDto findById(long id); /** - * create - * @param resources - * @return + * 创建 + * @param resources / */ - @CacheEvict(allEntries = true) - MenuDTO create(Menu resources); + void create(Menu resources); /** - * update - * @param resources + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) void update(Menu resources); /** - * delete - * @param id + * 获取所有子节点,包含自身ID + * @param menuList / + * @param menuSet / + * @return / */ - @CacheEvict(allEntries = true) - void delete(Long id); + Set getChildMenus(List menuList, Set menuSet); /** - * permission tree - * @return + * 构建菜单树 + * @param menuDtos 原始数据 + * @return / */ - @Cacheable(key = "'tree'") - Object getMenuTree(List menus); + List buildTree(List menuDtos); /** - * findByPid - * @param pid - * @return + * 构建菜单树 + * @param menuDtos / + * @return / */ - @Cacheable(key = "'pid:'+#p0") - List findByPid(long pid); + List buildMenus(List menuDtos); /** - * build Tree - * @param menuDTOS - * @return + * 根据ID查询 + * @param id / + * @return / */ - Map buildTree(List menuDTOS); + Menu findOne(Long id); /** - * findByRoles - * @param roles - * @return + * 删除 + * @param menuSet / */ - List findByRoles(List roles); + void delete(Set menuSet); /** - * buildMenus - * @param byRoles - * @return + * 导出 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / */ - Object buildMenus(List byRoles); + void download(List queryAll, HttpServletResponse response) throws IOException; - Menu findOne(Long id); + /** + * 懒加载菜单数据 + * @param pid / + * @return / + */ + List getMenus(Long pid); + + /** + * 根据ID获取同级与上级数据 + * @param menuDto / + * @param objects / + * @return / + */ + List getSuperior(MenuDto menuDto, List objects); + + /** + * 根据当前用户获取菜单 + * @param currentUserId / + * @return / + */ + List findByUser(Long currentUserId); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MonitorService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MonitorService.java new file mode 100644 index 000000000..f7e34ad96 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/MonitorService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service; + +import java.util.Map; + +/** + * @author Zheng Jie + * @date 2020-05-02 + */ +public interface MonitorService { + + /** + * 查询数据分页 + * @return Map + */ + Map getServers(); +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/PermissionService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/PermissionService.java deleted file mode 100644 index eb424e5ce..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/PermissionService.java +++ /dev/null @@ -1,79 +0,0 @@ -package me.zhengjie.modules.system.service; - -import me.zhengjie.modules.system.domain.Permission; -import me.zhengjie.modules.system.service.dto.PermissionDTO; -import me.zhengjie.modules.system.service.dto.PermissionQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; - -import java.util.List; - -/** - * @author Zheng Jie - * @date 2018-12-08 - */ -@CacheConfig(cacheNames = "permission") -public interface PermissionService { - - /** - * get - * @param id - * @return - */ - @Cacheable(key = "#p0") - PermissionDTO findById(long id); - - /** - * create - * @param resources - * @return - */ - @CacheEvict(allEntries = true) - PermissionDTO create(Permission resources); - - /** - * update - * @param resources - */ - @CacheEvict(allEntries = true) - void update(Permission resources); - - /** - * delete - * @param id - */ - @CacheEvict(allEntries = true) - void delete(Long id); - - /** - * permission tree - * @return - */ - @Cacheable(key = "'tree'") - Object getPermissionTree(List permissions); - - /** - * findByPid - * @param pid - * @return - */ - @Cacheable(key = "'pid:'+#p0") - List findByPid(long pid); - - /** - * build Tree - * @param permissionDTOS - * @return - */ - @Cacheable(keyGenerator = "keyGenerator") - Object buildTree(List permissionDTOS); - - /** - * queryAll - * @param criteria - * @return - */ - @Cacheable(keyGenerator = "keyGenerator") - List queryAll(PermissionQueryCriteria criteria); -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java index eccb24595..d1743cd9b 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java @@ -1,15 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; -import me.zhengjie.modules.system.domain.Menu; +import me.zhengjie.modules.security.service.dto.AuthorityDto; import me.zhengjie.modules.system.domain.Role; -import me.zhengjie.modules.system.service.dto.RoleDTO; +import me.zhengjie.modules.system.service.dto.RoleDto; import me.zhengjie.modules.system.service.dto.RoleQueryCriteria; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; +import me.zhengjie.utils.PageResult; import org.springframework.data.domain.Pageable; - +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.List; import java.util.Set; @@ -17,82 +32,106 @@ * @author Zheng Jie * @date 2018-12-03 */ -@CacheConfig(cacheNames = "role") public interface RoleService { /** - * get - * @param id - * @return + * 查询全部数据 + * @return / + */ + List queryAll(); + + /** + * 根据ID查询 + * @param id / + * @return / */ - @Cacheable(key = "#p0") - RoleDTO findById(long id); + RoleDto findById(long id); /** - * create - * @param resources - * @return + * 创建 + * @param resources / */ - @CacheEvict(allEntries = true) - RoleDTO create(Role resources); + void create(Role resources); /** - * update - * @param resources + * 编辑 + * @param resources / */ - @CacheEvict(allEntries = true) void update(Role resources); /** - * delete - * @param id + * 删除 + * @param ids / */ - @CacheEvict(allEntries = true) - void delete(Long id); + void delete(Set ids); /** - * key的名称如有修改,请同步修改 UserServiceImpl 中的 update 方法 - * findByUsers_Id - * @param id - * @return + * 根据用户ID查询 + * @param userId 用户ID + * @return / */ - @Cacheable(key = "'findByUsers_Id:' + #p0") - List findByUsers_Id(Long id); + List findByUsersId(Long userId); - @Cacheable(keyGenerator = "keyGenerator") + /** + * 根据角色查询角色级别 + * @param roles / + * @return / + */ Integer findByRoles(Set roles); /** - * updatePermission - * @param resources - * @param roleDTO + * 修改绑定的菜单 + * @param resources / + * @param roleDTO / + */ + void updateMenu(Role resources, RoleDto roleDTO); + + /** + * 解绑菜单 + * @param id / */ - @CacheEvict(allEntries = true) - void updatePermission(Role resources, RoleDTO roleDTO); + void untiedMenu(Long id); /** - * updateMenu - * @param resources - * @param roleDTO + * 待条件分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / */ - @CacheEvict(allEntries = true) - void updateMenu(Role resources, RoleDTO roleDTO); + PageResult queryAll(RoleQueryCriteria criteria, Pageable pageable); - @CacheEvict(allEntries = true) - void untiedMenu(Menu menu); + /** + * 查询全部 + * @param criteria 条件 + * @return / + */ + List queryAll(RoleQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 获取用户权限信息 + * @param user 用户信息 + * @return 权限信息 + */ + List buildPermissions(UserDto user); /** - * queryAll - * @param pageable - * @return + * 验证是否被用户关联 + * @param ids / */ - Object queryAll(Pageable pageable); + void verification(Set ids); /** - * queryAll - * @param pageable - * @param criteria - * @return + * 根据菜单Id查询 + * @param menuIds / + * @return / */ - Object queryAll(RoleQueryCriteria criteria, Pageable pageable); + List findInMenuId(List menuIds); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/UserService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/UserService.java index 5fe40b5f1..4e414c5ae 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/UserService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/UserService.java @@ -1,83 +1,132 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service; +import me.zhengjie.utils.PageResult; import me.zhengjie.modules.system.domain.User; -import me.zhengjie.modules.security.security.JwtUser; -import me.zhengjie.modules.system.service.dto.UserDTO; +import me.zhengjie.modules.system.service.dto.UserDto; import me.zhengjie.modules.system.service.dto.UserQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * @author Zheng Jie * @date 2018-11-23 */ -@CacheConfig(cacheNames = "user") public interface UserService { /** - * get - * @param id - * @return + * 根据ID查询 + * @param id ID + * @return / */ - @Cacheable(key = "#p0") - UserDTO findById(long id); + UserDto findById(long id); /** - * create - * @param resources - * @return + * 新增用户 + * @param resources / */ - @CacheEvict(allEntries = true) - UserDTO create(User resources); + void create(User resources); /** - * update - * @param resources + * 编辑用户 + * @param resources / + * @throws Exception / */ - @CacheEvict(allEntries = true) - void update(User resources); + void update(User resources) throws Exception; /** - * delete - * @param id + * 删除用户 + * @param ids / */ - @CacheEvict(allEntries = true) - void delete(Long id); + void delete(Set ids); /** - * findByName - * @param userName - * @return + * 根据用户名查询 + * @param userName / + * @return / */ - @Cacheable(key = "'loadUserByUsername:'+#p0") - UserDTO findByName(String userName); + UserDto findByName(String userName); + + /** + * 根据用户名查询 + * @param userName / + * @return / + */ + UserDto getLoginData(String userName); /** * 修改密码 - * @param username - * @param encryptPassword + * @param username 用户名 + * @param encryptPassword 密码 */ - @CacheEvict(allEntries = true) void updatePass(String username, String encryptPassword); /** * 修改头像 - * @param username - * @param url + * @param file 文件 + * @return / */ - @CacheEvict(allEntries = true) - void updateAvatar(String username, String url); + Map updateAvatar(MultipartFile file); /** * 修改邮箱 - * @param username - * @param email + * @param username 用户名 + * @param email 邮箱 */ - @CacheEvict(allEntries = true) void updateEmail(String username, String email); - @Cacheable(keyGenerator = "keyGenerator") - Object queryAll(UserQueryCriteria criteria, Pageable pageable); + /** + * 查询全部 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(UserQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部不分页 + * @param criteria 条件 + * @return / + */ + List queryAll(UserQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 用户自助修改资料 + * @param resources / + */ + void updateCenter(User resources); + + /** + * 重置密码 + * @param ids 用户id + * @param pwd 密码 + */ + void resetPwd(Set ids, String pwd); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/VerifyService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/VerifyService.java new file mode 100644 index 000000000..cc4a6b5ea --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/VerifyService.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service; + +import me.zhengjie.domain.vo.EmailVo; + +/** + * @author Zheng Jie + * @date 2018-12-26 + */ +public interface VerifyService { + + /** + * 发送验证码 + * @param email / + * @param key / + * @return / + */ + EmailVo sendEmail(String email, String key); + + + /** + * 验证 + * @param code / + * @param key / + */ + void validated(String key, String code); +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDTO.java deleted file mode 100644 index 47e808ee4..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDTO.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.Data; - -import javax.validation.constraints.NotNull; -import java.sql.Timestamp; -import java.io.Serializable; -import java.util.List; - -/** -* @author Zheng Jie -* @date 2019-03-25 -*/ -@Data -public class DeptDTO implements Serializable { - - /** - * ID - */ - private Long id; - - /** - * 名称 - */ - private String name; - - @NotNull - private Boolean enabled; - - /** - * 上级部门 - */ - private Long pid; - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List children; - - private Timestamp createTime; - - public String getLabel() { - return name; - } -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDto.java new file mode 100644 index 000000000..1be9a60d0 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptDto.java @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** +* @author Zheng Jie +* @date 2019-03-25 +*/ +@Getter +@Setter +public class DeptDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "排序") + private Integer deptSort; + + @ApiModelProperty(value = "子部门") + private List children; + + @ApiModelProperty(value = "上级部门") + private Long pid; + + @ApiModelProperty(value = "子部门数量", hidden = true) + private Integer subCount; + + @ApiModelProperty(value = "是否有子节点") + public Boolean getHasChildren() { + return subCount > 0; + } + + @ApiModelProperty(value = "是否为叶子") + public Boolean getLeaf() { + return subCount <= 0; + } + + @ApiModelProperty(value = "部门全名") + public String getLabel() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DeptDto deptDto = (DeptDto) o; + return Objects.equals(id, deptDto.id) && + Objects.equals(name, deptDto.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptQueryCriteria.java index bb5000ebb..900be67a4 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptQueryCriteria.java @@ -1,25 +1,52 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import me.zhengjie.annotation.DataPermission; import me.zhengjie.annotation.Query; -import java.util.Set; +import java.sql.Timestamp; +import java.util.List; /** * @author Zheng Jie * @date 2019-03-25 */ @Data +@DataPermission(fieldName = "id") public class DeptQueryCriteria{ - @Query(type = Query.Type.IN, propName="id") - private Set ids; - + @ApiModelProperty(value = "名称") @Query(type = Query.Type.INNER_LIKE) private String name; @Query + @ApiModelProperty(value = "是否启用") private Boolean enabled; @Query + @ApiModelProperty(value = "上级部门") private Long pid; + + @ApiModelProperty(value = "PID空查询", hidden = true) + @Query(type = Query.Type.IS_NULL, propName = "pid") + private Boolean pidIsNull; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDTO.java deleted file mode 100644 index 10620264b..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import java.io.Serializable; - -/** -* @author Zheng Jie -* @date 2019-6-10 16:32:18 -*/ -@Data -public class DeptSmallDTO implements Serializable { - - /** - * ID - */ - private Long id; - - /** - * 名称 - */ - private String name; -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDto.java new file mode 100644 index 000000000..1356d5254 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DeptSmallDto.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-6-10 16:32:18 +*/ +@Data +public class DeptSmallDto implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDTO.java deleted file mode 100644 index a96a4895a..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import java.io.Serializable; - -/** -* @author Zheng Jie -* @date 2019-04-10 -*/ -@Data -public class DictDTO implements Serializable { - - private Long id; - - /** - * 字典名称 - */ - private String name; - - /** - * 描述 - */ - private String remark; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDTO.java deleted file mode 100644 index b39fbb263..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDTO.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import java.io.Serializable; - -/** -* @author Zheng Jie -* @date 2019-04-10 -*/ -@Data -public class DictDetailDTO implements Serializable { - - private Long id; - - /** - * 字典标签 - */ - private String label; - - /** - * 字典值 - */ - private String value; - - /** - * 排序 - */ - private String sort; -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDto.java new file mode 100644 index 000000000..75c0f48d0 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailDto.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-04-10 +*/ +@Getter +@Setter +public class DictDetailDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "字典ID") + private DictSmallDto dict; + + @ApiModelProperty(value = "字典标签") + private String label; + + @ApiModelProperty(value = "字典值") + private String value; + + @ApiModelProperty(value = "排序") + private Integer dictSort; +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailQueryCriteria.java index 4678d8a9f..11e4e51b7 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDetailQueryCriteria.java @@ -1,5 +1,21 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.zhengjie.annotation.Query; @@ -10,9 +26,11 @@ @Data public class DictDetailQueryCriteria { + @ApiModelProperty(value = "字典标签") @Query(type = Query.Type.INNER_LIKE) private String label; + @ApiModelProperty(value = "字典名称") @Query(propName = "name",joinName = "dict") private String dictName; } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDto.java new file mode 100644 index 000000000..bdec264f7 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictDto.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.List; + +/** +* @author Zheng Jie +* @date 2019-04-10 +*/ +@Getter +@Setter +public class DictDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "字典详情") + private List dictDetails; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictQueryCriteria.java index fe2e21932..e95646312 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictQueryCriteria.java @@ -1,15 +1,32 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.zhengjie.annotation.Query; /** + * @author Zheng Jie * 公共查询类 */ @Data public class DictQueryCriteria { - // 多字段模糊 - @Query(blurry = "name,remark") + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "name,description") private String blurry; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictSmallDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictSmallDto.java new file mode 100644 index 000000000..7c16fdc35 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/DictSmallDto.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-04-10 +*/ +@Getter +@Setter +public class DictSmallDto implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDTO.java deleted file mode 100644 index d61c5ee5b..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDTO.java +++ /dev/null @@ -1,52 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.sql.Timestamp; -import java.io.Serializable; - -/** -* @author Zheng Jie -* @date 2019-03-29 -*/ -@Data -@NoArgsConstructor -public class JobDTO implements Serializable { - - /** - * ID - */ - private Long id; - - private Long sort; - - /** - * 名称 - */ - private String name; - - /** - * 状态 - */ - private Boolean enabled; - - private DeptDTO dept; - - /** - * 如果分公司存在相同部门,则显示上级部门名称 - */ - private String deptSuperiorName; - - /** - * 创建日期 - */ - private Timestamp createTime; - - public JobDTO(String name, Boolean enabled) { - this.name = name; - this.enabled = enabled; - } -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDto.java new file mode 100644 index 000000000..9852d3c27 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobDto.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; + +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-03-29 +*/ +@Getter +@Setter +@NoArgsConstructor +public class JobDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "岗位排序") + private Integer jobSort; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + public JobDto(String name, Boolean enabled) { + this.name = name; + this.enabled = enabled; + } +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobQueryCriteria.java index 8d535fb55..90895c2b0 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobQueryCriteria.java @@ -1,12 +1,26 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.NoArgsConstructor; import me.zhengjie.annotation.Query; - -import java.io.Serializable; import java.sql.Timestamp; -import java.util.Set; +import java.util.List; /** * @author Zheng Jie @@ -16,15 +30,15 @@ @NoArgsConstructor public class JobQueryCriteria { + @ApiModelProperty(value = "岗位名称") @Query(type = Query.Type.INNER_LIKE) private String name; @Query + @ApiModelProperty(value = "岗位状态") private Boolean enabled; - @Query(propName = "id", joinName = "dept") - private Long deptId; - - @Query(propName = "id", joinName = "dept", type = Query.Type.IN) - private Set deptIds; + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; } \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDTO.java deleted file mode 100644 index 2a3645472..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import lombok.NoArgsConstructor; -import java.io.Serializable; - -/** -* @author Zheng Jie -* @date 2019-6-10 16:32:18 -*/ -@Data -@NoArgsConstructor -public class JobSmallDTO implements Serializable { - - /** - * ID - */ - private Long id; - - /** - * 名称 - */ - private String name; -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDto.java new file mode 100644 index 000000000..50f04a544 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/JobSmallDto.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-6-10 16:32:18 +*/ +@Data +@NoArgsConstructor +public class JobSmallDto implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDTO.java deleted file mode 100644 index 9dd8bd610..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDTO.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; - -import java.sql.Timestamp; -import java.util.List; -import java.util.Set; - -/** - * @author Zheng Jie - * @date 2018-12-17 - */ -@Data -public class MenuDTO { - - private Long id; - - private String name; - - private Long sort; - - private String path; - - private String component; - - private Long pid; - - private Boolean iFrame; - - private String icon; - - private List children; - - private Timestamp createTime; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDto.java new file mode 100644 index 000000000..44e9d6509 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuDto.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * @author Zheng Jie + * @date 2018-12-17 + */ +@Getter +@Setter +public class MenuDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "子节点") + private List children; + + @ApiModelProperty(value = "类型") + private Integer type; + + @ApiModelProperty(value = "权限") + private String permission; + + @ApiModelProperty(value = "菜单标题") + private String title; + + @ApiModelProperty(value = "排序") + private Integer menuSort; + + @ApiModelProperty(value = "路径") + private String path; + + @ApiModelProperty(value = "组件") + private String component; + + @ApiModelProperty(value = "PID") + private Long pid; + + @ApiModelProperty(value = "子节点数目") + private Integer subCount; + + @ApiModelProperty(value = "是否为Iframe") + @JsonProperty("iFrame") + private Boolean iFrame; + + @ApiModelProperty(value = "是否缓存") + private Boolean cache; + + @ApiModelProperty(value = "是否隐藏") + private Boolean hidden; + + @ApiModelProperty(value = "组件名称") + private String componentName; + + @ApiModelProperty(value = "图标") + private String icon; + + @ApiModelProperty(value = "是否存在子节点") + public Boolean getHasChildren() { + return subCount > 0; + } + + @ApiModelProperty(value = "是否叶子节点") + public Boolean getLeaf() { + return subCount <= 0; + } + + @ApiModelProperty(value = "标题") + public String getLabel() { + return title; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuDto menuDto = (MenuDto) o; + return Objects.equals(id, menuDto.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuQueryCriteria.java index ccde232da..79027d52e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/MenuQueryCriteria.java @@ -1,15 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; /** + * @author Zheng Jie * 公共查询类 */ @Data public class MenuQueryCriteria { - // 多字段模糊 - @Query(blurry = "name,path,component") + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "title,component,permission") private String blurry; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; + + @ApiModelProperty(value = "PID空查询", hidden = true) + @Query(type = Query.Type.IS_NULL, propName = "pid") + private Boolean pidIsNull; + + @Query + @ApiModelProperty(value = "PID") + private Long pid; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/PermissionDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/PermissionDTO.java deleted file mode 100644 index 3ae4eb6ad..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/PermissionDTO.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; - -import java.io.Serializable; -import java.sql.Timestamp; -import java.util.List; - -/** - * @author Zheng Jie - * @date 2018-12-03 - */ -@Data -public class PermissionDTO implements Serializable{ - - private Long id; - - private String name; - - private Long pid; - - private String alias; - - private Timestamp createTime; - - private List children; - - @Override - public String toString() { - return "Permission{" + - "id=" + id + - ", name='" + name + '\'' + - ", pid=" + pid + - ", alias='" + alias + '\'' + - ", createTime=" + createTime + - '}'; - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/PermissionQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/PermissionQueryCriteria.java deleted file mode 100644 index 055b0ec47..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/PermissionQueryCriteria.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import me.zhengjie.annotation.Query; - -/** - * 公共查询类 - */ -@Data -public class PermissionQueryCriteria { - - // 多字段模糊 - @Query(blurry = "name,alias") - private String blurry; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDTO.java deleted file mode 100644 index c6d680735..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; -import java.io.Serializable; -import java.sql.Timestamp; -import java.util.Set; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Data -public class RoleDTO implements Serializable { - - private Long id; - - private String name; - - private String dataScope; - - private Integer level; - - private String remark; - - private Set permissions; - - private Set menus; - - private Set depts; - - private Timestamp createTime; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDto.java new file mode 100644 index 000000000..5db3fd536 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleDto.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.Objects; +import java.util.Set; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Getter +@Setter +public class RoleDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "菜单") + private Set menus; + + @ApiModelProperty(value = "部门") + private Set depts; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "数据权限") + private String dataScope; + + @ApiModelProperty(value = "级别") + private Integer level; + + @ApiModelProperty(value = "描述") + private String description; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RoleDto roleDto = (RoleDto) o; + return Objects.equals(id, roleDto.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleQueryCriteria.java index 3c9358891..bfdbcf067 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleQueryCriteria.java @@ -1,15 +1,39 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + /** + * @author Zheng Jie * 公共查询类 */ @Data public class RoleQueryCriteria { - // 多字段模糊 - @Query(blurry = "name,remark") + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "name,description") private String blurry; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDTO.java deleted file mode 100644 index 7e9d9fc1b..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDTO.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import lombok.Data; - -import java.io.Serializable; -import java.sql.Timestamp; -import java.util.Set; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Data -public class RoleSmallDTO implements Serializable { - - private Long id; - - private String name; - - private Integer level; - - private String dataScope; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDto.java new file mode 100644 index 000000000..d026ff34e --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/RoleSmallDto.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Data +public class RoleSmallDto implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "级别") + private Integer level; + + @ApiModelProperty(value = "数据权限") + private String dataScope; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDTO.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDTO.java deleted file mode 100644 index 3065471b7..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDTO.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.zhengjie.modules.system.service.dto; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import java.io.Serializable; -import java.sql.Timestamp; -import java.util.Date; -import java.util.Set; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Data -public class UserDTO implements Serializable { - - @ApiModelProperty(hidden = true) - private Long id; - - private String username; - - private String avatar; - - private String email; - - private String phone; - - private Boolean enabled; - - @JsonIgnore - private String password; - - private Timestamp createTime; - - private Date lastPasswordResetTime; - - @ApiModelProperty(hidden = true) - private Set roles; - - @ApiModelProperty(hidden = true) - private JobSmallDTO job; - - private DeptSmallDTO dept; - - private Long deptId; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDto.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDto.java new file mode 100644 index 000000000..9ec687407 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserDto.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; +import java.util.Date; +import java.util.Set; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Getter +@Setter +public class UserDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "角色") + private Set roles; + + @ApiModelProperty(value = "岗位") + private Set jobs; + + @ApiModelProperty(value = "部门") + private DeptSmallDto dept; + + @ApiModelProperty(value = "部门ID") + private Long deptId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickName; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "电话") + private String phone; + + @ApiModelProperty(value = "性别") + private String gender; + + @ApiModelProperty(value = "头像") + private String avatarName; + + @ApiModelProperty(value = "头像路径") + private String avatarPath; + + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "管理员") + @JSONField(serialize = false) + private Boolean isAdmin = false; + + @ApiModelProperty(value = "密码重置时间") + private Date pwdResetTime; +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserQueryCriteria.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserQueryCriteria.java index a69467c9f..e1da3434e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserQueryCriteria.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/dto/UserQueryCriteria.java @@ -1,8 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.zhengjie.annotation.Query; import java.io.Serializable; +import java.sql.Timestamp; +import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -13,17 +32,25 @@ public class UserQueryCriteria implements Serializable { @Query + @ApiModelProperty(value = "ID") private Long id; + @ApiModelProperty(value = "部门ID集合") @Query(propName = "id", type = Query.Type.IN, joinName = "dept") - private Set deptIds; + private Set deptIds = new HashSet<>(); - // 多字段模糊 - @Query(blurry = "email,username") + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "email,username,nickName") private String blurry; @Query + @ApiModelProperty(value = "是否启用") private Boolean enabled; + @ApiModelProperty(value = "部门ID") private Long deptId; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DataServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DataServiceImpl.java new file mode 100644 index 000000000..66daee94d --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DataServiceImpl.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.system.domain.Dept; +import me.zhengjie.modules.system.service.DataService; +import me.zhengjie.modules.system.service.DeptService; +import me.zhengjie.modules.system.service.RoleService; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; +import me.zhengjie.utils.CacheKey; +import me.zhengjie.utils.RedisUtils; +import me.zhengjie.utils.enums.DataScopeEnum; +import org.springframework.stereotype.Service; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author Zheng Jie + * @description 数据权限服务实现 + * @date 2020-05-07 + **/ +@Service +@RequiredArgsConstructor +public class DataServiceImpl implements DataService { + + private final RedisUtils redisUtils; + private final RoleService roleService; + private final DeptService deptService; + + /** + * 用户角色和用户部门改变时需清理缓存 + * @param user / + * @return / + */ + @Override + public List getDeptIds(UserDto user) { + String key = CacheKey.DATA_USER + user.getId(); + List ids = redisUtils.getList(key, Long.class); + if (CollUtil.isEmpty(ids)) { + // 用于存储部门id + Set deptIds = new HashSet<>(); + // 查询用户角色 + List roleSet = roleService.findByUsersId(user.getId()); + // 获取对应的部门ID + for (RoleSmallDto role : roleSet) { + DataScopeEnum dataScopeEnum = DataScopeEnum.find(role.getDataScope()); + switch (Objects.requireNonNull(dataScopeEnum)) { + case THIS_LEVEL: + deptIds.add(user.getDept().getId()); + break; + case CUSTOMIZE: + deptIds.addAll(getCustomize(deptIds, role)); + break; + default: + return new ArrayList<>(); + } + } + ids = new ArrayList<>(deptIds); + redisUtils.set(key, ids, 1, TimeUnit.DAYS); + } + return new ArrayList<>(ids); + } + + /** + * 获取自定义的数据权限 + * @param deptIds 部门ID + * @param role 角色 + * @return 数据权限ID + */ + public Set getCustomize(Set deptIds, RoleSmallDto role){ + Set depts = deptService.findByRoleId(role.getId()); + for (Dept dept : depts) { + deptIds.add(dept.getId()); + List deptChildren = deptService.findByPid(dept.getId()); + if (CollUtil.isNotEmpty(deptChildren)) { + deptIds.addAll(deptService.getDeptChildren(deptChildren)); + } + } + return deptIds; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DeptServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DeptServiceImpl.java index f56e19031..0847dd9bc 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DeptServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DeptServiceImpl.java @@ -1,20 +1,44 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.domain.Dept; +import me.zhengjie.modules.system.domain.User; +import me.zhengjie.modules.system.repository.RoleRepository; +import me.zhengjie.modules.system.repository.UserRepository; +import me.zhengjie.modules.system.service.dto.DeptDto; import me.zhengjie.modules.system.service.dto.DeptQueryCriteria; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.*; import me.zhengjie.modules.system.repository.DeptRepository; import me.zhengjie.modules.system.service.DeptService; -import me.zhengjie.modules.system.service.dto.DeptDTO; -import me.zhengjie.modules.system.service.mapper.DeptMapper; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.mapstruct.DeptMapper; +import me.zhengjie.utils.enums.DataScopeEnum; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.CollectionUtils; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -22,25 +46,56 @@ * @date 2019-03-25 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class DeptServiceImpl implements DeptService { - @Autowired - private DeptRepository deptRepository; - - @Autowired - private DeptMapper deptMapper; + private final DeptRepository deptRepository; + private final DeptMapper deptMapper; + private final UserRepository userRepository; + private final RedisUtils redisUtils; + private final RoleRepository roleRepository; @Override - public List queryAll(DeptQueryCriteria criteria) { - return deptMapper.toDto(deptRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + public List queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception { + Sort sort = Sort.by(Sort.Direction.ASC, "deptSort"); + String dataScopeType = SecurityUtils.getDataScopeType(); + if (isQuery) { + if(dataScopeType.equals(DataScopeEnum.ALL.getValue())){ + criteria.setPidIsNull(true); + } + List fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>()); + List fieldNames = new ArrayList(){{ add("pidIsNull");add("enabled");}}; + for (Field field : fields) { + //设置对象的访问权限,保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(criteria); + if(fieldNames.contains(field.getName())){ + continue; + } + if (ObjectUtil.isNotNull(val)) { + criteria.setPidIsNull(null); + break; + } + } + } + List list = deptMapper.toDto(deptRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort)); + // 如果为空,就代表为自定义权限或者本级权限,就需要去重,不理解可以注释掉,看查询结果 + if(StringUtils.isBlank(dataScopeType)){ + return deduplication(list); + } + return list; } @Override - public DeptDTO findById(Long id) { - Optional dept = deptRepository.findById(id); - ValidationUtil.isNull(dept,"Dept","id",id); - return deptMapper.toDto(dept.get()); + public DeptDto findById(Long id) { + String key = CacheKey.DEPT_ID + id; + Dept dept = redisUtils.get(key, Dept.class); + if(dept == null){ + dept = deptRepository.findById(id).orElseGet(Dept::new); + ValidationUtil.isNull(dept.getId(),"Dept","id",id); + redisUtils.set(key, dept, 1, TimeUnit.DAYS); + } + return deptMapper.toDto(dept); } @Override @@ -49,70 +104,183 @@ public List findByPid(long pid) { } @Override - public Set findByRoleIds(Long id) { - return deptRepository.findByRoles_Id(id); + public Set findByRoleId(Long id) { + return deptRepository.findByRoleId(id); } @Override - public Object buildTree(List deptDTOS) { - Set trees = new LinkedHashSet<>(); - Set depts= new LinkedHashSet<>(); - List deptNames = deptDTOS.stream().map(DeptDTO::getName).collect(Collectors.toList()); - Boolean isChild; - for (DeptDTO deptDTO : deptDTOS) { + @Transactional(rollbackFor = Exception.class) + public void create(Dept resources) { + deptRepository.save(resources); + // 计算子节点数目 + resources.setSubCount(0); + // 清理缓存 + updateSubCnt(resources.getPid()); + // 清理自定义角色权限的datascope缓存 + delCaches(resources.getPid()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(Dept resources) { + // 旧的部门 + Long oldPid = findById(resources.getId()).getPid(); + Long newPid = resources.getPid(); + if(resources.getPid() != null && resources.getId().equals(resources.getPid())) { + throw new BadRequestException("上级不能为自己"); + } + Dept dept = deptRepository.findById(resources.getId()).orElseGet(Dept::new); + ValidationUtil.isNull( dept.getId(),"Dept","id",resources.getId()); + resources.setId(dept.getId()); + deptRepository.save(resources); + // 更新父节点中子节点数目 + updateSubCnt(oldPid); + updateSubCnt(newPid); + // 清理缓存 + delCaches(resources.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set deptDtos) { + for (DeptDto deptDto : deptDtos) { + // 清理缓存 + delCaches(deptDto.getId()); + deptRepository.deleteById(deptDto.getId()); + updateSubCnt(deptDto.getPid()); + } + } + + @Override + public void download(List deptDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DeptDto deptDTO : deptDtos) { + Map map = new LinkedHashMap<>(); + map.put("部门名称", deptDTO.getName()); + map.put("部门状态", deptDTO.getEnabled() ? "启用" : "停用"); + map.put("创建日期", deptDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + @Override + public Set getDeleteDepts(List menuList, Set deptDtos) { + for (Dept dept : menuList) { + deptDtos.add(deptMapper.toDto(dept)); + List depts = deptRepository.findByPid(dept.getId()); + if(CollUtil.isNotEmpty(depts)){ + getDeleteDepts(depts, deptDtos); + } + } + return deptDtos; + } + + @Override + public List getDeptChildren(List deptList) { + List list = new ArrayList<>(); + deptList.forEach(dept -> { + if (dept!=null && dept.getEnabled()) { + List depts = deptRepository.findByPid(dept.getId()); + if (CollUtil.isNotEmpty(depts)) { + list.addAll(getDeptChildren(depts)); + } + list.add(dept.getId()); + } + } + ); + return list; + } + + @Override + public List getSuperior(DeptDto deptDto, List depts) { + if(deptDto.getPid() == null){ + depts.addAll(deptRepository.findByPidIsNull()); + return deptMapper.toDto(depts); + } + depts.addAll(deptRepository.findByPid(deptDto.getPid())); + return getSuperior(findById(deptDto.getPid()), depts); + } + + @Override + public Object buildTree(List deptDtos) { + Set trees = new LinkedHashSet<>(); + Set depts= new LinkedHashSet<>(); + List deptNames = deptDtos.stream().map(DeptDto::getName).collect(Collectors.toList()); + boolean isChild; + for (DeptDto deptDTO : deptDtos) { isChild = false; - if ("0".equals(deptDTO.getPid().toString())) { + if (deptDTO.getPid() == null) { trees.add(deptDTO); } - for (DeptDTO it : deptDTOS) { - if (it.getPid().equals(deptDTO.getId())) { + for (DeptDto it : deptDtos) { + if (it.getPid() != null && deptDTO.getId().equals(it.getPid())) { isChild = true; if (deptDTO.getChildren() == null) { - deptDTO.setChildren(new ArrayList()); + deptDTO.setChildren(new ArrayList<>()); } deptDTO.getChildren().add(it); } } - if(isChild) + if(isChild) { depts.add(deptDTO); - else if(!deptNames.contains(deptRepository.findNameById(deptDTO.getPid()))) + } else if(deptDTO.getPid() != null && !deptNames.contains(findById(deptDTO.getPid()).getName())) { depts.add(deptDTO); + } } - if (CollectionUtils.isEmpty(trees)) { + if (CollectionUtil.isEmpty(trees)) { trees = depts; } - - Integer totalElements = deptDTOS!=null?deptDTOS.size():0; - - Map map = new HashMap(); - map.put("totalElements",totalElements); - map.put("content",CollectionUtils.isEmpty(trees)?deptDTOS:trees); + Map map = new HashMap<>(2); + map.put("totalElements",deptDtos.size()); + map.put("content",CollectionUtil.isEmpty(trees)? deptDtos :trees); return map; } @Override - @Transactional(rollbackFor = Exception.class) - public DeptDTO create(Dept resources) { - return deptMapper.toDto(deptRepository.save(resources)); + public void verification(Set deptDtos) { + Set deptIds = deptDtos.stream().map(DeptDto::getId).collect(Collectors.toSet()); + if(userRepository.countByDepts(deptIds) > 0){ + throw new BadRequestException("所选部门存在用户关联,请解除后再试!"); + } + if(roleRepository.countByDepts(deptIds) > 0){ + throw new BadRequestException("所选部门存在角色关联,请解除后再试!"); + } } - @Override - @Transactional(rollbackFor = Exception.class) - public void update(Dept resources) { - if(resources.getId().equals(resources.getPid())) { - throw new BadRequestException("上级不能为自己"); + private void updateSubCnt(Long deptId){ + if(deptId != null){ + int count = deptRepository.countByPid(deptId); + deptRepository.updateSubCntById(count, deptId); } - Optional optionalDept = deptRepository.findById(resources.getId()); - ValidationUtil.isNull( optionalDept,"Dept","id",resources.getId()); - Dept dept = optionalDept.get(); - resources.setId(dept.getId()); - deptRepository.save(resources); } - @Override - @Transactional(rollbackFor = Exception.class) - public void delete(Long id) { - deptRepository.deleteById(id); + private List deduplication(List list) { + List deptDtos = new ArrayList<>(); + for (DeptDto deptDto : list) { + boolean flag = true; + for (DeptDto dto : list) { + if (dto.getId().equals(deptDto.getPid())) { + flag = false; + break; + } + } + if (flag){ + deptDtos.add(deptDto); + } + } + return deptDtos; + } + + /** + * 清理缓存 + * @param id / + */ + public void delCaches(Long id){ + List users = userRepository.findByRoleDeptId(id); + // 删除数据权限 + redisUtils.delByKeys(CacheKey.DATA_USER, users.stream().map(User::getId).collect(Collectors.toSet())); + redisUtils.del(CacheKey.DEPT_ID + id); } -} \ No newline at end of file +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictDetailServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictDetailServiceImpl.java index d89702199..2a008457a 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictDetailServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictDetailServiceImpl.java @@ -1,70 +1,99 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; +import cn.hutool.core.collection.CollUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.utils.PageResult; +import me.zhengjie.modules.system.domain.Dict; import me.zhengjie.modules.system.domain.DictDetail; +import me.zhengjie.modules.system.repository.DictRepository; import me.zhengjie.modules.system.service.dto.DictDetailQueryCriteria; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.*; import me.zhengjie.modules.system.repository.DictDetailRepository; import me.zhengjie.modules.system.service.DictDetailService; -import me.zhengjie.modules.system.service.dto.DictDetailDTO; -import me.zhengjie.modules.system.service.mapper.DictDetailMapper; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.dto.DictDetailDto; +import me.zhengjie.modules.system.service.mapstruct.DictDetailMapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; - -import java.util.Map; -import java.util.Optional; +import java.util.List; +import java.util.concurrent.TimeUnit; /** * @author Zheng Jie * @date 2019-04-10 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class DictDetailServiceImpl implements DictDetailService { - @Autowired - private DictDetailRepository dictDetailRepository; - - @Autowired - private DictDetailMapper dictDetailMapper; + private final DictRepository dictRepository; + private final DictDetailRepository dictDetailRepository; + private final DictDetailMapper dictDetailMapper; + private final RedisUtils redisUtils; @Override - public Map queryAll(DictDetailQueryCriteria criteria, Pageable pageable) { + public PageResult queryAll(DictDetailQueryCriteria criteria, Pageable pageable) { Page page = dictDetailRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); return PageUtil.toPage(page.map(dictDetailMapper::toDto)); } - @Override - public DictDetailDTO findById(Long id) { - Optional dictDetail = dictDetailRepository.findById(id); - ValidationUtil.isNull(dictDetail,"DictDetail","id",id); - return dictDetailMapper.toDto(dictDetail.get()); - } - @Override @Transactional(rollbackFor = Exception.class) - public DictDetailDTO create(DictDetail resources) { - return dictDetailMapper.toDto(dictDetailRepository.save(resources)); + public void create(DictDetail resources) { + dictDetailRepository.save(resources); + // 清理缓存 + delCaches(resources); } @Override @Transactional(rollbackFor = Exception.class) public void update(DictDetail resources) { - Optional optionalDictDetail = dictDetailRepository.findById(resources.getId()); - ValidationUtil.isNull( optionalDictDetail,"DictDetail","id",resources.getId()); - DictDetail dictDetail = optionalDictDetail.get(); + DictDetail dictDetail = dictDetailRepository.findById(resources.getId()).orElseGet(DictDetail::new); + ValidationUtil.isNull( dictDetail.getId(),"DictDetail","id",resources.getId()); resources.setId(dictDetail.getId()); dictDetailRepository.save(resources); + // 清理缓存 + delCaches(resources); + } + + @Override + public List getDictByName(String name) { + String key = CacheKey.DICT_NAME + name; + List dictDetails = redisUtils.getList(key, DictDetail.class); + if(CollUtil.isEmpty(dictDetails)){ + dictDetails = dictDetailRepository.findByDictName(name); + redisUtils.set(key, dictDetails, 1 , TimeUnit.DAYS); + } + return dictDetailMapper.toDto(dictDetails); } @Override @Transactional(rollbackFor = Exception.class) public void delete(Long id) { + DictDetail dictDetail = dictDetailRepository.findById(id).orElseGet(DictDetail::new); + // 清理缓存 + delCaches(dictDetail); dictDetailRepository.deleteById(id); } -} \ No newline at end of file + + public void delCaches(DictDetail dictDetail){ + Dict dict = dictRepository.findById(dictDetail.getDict().getId()).orElseGet(Dict::new); + redisUtils.del(CacheKey.DICT_NAME + dict.getName()); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictServiceImpl.java index 12ad1da06..042200d00 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/DictServiceImpl.java @@ -1,68 +1,120 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; +import cn.hutool.core.collection.CollectionUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.utils.PageResult; import me.zhengjie.modules.system.domain.Dict; +import me.zhengjie.modules.system.service.dto.DictDetailDto; import me.zhengjie.modules.system.service.dto.DictQueryCriteria; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.*; import me.zhengjie.modules.system.repository.DictRepository; import me.zhengjie.modules.system.service.DictService; -import me.zhengjie.modules.system.service.dto.DictDTO; -import me.zhengjie.modules.system.service.mapper.DictMapper; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.dto.DictDto; +import me.zhengjie.modules.system.service.mapstruct.DictMapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; /** * @author Zheng Jie * @date 2019-04-10 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class DictServiceImpl implements DictService { - @Autowired - private DictRepository dictRepository; - - @Autowired - private DictMapper dictMapper; + private final DictRepository dictRepository; + private final DictMapper dictMapper; + private final RedisUtils redisUtils; @Override - public Object queryAll(DictQueryCriteria dict, Pageable pageable){ + public PageResult queryAll(DictQueryCriteria dict, Pageable pageable){ Page page = dictRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, dict, cb), pageable); return PageUtil.toPage(page.map(dictMapper::toDto)); } @Override - public DictDTO findById(Long id) { - Optional dict = dictRepository.findById(id); - ValidationUtil.isNull(dict,"Dict","id",id); - return dictMapper.toDto(dict.get()); + public List queryAll(DictQueryCriteria dict) { + List list = dictRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, dict, cb)); + return dictMapper.toDto(list); } @Override @Transactional(rollbackFor = Exception.class) - public DictDTO create(Dict resources) { - return dictMapper.toDto(dictRepository.save(resources)); + public void create(Dict resources) { + dictRepository.save(resources); } @Override @Transactional(rollbackFor = Exception.class) public void update(Dict resources) { - Optional optionalDict = dictRepository.findById(resources.getId()); - ValidationUtil.isNull( optionalDict,"Dict","id",resources.getId()); - Dict dict = optionalDict.get(); - resources.setId(dict.getId()); - dictRepository.save(resources); + // 清理缓存 + delCaches(resources); + Dict dict = dictRepository.findById(resources.getId()).orElseGet(Dict::new); + ValidationUtil.isNull( dict.getId(),"Dict","id",resources.getId()); + dict.setName(resources.getName()); + dict.setDescription(resources.getDescription()); + dictRepository.save(dict); } @Override @Transactional(rollbackFor = Exception.class) - public void delete(Long id) { - dictRepository.deleteById(id); + public void delete(Set ids) { + // 清理缓存 + List dicts = dictRepository.findByIdIn(ids); + for (Dict dict : dicts) { + delCaches(dict); + } + dictRepository.deleteByIdIn(ids); + } + + @Override + public void download(List dictDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DictDto dictDTO : dictDtos) { + if(CollectionUtil.isNotEmpty(dictDTO.getDictDetails())){ + for (DictDetailDto dictDetail : dictDTO.getDictDetails()) { + Map map = new LinkedHashMap<>(); + map.put("字典名称", dictDTO.getName()); + map.put("字典描述", dictDTO.getDescription()); + map.put("字典标签", dictDetail.getLabel()); + map.put("字典值", dictDetail.getValue()); + map.put("创建日期", dictDetail.getCreateTime()); + list.add(map); + } + } else { + Map map = new LinkedHashMap<>(); + map.put("字典名称", dictDTO.getName()); + map.put("字典描述", dictDTO.getDescription()); + map.put("字典标签", null); + map.put("字典值", null); + map.put("创建日期", dictDTO.getCreateTime()); + list.add(map); + } + } + FileUtil.downloadExcel(list, response); + } + + public void delCaches(Dict dict){ + redisUtils.del(CacheKey.DICT_NAME + dict.getName()); } -} \ No newline at end of file +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/JobServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/JobServiceImpl.java index 64c90972b..e6646c29d 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/JobServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/JobServiceImpl.java @@ -1,79 +1,136 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; +import lombok.RequiredArgsConstructor; +import me.zhengjie.utils.PageResult; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.exception.EntityExistException; import me.zhengjie.modules.system.domain.Job; -import me.zhengjie.modules.system.repository.DeptRepository; +import me.zhengjie.modules.system.repository.UserRepository; import me.zhengjie.modules.system.service.dto.JobQueryCriteria; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.*; import me.zhengjie.modules.system.repository.JobRepository; import me.zhengjie.modules.system.service.JobService; -import me.zhengjie.modules.system.service.dto.JobDTO; -import me.zhengjie.modules.system.service.mapper.JobMapper; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.dto.JobDto; +import me.zhengjie.modules.system.service.mapstruct.JobMapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; /** * @author Zheng Jie * @date 2019-03-29 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class JobServiceImpl implements JobService { - @Autowired - private JobRepository jobRepository; - - @Autowired - private JobMapper jobMapper; - - @Autowired - private DeptRepository deptRepository; + private final JobRepository jobRepository; + private final JobMapper jobMapper; + private final RedisUtils redisUtils; + private final UserRepository userRepository; @Override - public Object queryAll(JobQueryCriteria criteria, Pageable pageable) { + public PageResult queryAll(JobQueryCriteria criteria, Pageable pageable) { Page page = jobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); - List jobs = new ArrayList<>(); - for (Job job : page.getContent()) { - jobs.add(jobMapper.toDto(job,deptRepository.findNameById(job.getDept().getPid()))); - } - return PageUtil.toPage(jobs,page.getTotalElements()); + return PageUtil.toPage(page.map(jobMapper::toDto).getContent(),page.getTotalElements()); } @Override - public JobDTO findById(Long id) { - Optional job = jobRepository.findById(id); - ValidationUtil.isNull(job,"Job","id",id); - return jobMapper.toDto(job.get()); + public List queryAll(JobQueryCriteria criteria) { + List list = jobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + return jobMapper.toDto(list); + } + + @Override + public JobDto findById(Long id) { + String key = CacheKey.JOB_ID + id; + Job job = redisUtils.get(key, Job.class); + if(job == null){ + job = jobRepository.findById(id).orElseGet(Job::new); + ValidationUtil.isNull(job.getId(),"Job","id",id); + redisUtils.set(key, job, 1, TimeUnit.DAYS); + } + return jobMapper.toDto(job); } @Override @Transactional(rollbackFor = Exception.class) - public JobDTO create(Job resources) { - return jobMapper.toDto(jobRepository.save(resources)); + public void create(Job resources) { + Job job = jobRepository.findByName(resources.getName()); + if(job != null){ + throw new EntityExistException(Job.class,"name",resources.getName()); + } + jobRepository.save(resources); } @Override @Transactional(rollbackFor = Exception.class) public void update(Job resources) { - Optional optionalJob = jobRepository.findById(resources.getId()); - ValidationUtil.isNull( optionalJob,"Job","id",resources.getId()); - - Job job = optionalJob.get(); + Job job = jobRepository.findById(resources.getId()).orElseGet(Job::new); + Job old = jobRepository.findByName(resources.getName()); + if(old != null && !old.getId().equals(resources.getId())){ + throw new EntityExistException(Job.class,"name",resources.getName()); + } + ValidationUtil.isNull( job.getId(),"Job","id",resources.getId()); resources.setId(job.getId()); jobRepository.save(resources); + // 删除缓存 + delCaches(resources.getId()); } @Override @Transactional(rollbackFor = Exception.class) - public void delete(Long id) { - jobRepository.deleteById(id); + public void delete(Set ids) { + jobRepository.deleteAllByIdIn(ids); + // 删除缓存 + redisUtils.delByKeys(CacheKey.JOB_ID, ids); + } + + @Override + public void download(List jobDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (JobDto jobDTO : jobDtos) { + Map map = new LinkedHashMap<>(); + map.put("岗位名称", jobDTO.getName()); + map.put("岗位状态", jobDTO.getEnabled() ? "启用" : "停用"); + map.put("创建日期", jobDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + @Override + public void verification(Set ids) { + if(userRepository.countByJobs(ids) > 0){ + throw new BadRequestException("所选的岗位中存在用户关联,请解除关联再试!"); + } + } + + /** + * 删除缓存 + * @param id / + */ + public void delCaches(Long id){ + redisUtils.del(CacheKey.JOB_ID + id); } -} \ No newline at end of file +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java index 7d0815592..ae328c028 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java @@ -1,209 +1,378 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; import me.zhengjie.modules.system.domain.Menu; +import me.zhengjie.modules.system.domain.Role; +import me.zhengjie.modules.system.domain.User; import me.zhengjie.modules.system.domain.vo.MenuMetaVo; import me.zhengjie.modules.system.domain.vo.MenuVo; import me.zhengjie.exception.BadRequestException; import me.zhengjie.exception.EntityExistException; import me.zhengjie.modules.system.repository.MenuRepository; +import me.zhengjie.modules.system.repository.UserRepository; import me.zhengjie.modules.system.service.MenuService; -import me.zhengjie.modules.system.service.dto.MenuDTO; +import me.zhengjie.modules.system.service.RoleService; +import me.zhengjie.modules.system.service.dto.MenuDto; import me.zhengjie.modules.system.service.dto.MenuQueryCriteria; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; -import me.zhengjie.modules.system.service.mapper.MenuMapper; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.mapstruct.MenuMapper; +import me.zhengjie.utils.*; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +/** + * @author Zheng Jie + */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class MenuServiceImpl implements MenuService { - @Autowired - private MenuRepository menuRepository; - - @Autowired - private MenuMapper menuMapper; + private final MenuRepository menuRepository; + private final UserRepository userRepository; + private final MenuMapper menuMapper; + private final RoleService roleService; + private final RedisUtils redisUtils; + private static final String HTTP_PRE = "http://"; + private static final String HTTPS_PRE = "https://"; + private static final String YES_STR = "是"; + private static final String NO_STR = "否"; + private static final String BAD_REQUEST = "外链必须以http://或者https://开头"; + @Override - public List queryAll(MenuQueryCriteria criteria){ - return menuMapper.toDto(menuRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + public List queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception { + Sort sort = Sort.by(Sort.Direction.ASC, "menuSort"); + if(Boolean.TRUE.equals(isQuery)){ + criteria.setPidIsNull(true); + List fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>()); + for (Field field : fields) { + //设置对象的访问权限,保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(criteria); + if("pidIsNull".equals(field.getName())){ + continue; + } + if (ObjectUtil.isNotNull(val)) { + criteria.setPidIsNull(null); + break; + } + } + } + return menuMapper.toDto(menuRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort)); } @Override - public MenuDTO findById(long id) { - Optional menu = menuRepository.findById(id); - ValidationUtil.isNull(menu,"Menu","id",id); - return menuMapper.toDto(menu.get()); + public MenuDto findById(long id) { + String key = CacheKey.MENU_ID + id; + Menu menu = redisUtils.get(key, Menu.class); + if(menu == null){ + menu = menuRepository.findById(id).orElseGet(Menu::new); + ValidationUtil.isNull(menu.getId(),"Menu","id",id); + redisUtils.set(key, menu, 1, TimeUnit.DAYS); + } + return menuMapper.toDto(menu); } + /** + * 用户角色改变时需清理缓存 + * @param currentUserId / + * @return / + */ @Override - public List findByRoles(List roles) { - Set menus = new LinkedHashSet<>(); - for (RoleSmallDTO role : roles) { - List menus1 = menuRepository.findByRoles_IdOrderBySortAsc(role.getId()).stream().collect(Collectors.toList()); - menus.addAll(menus1); + public List findByUser(Long currentUserId) { + String key = CacheKey.MENU_USER + currentUserId; + List menus = redisUtils.getList(key, Menu.class); + if (CollUtil.isEmpty(menus)){ + List roles = roleService.findByUsersId(currentUserId); + Set roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet()); + LinkedHashSet data = menuRepository.findByRoleIdsAndTypeNot(roleIds, 2); + menus = new ArrayList<>(data); + redisUtils.set(key, menus, 1, TimeUnit.DAYS); } return menus.stream().map(menuMapper::toDto).collect(Collectors.toList()); } @Override - public MenuDTO create(Menu resources) { - if(menuRepository.findByName(resources.getName()) != null){ - throw new EntityExistException(Menu.class,"name",resources.getName()); + @Transactional(rollbackFor = Exception.class) + public void create(Menu resources) { + if(menuRepository.findByTitle(resources.getTitle()) != null){ + throw new EntityExistException(Menu.class,"title",resources.getTitle()); + } + if(StringUtils.isNotBlank(resources.getComponentName())){ + if(menuRepository.findByComponentName(resources.getComponentName()) != null){ + throw new EntityExistException(Menu.class,"componentName",resources.getComponentName()); + } + } + if (Long.valueOf(0L).equals(resources.getPid())) { + resources.setPid(null); } if(resources.getIFrame()){ - if (!(resources.getPath().toLowerCase().startsWith("http://")||resources.getPath().toLowerCase().startsWith("https://"))) { - throw new BadRequestException("外链必须以http://或者https://开头"); + if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) { + throw new BadRequestException(BAD_REQUEST); } } - return menuMapper.toDto(menuRepository.save(resources)); + menuRepository.save(resources); + // 计算子节点数目 + resources.setSubCount(0); + // 更新父节点菜单数目 + updateSubCnt(resources.getPid()); } @Override + @Transactional(rollbackFor = Exception.class) public void update(Menu resources) { if(resources.getId().equals(resources.getPid())) { throw new BadRequestException("上级不能为自己"); } - Optional optionalPermission = menuRepository.findById(resources.getId()); - ValidationUtil.isNull(optionalPermission,"Permission","id",resources.getId()); + Menu menu = menuRepository.findById(resources.getId()).orElseGet(Menu::new); + ValidationUtil.isNull(menu.getId(),"Permission","id",resources.getId()); if(resources.getIFrame()){ - if (!(resources.getPath().toLowerCase().startsWith("http://")||resources.getPath().toLowerCase().startsWith("https://"))) { - throw new BadRequestException("外链必须以http://或者https://开头"); + if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) { + throw new BadRequestException(BAD_REQUEST); } } - Menu menu = optionalPermission.get(); - Menu menu1 = menuRepository.findByName(resources.getName()); + Menu menu1 = menuRepository.findByTitle(resources.getTitle()); if(menu1 != null && !menu1.getId().equals(menu.getId())){ - throw new EntityExistException(Menu.class,"name",resources.getName()); + throw new EntityExistException(Menu.class,"title",resources.getTitle()); + } + + if(resources.getPid().equals(0L)){ + resources.setPid(null); + } + + // 记录的父节点ID + Long oldPid = menu.getPid(); + Long newPid = resources.getPid(); + + if(StringUtils.isNotBlank(resources.getComponentName())){ + menu1 = menuRepository.findByComponentName(resources.getComponentName()); + if(menu1 != null && !menu1.getId().equals(menu.getId())){ + throw new EntityExistException(Menu.class,"componentName",resources.getComponentName()); + } } - menu.setName(resources.getName()); + menu.setTitle(resources.getTitle()); menu.setComponent(resources.getComponent()); menu.setPath(resources.getPath()); menu.setIcon(resources.getIcon()); menu.setIFrame(resources.getIFrame()); menu.setPid(resources.getPid()); - menu.setSort(resources.getSort()); + menu.setMenuSort(resources.getMenuSort()); + menu.setCache(resources.getCache()); + menu.setHidden(resources.getHidden()); + menu.setComponentName(resources.getComponentName()); + menu.setPermission(resources.getPermission()); + menu.setType(resources.getType()); menuRepository.save(menu); + // 计算父级菜单节点数目 + updateSubCnt(oldPid); + updateSubCnt(newPid); + // 清理缓存 + delCaches(resources.getId()); } @Override - public void delete(Long id) { - menuRepository.deleteById(id); + public Set getChildMenus(List menuList, Set menuSet) { + for (Menu menu : menuList) { + menuSet.add(menu); + List menus = menuRepository.findByPidOrderByMenuSort(menu.getId()); + if(CollUtil.isNotEmpty(menus)){ + getChildMenus(menus, menuSet); + } + } + return menuSet; } @Override - public Object getMenuTree(List menus) { - List> list = new LinkedList<>(); - menus.forEach(menu -> { - if (menu!=null){ - List menuList = menuRepository.findByPid(menu.getId()); - Map map = new HashMap<>(); - map.put("id",menu.getId()); - map.put("label",menu.getName()); - if(menuList!=null && menuList.size()!=0){ - map.put("children",getMenuTree(menuList)); - } - list.add(map); - } - } - ); - return list; + @Transactional(rollbackFor = Exception.class) + public void delete(Set menuSet) { + for (Menu menu : menuSet) { + // 清理缓存 + delCaches(menu.getId()); + roleService.untiedMenu(menu.getId()); + menuRepository.deleteById(menu.getId()); + updateSubCnt(menu.getPid()); + } } @Override - public List findByPid(long pid) { - return menuRepository.findByPid(pid); + public List getMenus(Long pid) { + List menus; + if(pid != null && !pid.equals(0L)){ + menus = menuRepository.findByPidOrderByMenuSort(pid); + } else { + menus = menuRepository.findByPidIsNullOrderByMenuSort(); + } + return menuMapper.toDto(menus); } @Override - public Map buildTree(List menuDTOS) { - List trees = new ArrayList(); - - for (MenuDTO menuDTO : menuDTOS) { + public List getSuperior(MenuDto menuDto, List menus) { + if(menuDto.getPid() == null){ + menus.addAll(menuRepository.findByPidIsNullOrderByMenuSort()); + return menuMapper.toDto(menus); + } + menus.addAll(menuRepository.findByPidOrderByMenuSort(menuDto.getPid())); + return getSuperior(findById(menuDto.getPid()), menus); + } - if ("0".equals(menuDTO.getPid().toString())) { + @Override + public List buildTree(List menuDtos) { + List trees = new ArrayList<>(); + Set ids = new HashSet<>(); + for (MenuDto menuDTO : menuDtos) { + if (menuDTO.getPid() == null) { trees.add(menuDTO); } - - for (MenuDTO it : menuDTOS) { - if (it.getPid().equals(menuDTO.getId())) { + for (MenuDto it : menuDtos) { + if (menuDTO.getId().equals(it.getPid())) { if (menuDTO.getChildren() == null) { - menuDTO.setChildren(new ArrayList()); + menuDTO.setChildren(new ArrayList<>()); } menuDTO.getChildren().add(it); + ids.add(it.getId()); } } } - Map map = new HashMap(); - map.put("content",trees.size() == 0?menuDTOS:trees); - map.put("totalElements",menuDTOS!=null?menuDTOS.size():0); - return map; + if(trees.isEmpty()){ + trees = menuDtos.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList()); + } + return trees; } @Override - public List buildMenus(List menuDTOS) { + public List buildMenus(List menuDtos) { List list = new LinkedList<>(); - menuDTOS.forEach(menuDTO -> { - if (menuDTO!=null){ - List menuDTOList = menuDTO.getChildren(); - MenuVo menuVo = new MenuVo(); - menuVo.setName(menuDTO.getName()); - menuVo.setPath(menuDTO.getPath()); - - // 如果不是外链 - if(!menuDTO.getIFrame()){ - if(menuDTO.getPid().equals(0L)){ - //一级目录需要加斜杠,不然访问 会跳转404页面 - menuVo.setPath("/" + menuDTO.getPath()); - menuVo.setComponent(StrUtil.isEmpty(menuDTO.getComponent())?"Layout":menuDTO.getComponent()); - }else if(!StrUtil.isEmpty(menuDTO.getComponent())){ - menuVo.setComponent(menuDTO.getComponent()); - } - } - menuVo.setMeta(new MenuMetaVo(menuDTO.getName(),menuDTO.getIcon())); - if(menuDTOList!=null && menuDTOList.size()!=0){ - menuVo.setAlwaysShow(true); - menuVo.setRedirect("noredirect"); - menuVo.setChildren(buildMenus(menuDTOList)); - // 处理是一级菜单并且没有子菜单的情况 - } else if(menuDTO.getPid().equals(0L)){ - MenuVo menuVo1 = new MenuVo(); - menuVo1.setMeta(menuVo.getMeta()); - // 非外链 - if(!menuDTO.getIFrame()){ - menuVo1.setPath("index"); - menuVo1.setName(menuVo.getName()); - menuVo1.setComponent(menuVo.getComponent()); - } else { - menuVo1.setPath(menuDTO.getPath()); + menuDtos.forEach(menuDTO -> { + if (menuDTO!=null){ + List menuDtoList = menuDTO.getChildren(); + MenuVo menuVo = new MenuVo(); + menuVo.setName(ObjectUtil.isNotEmpty(menuDTO.getComponentName()) ? menuDTO.getComponentName() : menuDTO.getTitle()); + // 一级目录需要加斜杠,不然会报警告 + menuVo.setPath(menuDTO.getPid() == null ? "/" + menuDTO.getPath() :menuDTO.getPath()); + menuVo.setHidden(menuDTO.getHidden()); + // 如果不是外链 + if(!menuDTO.getIFrame()){ + if(menuDTO.getPid() == null){ + menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"Layout":menuDTO.getComponent()); + // 如果不是一级菜单,并且菜单类型为目录,则代表是多级菜单 + }else if(menuDTO.getType() == 0){ + menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"ParentView":menuDTO.getComponent()); + }else if(StringUtils.isNoneBlank(menuDTO.getComponent())){ + menuVo.setComponent(menuDTO.getComponent()); + } + } + menuVo.setMeta(new MenuMetaVo(menuDTO.getTitle(),menuDTO.getIcon(),!menuDTO.getCache())); + if(CollectionUtil.isNotEmpty(menuDtoList)){ + menuVo.setAlwaysShow(true); + menuVo.setRedirect("noredirect"); + menuVo.setChildren(buildMenus(menuDtoList)); + // 处理是一级菜单并且没有子菜单的情况 + } else if(menuDTO.getPid() == null){ + MenuVo menuVo1 = getMenuVo(menuDTO, menuVo); + menuVo.setName(null); + menuVo.setMeta(null); + menuVo.setComponent("Layout"); + List list1 = new ArrayList<>(); + list1.add(menuVo1); + menuVo.setChildren(list1); + } + list.add(menuVo); } - menuVo.setName(null); - menuVo.setMeta(null); - menuVo.setComponent("Layout"); - List list1 = new ArrayList(); - list1.add(menuVo1); - menuVo.setChildren(list1); } - list.add(menuVo); - } - } ); return list; } @Override public Menu findOne(Long id) { - Optional menu = menuRepository.findById(id); - ValidationUtil.isNull(menu,"Menu","id",id); - return menu.get(); + Menu menu = menuRepository.findById(id).orElseGet(Menu::new); + ValidationUtil.isNull(menu.getId(),"Menu","id",id); + return menu; + } + + @Override + public void download(List menuDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (MenuDto menuDTO : menuDtos) { + Map map = new LinkedHashMap<>(); + map.put("菜单标题", menuDTO.getTitle()); + map.put("菜单类型", menuDTO.getType() == null ? "目录" : menuDTO.getType() == 1 ? "菜单" : "按钮"); + map.put("权限标识", menuDTO.getPermission()); + map.put("外链菜单", menuDTO.getIFrame() ? YES_STR : NO_STR); + map.put("菜单可见", menuDTO.getHidden() ? NO_STR : YES_STR); + map.put("是否缓存", menuDTO.getCache() ? YES_STR : NO_STR); + map.put("创建日期", menuDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + private void updateSubCnt(Long menuId){ + if(menuId != null){ + int count = menuRepository.countByPid(menuId); + menuRepository.updateSubCntById(count, menuId); + } + } + + /** + * 清理缓存 + * @param id 菜单ID + */ + public void delCaches(Long id){ + List users = userRepository.findByMenuId(id); + redisUtils.del(CacheKey.MENU_ID + id); + redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(User::getId).collect(Collectors.toSet())); + // 清除 Role 缓存 + List roles = roleService.findInMenuId(new ArrayList(){{ + add(id); + }}); + redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(Role::getId).collect(Collectors.toSet())); + } + + /** + * 构建前端路由 + * @param menuDTO / + * @param menuVo / + * @return / + */ + private static MenuVo getMenuVo(MenuDto menuDTO, MenuVo menuVo) { + MenuVo menuVo1 = new MenuVo(); + menuVo1.setMeta(menuVo.getMeta()); + // 非外链 + if(!menuDTO.getIFrame()){ + menuVo1.setPath("index"); + menuVo1.setName(menuVo.getName()); + menuVo1.setComponent(menuVo.getComponent()); + } else { + menuVo1.setPath(menuDTO.getPath()); + } + return menuVo1; } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MonitorServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MonitorServiceImpl.java new file mode 100644 index 000000000..99ee9694c --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MonitorServiceImpl.java @@ -0,0 +1,194 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.impl; + +import cn.hutool.core.date.BetweenFormatter.Level; +import cn.hutool.core.date.DateUtil; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.modules.system.service.MonitorService; +import me.zhengjie.utils.ElConstant; +import me.zhengjie.utils.FileUtil; +import me.zhengjie.utils.StringUtils; +import org.springframework.stereotype.Service; +import oshi.SystemInfo; +import oshi.hardware.*; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.FormatUtil; +import oshi.util.Util; +import java.lang.management.ManagementFactory; +import java.text.DecimalFormat; +import java.util.*; + +/** +* @author Zheng Jie +* @date 2020-05-02 +*/ +@Slf4j +@Service +public class MonitorServiceImpl implements MonitorService { + + private final DecimalFormat df = new DecimalFormat("0.00"); + + @Override + public Map getServers(){ + Map resultMap = new LinkedHashMap<>(8); + try { + SystemInfo si = new SystemInfo(); + OperatingSystem os = si.getOperatingSystem(); + HardwareAbstractionLayer hal = si.getHardware(); + // 系统信息 + resultMap.put("sys", getSystemInfo(os)); + // cpu 信息 + resultMap.put("cpu", getCpuInfo(hal.getProcessor())); + // 内存信息 + resultMap.put("memory", getMemoryInfo(hal.getMemory())); + // 交换区信息 + resultMap.put("swap", getSwapInfo(hal.getMemory())); + // 磁盘 + resultMap.put("disk", getDiskInfo(os)); + resultMap.put("time", DateUtil.format(new Date(), "HH:mm:ss")); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return resultMap; + } + + /** + * 获取磁盘信息 + * @return / + */ + private Map getDiskInfo(OperatingSystem os) { + Map diskInfo = new LinkedHashMap<>(); + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + String osName = System.getProperty("os.name"); + long available = 0, total = 0; + for (OSFileStore fs : fsArray){ + // windows 需要将所有磁盘分区累加,linux 和 mac 直接累加会出现磁盘重复的问题,待修复 + if(osName.toLowerCase().startsWith(ElConstant.WIN)) { + available += fs.getUsableSpace(); + total += fs.getTotalSpace(); + } else { + available = fs.getUsableSpace(); + total = fs.getTotalSpace(); + break; + } + } + long used = total - available; + diskInfo.put("total", total > 0 ? FileUtil.getSize(total) : "?"); + diskInfo.put("available", FileUtil.getSize(available)); + diskInfo.put("used", FileUtil.getSize(used)); + if(total != 0){ + diskInfo.put("usageRate", df.format(used/(double)total * 100)); + } else { + diskInfo.put("usageRate", 0); + } + return diskInfo; + } + + /** + * 获取交换区信息 + * @param memory / + * @return / + */ + private Map getSwapInfo(GlobalMemory memory) { + Map swapInfo = new LinkedHashMap<>(); + VirtualMemory virtualMemory = memory.getVirtualMemory(); + long total = virtualMemory.getSwapTotal(); + long used = virtualMemory.getSwapUsed(); + swapInfo.put("total", FormatUtil.formatBytes(total)); + swapInfo.put("used", FormatUtil.formatBytes(used)); + swapInfo.put("available", FormatUtil.formatBytes(total - used)); + if(used == 0){ + swapInfo.put("usageRate", 0); + } else { + swapInfo.put("usageRate", df.format(used/(double)total * 100)); + } + return swapInfo; + } + + /** + * 获取内存信息 + * @param memory / + * @return / + */ + private Map getMemoryInfo(GlobalMemory memory) { + Map memoryInfo = new LinkedHashMap<>(); + memoryInfo.put("total", FormatUtil.formatBytes(memory.getTotal())); + memoryInfo.put("available", FormatUtil.formatBytes(memory.getAvailable())); + memoryInfo.put("used", FormatUtil.formatBytes(memory.getTotal() - memory.getAvailable())); + memoryInfo.put("usageRate", df.format((memory.getTotal() - memory.getAvailable())/(double)memory.getTotal() * 100)); + return memoryInfo; + } + + /** + * 获取Cpu相关信息 + * @param processor / + * @return / + */ + private Map getCpuInfo(CentralProcessor processor) { + Map cpuInfo = new LinkedHashMap<>(); + cpuInfo.put("name", processor.getProcessorIdentifier().getName()); + cpuInfo.put("package", processor.getPhysicalPackageCount() + "个物理CPU"); + cpuInfo.put("core", processor.getPhysicalProcessorCount() + "个物理核心"); + cpuInfo.put("coreNumber", processor.getPhysicalProcessorCount()); + cpuInfo.put("logic", processor.getLogicalProcessorCount() + "个逻辑CPU"); + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + // 默认等待300毫秒... + long time = 300; + Util.sleep(time); + long[] ticks = processor.getSystemCpuLoadTicks(); + while (Arrays.toString(prevTicks).equals(Arrays.toString(ticks)) && time < 1000){ + time += 25; + Util.sleep(25); + ticks = processor.getSystemCpuLoadTicks(); + } + long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal; + cpuInfo.put("used", df.format(100d * user / totalCpu + 100d * sys / totalCpu)); + cpuInfo.put("idle", df.format(100d * idle / totalCpu)); + return cpuInfo; + } + + /** + * 获取系统相关信息,系统、运行天数、系统IP + * @param os / + * @return / + */ + private Map getSystemInfo(OperatingSystem os){ + Map systemInfo = new LinkedHashMap<>(); + // jvm 运行时间 + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + Date date = new Date(time); + // 计算项目运行时间 + String formatBetween = DateUtil.formatBetween(date, new Date(), Level.HOUR); + // 系统信息 + systemInfo.put("os", os.toString()); + systemInfo.put("day", formatBetween); + systemInfo.put("ip", StringUtils.getLocalIp()); + return systemInfo; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/PermissionServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/PermissionServiceImpl.java deleted file mode 100644 index ea521a79d..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/PermissionServiceImpl.java +++ /dev/null @@ -1,139 +0,0 @@ -package me.zhengjie.modules.system.service.impl; - -import me.zhengjie.modules.system.domain.Permission; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.exception.EntityExistException; -import me.zhengjie.modules.system.repository.PermissionRepository; -import me.zhengjie.modules.system.service.PermissionService; -import me.zhengjie.modules.system.service.dto.PermissionDTO; -import me.zhengjie.modules.system.service.dto.PermissionQueryCriteria; -import me.zhengjie.modules.system.service.mapper.PermissionMapper; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import java.util.*; - -/** - * @author Zheng Jie - * @date 2018-12-03 - */ -@Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class PermissionServiceImpl implements PermissionService { - - @Autowired - private PermissionRepository permissionRepository; - - @Autowired - private PermissionMapper permissionMapper; - - @Override - public List queryAll(PermissionQueryCriteria criteria) { - return permissionMapper.toDto(permissionRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); - } - - @Override - public PermissionDTO findById(long id) { - Optional permission = permissionRepository.findById(id); - ValidationUtil.isNull(permission,"Permission","id",id); - return permissionMapper.toDto(permission.get()); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public PermissionDTO create(Permission resources) { - if(permissionRepository.findByName(resources.getName()) != null){ - throw new EntityExistException(Permission.class,"name",resources.getName()); - } - return permissionMapper.toDto(permissionRepository.save(resources)); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void update(Permission resources) { - Optional optionalPermission = permissionRepository.findById(resources.getId()); - if(resources.getId().equals(resources.getPid())) { - throw new BadRequestException("上级不能为自己"); - } - ValidationUtil.isNull(optionalPermission,"Permission","id",resources.getId()); - - Permission permission = optionalPermission.get(); - - Permission permission1 = permissionRepository.findByName(resources.getName()); - - if(permission1 != null && !permission1.getId().equals(permission.getId())){ - throw new EntityExistException(Permission.class,"name",resources.getName()); - } - - permission.setName(resources.getName()); - permission.setAlias(resources.getAlias()); - permission.setPid(resources.getPid()); - permissionRepository.save(permission); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void delete(Long id) { - List permissionList = permissionRepository.findByPid(id); - for (Permission permission : permissionList) { - permissionRepository.delete(permission); - } - permissionRepository.deleteById(id); - } - - @Override - public Object getPermissionTree(List permissions) { - List> list = new LinkedList<>(); - permissions.forEach(permission -> { - if (permission!=null){ - List permissionList = permissionRepository.findByPid(permission.getId()); - Map map = new HashMap<>(); - map.put("id",permission.getId()); - map.put("label",permission.getAlias()); - if(permissionList!=null && permissionList.size()!=0){ - map.put("children",getPermissionTree(permissionList)); - } - list.add(map); - } - } - ); - return list; - } - - @Override - public List findByPid(long pid) { - return permissionRepository.findByPid(pid); - } - - @Override - public Object buildTree(List permissionDTOS) { - - List trees = new ArrayList(); - - for (PermissionDTO permissionDTO : permissionDTOS) { - - if ("0".equals(permissionDTO.getPid().toString())) { - trees.add(permissionDTO); - } - - for (PermissionDTO it : permissionDTOS) { - if (it.getPid().equals(permissionDTO.getId())) { - if (permissionDTO.getChildren() == null) { - permissionDTO.setChildren(new ArrayList()); - } - permissionDTO.getChildren().add(it); - } - } - } - - Integer totalElements = permissionDTOS!=null?permissionDTOS.size():0; - - Map map = new HashMap(); - map.put("content",trees.size() == 0?permissionDTOS:trees); - map.put("totalElements",totalElements); - return map; - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java index cc68febba..7fba169ba 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java @@ -1,25 +1,49 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.security.service.UserCacheManager; +import me.zhengjie.modules.security.service.dto.AuthorityDto; import me.zhengjie.modules.system.domain.Menu; import me.zhengjie.modules.system.domain.Role; import me.zhengjie.exception.EntityExistException; +import me.zhengjie.modules.system.domain.User; import me.zhengjie.modules.system.repository.RoleRepository; +import me.zhengjie.modules.system.repository.UserRepository; import me.zhengjie.modules.system.service.RoleService; -import me.zhengjie.modules.system.service.dto.RoleDTO; +import me.zhengjie.modules.system.service.dto.RoleDto; import me.zhengjie.modules.system.service.dto.RoleQueryCriteria; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; -import me.zhengjie.modules.system.service.mapper.RoleMapper; -import me.zhengjie.modules.system.service.mapper.RoleSmallMapper; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; +import me.zhengjie.modules.system.service.mapstruct.RoleMapper; +import me.zhengjie.modules.system.service.mapstruct.RoleSmallMapper; +import me.zhengjie.utils.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -27,109 +51,188 @@ * @date 2018-12-03 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class RoleServiceImpl implements RoleService { - @Autowired - private RoleRepository roleRepository; - - @Autowired - private RoleMapper roleMapper; + private final RoleRepository roleRepository; + private final RoleMapper roleMapper; + private final RoleSmallMapper roleSmallMapper; + private final RedisUtils redisUtils; + private final UserRepository userRepository; + private final UserCacheManager userCacheManager; - @Autowired - private RoleSmallMapper roleSmallMapper; + @Override + public List queryAll() { + Sort sort = Sort.by(Sort.Direction.ASC, "level"); + return roleMapper.toDto(roleRepository.findAll(sort)); + } @Override - public Object queryAll(Pageable pageable) { - return roleMapper.toDto(roleRepository.findAll(pageable).getContent()); + public List queryAll(RoleQueryCriteria criteria) { + return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder))); } @Override - public Object queryAll(RoleQueryCriteria criteria, Pageable pageable) { - Page page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + public PageResult queryAll(RoleQueryCriteria criteria, Pageable pageable) { + Page page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable); return PageUtil.toPage(page.map(roleMapper::toDto)); } @Override - public RoleDTO findById(long id) { - Optional role = roleRepository.findById(id); - ValidationUtil.isNull(role,"Role","id",id); - return roleMapper.toDto(role.get()); + public RoleDto findById(long id) { + String key = CacheKey.ROLE_ID + id; + Role role = redisUtils.get(key, Role.class); + if (role == null) { + role = roleRepository.findById(id).orElseGet(Role::new); + ValidationUtil.isNull(role.getId(), "Role", "id", id); + redisUtils.set(key, role, 1, TimeUnit.DAYS); + } + return roleMapper.toDto(role); } @Override @Transactional(rollbackFor = Exception.class) - public RoleDTO create(Role resources) { - if(roleRepository.findByName(resources.getName()) != null){ - throw new EntityExistException(Role.class,"username",resources.getName()); + public void create(Role resources) { + if (roleRepository.findByName(resources.getName()) != null) { + throw new EntityExistException(Role.class, "username", resources.getName()); } - return roleMapper.toDto(roleRepository.save(resources)); + roleRepository.save(resources); } @Override @Transactional(rollbackFor = Exception.class) public void update(Role resources) { - - Optional optionalRole = roleRepository.findById(resources.getId()); - ValidationUtil.isNull(optionalRole,"Role","id",resources.getId()); - - Role role = optionalRole.get(); + Role role = roleRepository.findById(resources.getId()).orElseGet(Role::new); + ValidationUtil.isNull(role.getId(), "Role", "id", resources.getId()); Role role1 = roleRepository.findByName(resources.getName()); - if(role1 != null && !role1.getId().equals(role.getId())){ - throw new EntityExistException(Role.class,"username",resources.getName()); + if (role1 != null && !role1.getId().equals(role.getId())) { + throw new EntityExistException(Role.class, "username", resources.getName()); } - role.setName(resources.getName()); - role.setRemark(resources.getRemark()); + role.setDescription(resources.getDescription()); role.setDataScope(resources.getDataScope()); role.setDepts(resources.getDepts()); role.setLevel(resources.getLevel()); roleRepository.save(role); + // 更新相关缓存 + delCaches(role.getId(), null); } @Override - public void updatePermission(Role resources, RoleDTO roleDTO) { + public void updateMenu(Role resources, RoleDto roleDTO) { Role role = roleMapper.toEntity(roleDTO); - role.setPermissions(resources.getPermissions()); + List users = userRepository.findByRoleId(role.getId()); + // 更新菜单 + role.setMenus(resources.getMenus()); + delCaches(resources.getId(), users); roleRepository.save(role); } @Override - public void updateMenu(Role resources, RoleDTO roleDTO) { - Role role = roleMapper.toEntity(roleDTO); - role.setMenus(resources.getMenus()); - roleRepository.save(role); + @Transactional(rollbackFor = Exception.class) + public void untiedMenu(Long menuId) { + // 更新菜单 + roleRepository.untiedMenu(menuId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (Long id : ids) { + // 更新相关缓存 + delCaches(id, null); + } + roleRepository.deleteAllByIdIn(ids); } @Override - public void untiedMenu(Menu menu) { - Set roles = roleRepository.findByMenus_Id(menu.getId()); + public List findByUsersId(Long userId) { + String key = CacheKey.ROLE_USER + userId; + List roles = redisUtils.getList(key, RoleSmallDto.class); + if (CollUtil.isEmpty(roles)) { + roles = roleSmallMapper.toDto(new ArrayList<>(roleRepository.findByUserId(userId))); + redisUtils.set(key, roles, 1, TimeUnit.DAYS); + } + return roles; + } + + @Override + public Integer findByRoles(Set roles) { + if (roles.isEmpty()) { + return Integer.MAX_VALUE; + } + Set roleDtos = new HashSet<>(); for (Role role : roles) { - menu.getRoles().remove(role); - role.getMenus().remove(menu); - roleRepository.save(role); + roleDtos.add(findById(role.getId())); } + return Collections.min(roleDtos.stream().map(RoleDto::getLevel).collect(Collectors.toList())); } @Override - @Transactional(rollbackFor = Exception.class) - public void delete(Long id) { - roleRepository.deleteById(id); + public List buildPermissions(UserDto user) { + String key = CacheKey.ROLE_AUTH + user.getId(); + List authorityDtos = redisUtils.getList(key, AuthorityDto.class); + if (CollUtil.isEmpty(authorityDtos)) { + Set permissions = new HashSet<>(); + // 如果是管理员直接返回 + if (user.getIsAdmin()) { + permissions.add("admin"); + return permissions.stream().map(AuthorityDto::new) + .collect(Collectors.toList()); + } + Set roles = roleRepository.findByUserId(user.getId()); + permissions = roles.stream().flatMap(role -> role.getMenus().stream()) + .map(Menu::getPermission) + .filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + authorityDtos = permissions.stream().map(AuthorityDto::new) + .collect(Collectors.toList()); + redisUtils.set(key, authorityDtos, 1, TimeUnit.HOURS); + } + return authorityDtos; } @Override - public List findByUsers_Id(Long id) { - return roleSmallMapper.toDto(roleRepository.findByUsers_Id(id).stream().collect(Collectors.toList())); + public void download(List roles, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (RoleDto role : roles) { + Map map = new LinkedHashMap<>(); + map.put("角色名称", role.getName()); + map.put("角色级别", role.getLevel()); + map.put("描述", role.getDescription()); + map.put("创建日期", role.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); } @Override - public Integer findByRoles(Set roles) { - Set roleDTOS = new HashSet<>(); - for (Role role : roles) { - roleDTOS.add(findById(role.getId())); + public void verification(Set ids) { + if (userRepository.countByRoles(ids) > 0) { + throw new BadRequestException("所选角色存在用户关联,请解除关联再试!"); + } + } + + @Override + public List findInMenuId(List menuIds) { + return roleRepository.findInMenuId(menuIds); + } + + /** + * 清理缓存 + * @param id / + */ + public void delCaches(Long id, List users) { + users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users; + if (CollectionUtil.isNotEmpty(users)) { + users.forEach(item -> userCacheManager.cleanUserCache(item.getUsername())); + Set userIds = users.stream().map(User::getId).collect(Collectors.toSet()); + redisUtils.delByKeys(CacheKey.DATA_USER, userIds); + redisUtils.delByKeys(CacheKey.MENU_USER, userIds); + redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds); + redisUtils.delByKeys(CacheKey.ROLE_USER, userIds); } - return Collections.min(roleDTOS.stream().map(RoleDTO::getLevel).collect(Collectors.toList())); + redisUtils.del(CacheKey.ROLE_ID + id); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java index 8a951b386..706938854 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java @@ -1,125 +1,178 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.modules.system.service.impl; -import me.zhengjie.modules.monitor.service.RedisService; +import lombok.RequiredArgsConstructor; +import me.zhengjie.utils.PageResult; +import me.zhengjie.config.properties.FileProperties; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.modules.security.service.UserCacheManager; import me.zhengjie.modules.system.domain.User; import me.zhengjie.exception.EntityExistException; import me.zhengjie.exception.EntityNotFoundException; import me.zhengjie.modules.system.repository.UserRepository; import me.zhengjie.modules.system.service.UserService; -import me.zhengjie.modules.system.service.dto.UserDTO; -import me.zhengjie.modules.system.service.dto.UserQueryCriteria; -import me.zhengjie.modules.system.service.mapper.UserMapper; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.modules.system.service.dto.*; +import me.zhengjie.modules.system.service.mapstruct.UserMapper; +import me.zhengjie.utils.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.Date; -import java.util.Optional; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotBlank; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * @author Zheng Jie * @date 2018-11-23 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor public class UserServiceImpl implements UserService { - @Autowired - private UserRepository userRepository; - - @Autowired - private UserMapper userMapper; - - @Autowired - private RedisService redisService; + private final UserRepository userRepository; + private final UserMapper userMapper; + private final FileProperties properties; + private final RedisUtils redisUtils; + private final UserCacheManager userCacheManager; + private final OnlineUserService onlineUserService; @Override - public Object queryAll(UserQueryCriteria criteria, Pageable pageable) { - Page page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + public PageResult queryAll(UserQueryCriteria criteria, Pageable pageable) { + Page page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable); return PageUtil.toPage(page.map(userMapper::toDto)); } @Override - public UserDTO findById(long id) { - Optional user = userRepository.findById(id); - ValidationUtil.isNull(user,"User","id",id); - return userMapper.toDto(user.get()); + public List queryAll(UserQueryCriteria criteria) { + List users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)); + return userMapper.toDto(users); } @Override @Transactional(rollbackFor = Exception.class) - public UserDTO create(User resources) { - - if(userRepository.findByUsername(resources.getUsername())!=null){ - throw new EntityExistException(User.class,"username",resources.getUsername()); + public UserDto findById(long id) { + String key = CacheKey.USER_ID + id; + User user = redisUtils.get(key, User.class); + if (user == null) { + user = userRepository.findById(id).orElseGet(User::new); + ValidationUtil.isNull(user.getId(), "User", "id", id); + redisUtils.set(key, user, 1, TimeUnit.DAYS); } + return userMapper.toDto(user); + } - if(userRepository.findByEmail(resources.getEmail())!=null){ - throw new EntityExistException(User.class,"email",resources.getEmail()); + @Override + @Transactional(rollbackFor = Exception.class) + public void create(User resources) { + if (userRepository.findByUsername(resources.getUsername()) != null) { + throw new EntityExistException(User.class, "username", resources.getUsername()); } - - // 默认密码 123456,此密码是加密后的字符 - resources.setPassword("e10adc3949ba59abbe56e057f20f883e"); - resources.setAvatar("https://i.loli.net/2019/04/04/5ca5b971e1548.jpeg"); - return userMapper.toDto(userRepository.save(resources)); + if (userRepository.findByEmail(resources.getEmail()) != null) { + throw new EntityExistException(User.class, "email", resources.getEmail()); + } + if (userRepository.findByPhone(resources.getPhone()) != null) { + throw new EntityExistException(User.class, "phone", resources.getPhone()); + } + userRepository.save(resources); } @Override @Transactional(rollbackFor = Exception.class) - public void update(User resources) { - Optional userOptional = userRepository.findById(resources.getId()); - ValidationUtil.isNull(userOptional,"User","id",resources.getId()); - - User user = userOptional.get(); - - User user1 = userRepository.findByUsername(user.getUsername()); - User user2 = userRepository.findByEmail(user.getEmail()); - - if(user1 !=null&&!user.getId().equals(user1.getId())){ - throw new EntityExistException(User.class,"username",resources.getUsername()); + public void update(User resources) throws Exception { + User user = userRepository.findById(resources.getId()).orElseGet(User::new); + ValidationUtil.isNull(user.getId(), "User", "id", resources.getId()); + User user1 = userRepository.findByUsername(resources.getUsername()); + User user2 = userRepository.findByEmail(resources.getEmail()); + User user3 = userRepository.findByPhone(resources.getPhone()); + if (user1 != null && !user.getId().equals(user1.getId())) { + throw new EntityExistException(User.class, "username", resources.getUsername()); } - - if(user2!=null&&!user.getId().equals(user2.getId())){ - throw new EntityExistException(User.class,"email",resources.getEmail()); + if (user2 != null && !user.getId().equals(user2.getId())) { + throw new EntityExistException(User.class, "email", resources.getEmail()); } - - // 如果用户的角色改变了,需要手动清理下缓存 + if (user3 != null && !user.getId().equals(user3.getId())) { + throw new EntityExistException(User.class, "phone", resources.getPhone()); + } + // 如果用户的角色改变 if (!resources.getRoles().equals(user.getRoles())) { - String key = "role::loadPermissionByUser:" + user.getUsername(); - redisService.delete(key); - key = "role::findByUsers_Id:" + user.getId(); - redisService.delete(key); + redisUtils.del(CacheKey.DATA_USER + resources.getId()); + redisUtils.del(CacheKey.MENU_USER + resources.getId()); + redisUtils.del(CacheKey.ROLE_AUTH + resources.getId()); + redisUtils.del(CacheKey.ROLE_USER + resources.getId()); + } + // 修改部门会影响 数据权限 + if (!Objects.equals(resources.getDept(),user.getDept())) { + redisUtils.del(CacheKey.DATA_USER + resources.getId()); + } + // 如果用户被禁用,则清除用户登录信息 + if(!resources.getEnabled()){ + onlineUserService.kickOutForUsername(resources.getUsername()); } - user.setUsername(resources.getUsername()); user.setEmail(resources.getEmail()); user.setEnabled(resources.getEnabled()); user.setRoles(resources.getRoles()); user.setDept(resources.getDept()); - user.setJob(resources.getJob()); + user.setJobs(resources.getJobs()); user.setPhone(resources.getPhone()); + user.setNickName(resources.getNickName()); + user.setGender(resources.getGender()); userRepository.save(user); + // 清除缓存 + delCaches(user.getId(), user.getUsername()); } @Override @Transactional(rollbackFor = Exception.class) - public void delete(Long id) { - userRepository.deleteById(id); + public void updateCenter(User resources) { + User user = userRepository.findById(resources.getId()).orElseGet(User::new); + User user1 = userRepository.findByPhone(resources.getPhone()); + if (user1 != null && !user.getId().equals(user1.getId())) { + throw new EntityExistException(User.class, "phone", resources.getPhone()); + } + user.setNickName(resources.getNickName()); + user.setPhone(resources.getPhone()); + user.setGender(resources.getGender()); + userRepository.save(user); + // 清理缓存 + delCaches(user.getId(), user.getUsername()); } @Override - public UserDTO findByName(String userName) { - User user = null; - if(ValidationUtil.isEmail(userName)){ - user = userRepository.findByEmail(userName); - } else { - user = userRepository.findByUsername(userName); + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + for (Long id : ids) { + // 清理缓存 + UserDto user = findById(id); + delCaches(user.getId(), user.getUsername()); } + userRepository.deleteAllByIdIn(ids); + } + + @Override + public UserDto findByName(String userName) { + User user = userRepository.findByUsername(userName); if (user == null) { throw new EntityNotFoundException(User.class, "name", userName); } else { @@ -127,21 +180,108 @@ public UserDTO findByName(String userName) { } } + @Override + public UserDto getLoginData(String userName) { + User user = userRepository.findByUsername(userName); + if (user == null) { + return null; + } else { + return userMapper.toDto(user); + } + } + @Override @Transactional(rollbackFor = Exception.class) public void updatePass(String username, String pass) { - userRepository.updatePass(username,pass,new Date()); + userRepository.updatePass(username, pass, new Date()); + flushCache(username); } @Override @Transactional(rollbackFor = Exception.class) - public void updateAvatar(String username, String url) { - userRepository.updateAvatar(username,url); + public void resetPwd(Set ids, String pwd) { + List users = userRepository.findAllById(ids); + // 清除缓存 + users.forEach(user -> { + // 清除缓存 + flushCache(user.getUsername()); + // 强制退出 + onlineUserService.kickOutForUsername(user.getUsername()); + }); + // 重置密码 + userRepository.resetPwd(ids, pwd); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Map updateAvatar(MultipartFile multipartFile) { + // 文件大小验证 + FileUtil.checkSize(properties.getAvatarMaxSize(), multipartFile.getSize()); + // 验证文件上传的格式 + String image = "gif jpg png jpeg"; + String fileType = FileUtil.getExtensionName(multipartFile.getOriginalFilename()); + if(fileType != null && !image.contains(fileType)){ + throw new BadRequestException("文件格式错误!, 仅支持 " + image +" 格式"); + } + User user = userRepository.findByUsername(SecurityUtils.getCurrentUsername()); + String oldPath = user.getAvatarPath(); + File file = FileUtil.upload(multipartFile, properties.getPath().getAvatar()); + user.setAvatarPath(Objects.requireNonNull(file).getPath()); + user.setAvatarName(file.getName()); + userRepository.save(user); + if (StringUtils.isNotBlank(oldPath)) { + FileUtil.del(oldPath); + } + @NotBlank String username = user.getUsername(); + flushCache(username); + return new HashMap(1) {{ + put("avatar", file.getName()); + }}; } @Override @Transactional(rollbackFor = Exception.class) public void updateEmail(String username, String email) { - userRepository.updateEmail(username,email); + userRepository.updateEmail(username, email); + flushCache(username); + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (UserDto userDTO : queryAll) { + List roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList()); + Map map = new LinkedHashMap<>(); + map.put("用户名", userDTO.getUsername()); + map.put("角色", roles); + map.put("部门", userDTO.getDept().getName()); + map.put("岗位", userDTO.getJobs().stream().map(JobSmallDto::getName).collect(Collectors.toList())); + map.put("邮箱", userDTO.getEmail()); + map.put("状态", userDTO.getEnabled() ? "启用" : "禁用"); + map.put("手机号码", userDTO.getPhone()); + map.put("修改密码的时间", userDTO.getPwdResetTime()); + map.put("创建日期", userDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + /** + * 清理缓存 + * + * @param id / + */ + public void delCaches(Long id, String username) { + redisUtils.del(CacheKey.USER_ID + id); + flushCache(username); + } + + /** + * 清理 登陆时 用户缓存信息 + * + * @param username / + */ + private void flushCache(String username) { + userCacheManager.cleanUserCache(username); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/VerifyServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/VerifyServiceImpl.java new file mode 100644 index 000000000..ead7ba5a2 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/VerifyServiceImpl.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.impl; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.extra.template.Template; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import cn.hutool.extra.template.TemplateUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.domain.vo.EmailVo; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.system.service.VerifyService; +import me.zhengjie.utils.RedisUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.Collections; + +/** + * @author Zheng Jie + * @date 2018-12-26 + */ +@Service +@RequiredArgsConstructor +public class VerifyServiceImpl implements VerifyService { + + @Value("${code.expiration}") + private Long expiration; + private final RedisUtils redisUtils; + + @Override + @Transactional(rollbackFor = Exception.class) + public EmailVo sendEmail(String email, String key) { + EmailVo emailVo; + String content; + String redisKey = key + email; + // 如果不存在有效的验证码,就创建一个新的 + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + Template template = engine.getTemplate("email.ftl"); + String oldCode = redisUtils.get(redisKey, String.class); + if(oldCode == null){ + String code = RandomUtil.randomNumbers (6); + // 存入缓存 + if(!redisUtils.set(redisKey, code, expiration)){ + throw new BadRequestException("服务异常,请联系网站负责人"); + } + content = template.render(Dict.create().set("code",code)); + // 存在就再次发送原来的验证码 + } else { + content = template.render(Dict.create().set("code",oldCode)); + } + emailVo = new EmailVo(Collections.singletonList(email),"ELADMIN后台管理系统",content); + return emailVo; + } + + @Override + public void validated(String key, String code) { + String value = redisUtils.get(key, String.class); + if(!code.equals(value)){ + throw new BadRequestException("无效验证码"); + } else { + redisUtils.del(key); + } + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DeptMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DeptMapper.java deleted file mode 100644 index 46da4c2c5..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DeptMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.Dept; -import me.zhengjie.modules.system.service.dto.DeptDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** -* @author Zheng Jie -* @date 2019-03-25 -*/ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface DeptMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DeptSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DeptSmallMapper.java deleted file mode 100644 index 338d55a44..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DeptSmallMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.Dept; -import me.zhengjie.modules.system.service.dto.DeptSmallDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** -* @author Zheng Jie -* @date 2019-03-25 -*/ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface DeptSmallMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DictDetailMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DictDetailMapper.java deleted file mode 100644 index 797c1dab0..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DictDetailMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.DictDetail; -import me.zhengjie.modules.system.service.dto.DictDetailDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** -* @author Zheng Jie -* @date 2019-04-10 -*/ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface DictDetailMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DictMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DictMapper.java deleted file mode 100644 index 1d1f93d57..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/DictMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.Dict; -import me.zhengjie.modules.system.service.dto.DictDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** -* @author Zheng Jie -* @date 2019-04-10 -*/ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface DictMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/JobMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/JobMapper.java deleted file mode 100644 index b4d2b3473..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/JobMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.Job; -import me.zhengjie.modules.system.service.dto.JobDTO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.ReportingPolicy; - -/** -* @author Zheng Jie -* @date 2019-03-29 -*/ -@Mapper(componentModel = "spring",uses = {DeptMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface JobMapper extends EntityMapper { - - @Mapping(source = "deptSuperiorName", target = "deptSuperiorName") - JobDTO toDto(Job job, String deptSuperiorName); -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/JobSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/JobSmallMapper.java deleted file mode 100644 index c48b4d36a..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/JobSmallMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.Job; -import me.zhengjie.modules.system.service.dto.JobSmallDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** -* @author Zheng Jie -* @date 2019-03-29 -*/ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface JobSmallMapper extends EntityMapper { - -} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/MenuMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/MenuMapper.java deleted file mode 100644 index 513f1bc3a..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/MenuMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.modules.system.domain.Menu; -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.service.dto.MenuDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2018-12-17 - */ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface MenuMapper extends EntityMapper { - -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/PermissionMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/PermissionMapper.java deleted file mode 100644 index f86c9d4c4..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/PermissionMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.modules.system.domain.Permission; -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.service.dto.PermissionDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface PermissionMapper extends EntityMapper { - -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/RoleMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/RoleMapper.java deleted file mode 100644 index 9acaf31be..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/RoleMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.modules.system.domain.Role; -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.service.dto.RoleDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Mapper(componentModel = "spring", uses = {PermissionMapper.class, MenuMapper.class, DeptMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface RoleMapper extends EntityMapper { - -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/RoleSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/RoleSmallMapper.java deleted file mode 100644 index c5858fb4d..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/RoleSmallMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.domain.Role; -import me.zhengjie.modules.system.service.dto.RoleDTO; -import me.zhengjie.modules.system.service.dto.RoleSmallDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2019-5-23 - */ -@Mapper(componentModel = "spring", uses = {}, unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface RoleSmallMapper extends EntityMapper { - -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/UserMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/UserMapper.java deleted file mode 100644 index 50cbfb8fb..000000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapper/UserMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.zhengjie.modules.system.service.mapper; - -import me.zhengjie.modules.system.domain.User; -import me.zhengjie.mapper.EntityMapper; -import me.zhengjie.modules.system.service.dto.UserDTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** - * @author Zheng Jie - * @date 2018-11-23 - */ -@Mapper(componentModel = "spring",uses = {RoleMapper.class, DeptMapper.class, JobMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface UserMapper extends EntityMapper { - -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptMapper.java new file mode 100644 index 000000000..21c197632 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Dept; +import me.zhengjie.modules.system.service.dto.DeptDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-03-25 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DeptMapper extends BaseMapper { +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptSmallMapper.java new file mode 100644 index 000000000..5b3ba63a6 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DeptSmallMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Dept; +import me.zhengjie.modules.system.service.dto.DeptSmallDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-03-25 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DeptSmallMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictDetailMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictDetailMapper.java new file mode 100644 index 000000000..52b4020e6 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictDetailMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.DictDetail; +import me.zhengjie.modules.system.service.dto.DictDetailDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-04-10 +*/ +@Mapper(componentModel = "spring", uses = {DictSmallMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DictDetailMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictMapper.java new file mode 100644 index 000000000..58818acdc --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Dict; +import me.zhengjie.modules.system.service.dto.DictDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-04-10 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DictMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictSmallMapper.java new file mode 100644 index 000000000..a9707c595 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/DictSmallMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Dict; +import me.zhengjie.modules.system.service.dto.DictSmallDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-04-10 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DictSmallMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobMapper.java new file mode 100644 index 000000000..1012ab6a3 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Job; +import me.zhengjie.modules.system.service.dto.JobDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-03-29 +*/ +@Mapper(componentModel = "spring",uses = {DeptMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface JobMapper extends BaseMapper { +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobSmallMapper.java new file mode 100644 index 000000000..b5ad149e7 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/JobSmallMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Job; +import me.zhengjie.modules.system.service.dto.JobSmallDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-03-29 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface JobSmallMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/MenuMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/MenuMapper.java new file mode 100644 index 000000000..666cd6fdf --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/MenuMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Menu; +import me.zhengjie.modules.system.service.dto.MenuDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2018-12-17 + */ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface MenuMapper extends BaseMapper { +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleMapper.java new file mode 100644 index 000000000..fdabbe8cd --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Role; +import me.zhengjie.modules.system.service.dto.RoleDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Mapper(componentModel = "spring", uses = {MenuMapper.class, DeptMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface RoleMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleSmallMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleSmallMapper.java new file mode 100644 index 000000000..dd8e95860 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/RoleSmallMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.Role; +import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2019-5-23 + */ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface RoleSmallMapper extends BaseMapper { + +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/UserMapper.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/UserMapper.java new file mode 100644 index 000000000..9f40d60ab --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/mapstruct/UserMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.modules.system.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.modules.system.domain.User; +import me.zhengjie.modules.system.service.dto.UserDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Mapper(componentModel = "spring",uses = {RoleMapper.class, DeptMapper.class, JobMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface UserMapper extends BaseMapper { +} diff --git a/eladmin-system/src/main/java/me/zhengjie/sysrunner/SystemRunner.java b/eladmin-system/src/main/java/me/zhengjie/sysrunner/SystemRunner.java new file mode 100644 index 000000000..b69aba268 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/sysrunner/SystemRunner.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ +package me.zhengjie.sysrunner; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +/** + * @author Zheng Jie + * @description 程序启动后处理数据 + * @date 2025-01-13 + **/ +@Slf4j +@Component +@RequiredArgsConstructor +public class SystemRunner implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) { + } +} diff --git a/eladmin-system/src/main/resources/banner.txt b/eladmin-system/src/main/resources/banner.txt index d0f401a88..cc460ce3b 100644 --- a/eladmin-system/src/main/resources/banner.txt +++ b/eladmin-system/src/main/resources/banner.txt @@ -5,4 +5,4 @@ | __| | | (_| | (_| | | | | | | | | | | \___|_| \__,_|\__,_|_| |_| |_|_|_| |_| - :: Spring Boot :: (v2.1.0.RELEASE) \ No newline at end of file + :: Spring Boot :: (v2.6.4) \ No newline at end of file diff --git a/eladmin-system/src/main/resources/config/application-dev.yml b/eladmin-system/src/main/resources/config/application-dev.yml index 96eeef789..952326c09 100644 --- a/eladmin-system/src/main/resources/config/application-dev.yml +++ b/eladmin-system/src/main/resources/config/application-dev.yml @@ -2,56 +2,97 @@ spring: datasource: druid: - type: com.alibaba.druid.pool.DruidDataSource - driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy - url: jdbc:log4jdbc:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false + db-type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.p6spy.engine.spy.P6SpyDriver + url: jdbc:p6spy:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root password: 123456 - - # 初始化配置 - initial-size: 3 - # 最小连接数 - min-idle: 3 - # 最大连接数 - max-active: 15 - # 获取连接超时时间 - max-wait: 5000 - # 连接有效性检测时间 - time-between-eviction-runs-millis: 90000 - # 最大空闲时间 - min-evictable-idle-time-millis: 1800000 + # 初始连接数,建议设置为与最小空闲连接数相同 + initial-size: 20 + # 最小空闲连接数,保持足够的空闲连接以应对请求 + min-idle: 20 + # 最大连接数,根据并发需求适当增加 + max-active: 50 + # 获取连接超时时间(毫秒),调整以满足响应时间要求 + max-wait: 3000 + # 启用KeepAlive机制,保持长连接 + keep-alive: true + # 连接有效性检测间隔时间(毫秒),定期检查连接的健康状态 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),确保连接在池中至少存在一段时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),防止连接在池中停留过长 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 test-while-idle: true - test-on-borrow: false + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: true + # 是否在归还到池中前进行检验 test-on-return: false - - validation-query: select 1 - # 配置监控统计拦截的filters - filters: stat + # 停用 com_ping 探活机制 + use-ping-method: false + # 检测连接是否有效 + validation-query: SELECT 1 + # 配置监控统计 + webStatFilter: + enabled: true stat-view-servlet: + enabled: true url-pattern: /druid/* reset-enable: false + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 2000 + merge-sql: true + wall: + config: + multi-statement-allow: true - web-stat-filter: - url-pattern: /* - exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" - - #配置 Jpa - jpa: - hibernate: - # 生产环境设置成 none,避免程序运行时自动更新数据库结构 - ddl-auto: update +# 登录相关配置 +login: + # 是否限制单用户登录 + single-login: false + # Redis用户登录缓存配置 + user-cache: + # 存活时间/秒 + idle-time: 21600 + # 验证码 + code: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + height: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 #jwt jwt: header: Authorization - secret: mySecret - # token 过期时间 2个小时 - expiration: 7200000 - auth: - # 授权路径 - path: /login - # 获取用户信息 - account: /info + # 令牌前缀 + token-start-with: Bearer + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html + token-validity-in-seconds: 14400000 + # 在线用户key + online-key: "online_token:" + # 验证码 + code-key: "captcha_code:" + # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 + detect: 1800000 + # 续期时间范围,默认1小时,单位毫秒 + renew: 3600000 #是否允许生成代码,生产环境设置为false generator: @@ -59,4 +100,38 @@ generator: #是否开启 swagger-ui swagger: - enabled: true \ No newline at end of file + enabled: true + +# 文件存储路径 +file: + mac: + path: ~/file/ + avatar: ~/avatar/ + linux: + path: /home/eladmin/file/ + avatar: /home/eladmin/avatar/ + windows: + path: C:\eladmin\file\ + avatar: C:\eladmin\avatar\ + # 文件大小 /M + maxSize: 100 + avatarMaxSize: 5 + +# 亚马逊S3协议云存储配置 +#支持七牛云,阿里云OSS,腾讯云COS,华为云OBS,移动云EOS等 +amz: + s3: + # 地域 + region: test + # 地域对应的 endpoint + endPoint: https://s3.test.com + # 访问的域名 + domain: https://s3.test.com + # 账号的认证信息,或者子账号的认证信息 + accessKey: 填写你的AccessKey + secretKey: 填写你的SecretKey + # 存储桶(Bucket) + defaultBucket: 填写你的存储桶名称 + # 文件存储路径 + timeformat: yyyy-MM + diff --git a/eladmin-system/src/main/resources/config/application-prod.yml b/eladmin-system/src/main/resources/config/application-prod.yml index dfd1d7169..f01c6707e 100644 --- a/eladmin-system/src/main/resources/config/application-prod.yml +++ b/eladmin-system/src/main/resources/config/application-prod.yml @@ -2,58 +2,101 @@ spring: datasource: druid: - type: com.alibaba.druid.pool.DruidDataSource - driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy - url: jdbc:log4jdbc:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false - username: root - password: 123456 - - # 初始化配置 - initial-size: 3 - # 最小连接数 - min-idle: 3 - # 最大连接数 - max-active: 15 - # 获取连接超时时间 - max-wait: 5000 - # 连接有效性检测时间 - time-between-eviction-runs-millis: 90000 - # 最大空闲时间 - min-evictable-idle-time-millis: 1800000 + db-type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:eladmin}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false + username: ${DB_USER:root} + password: ${DB_PWD:123456} + # 初始连接数,建议设置为与最小空闲连接数相同 + initial-size: 20 + # 最小空闲连接数,保持足够的空闲连接以应对请求 + min-idle: 20 + # 最大连接数,根据并发需求适当增加 + max-active: 50 + # 获取连接超时时间(毫秒),调整以满足响应时间要求 + max-wait: 3000 + # 启用KeepAlive机制,保持长连接 + keep-alive: true + # 连接有效性检测间隔时间(毫秒),定期检查连接的健康状态 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),确保连接在池中至少存在一段时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),防止连接在池中停留过长 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 test-while-idle: true - test-on-borrow: false + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: true + # 是否在归还到池中前进行检验 test-on-return: false - validation-query: select 1 - # 配置监控统计拦截的filters - filters: stat - + # 停用 com_ping 探活机制 + use-ping-method: false + # 检测连接是否有效 + validation-query: SELECT 1 + # 配置监控统计 + webStatFilter: + enabled: true stat-view-servlet: + allow: + enabled: true + # 控制台管理用户名和密码 url-pattern: /druid/* reset-enable: false login-username: admin login-password: 123456 + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 2000 + merge-sql: true + wall: + config: + multi-statement-allow: true - web-stat-filter: - url-pattern: /* - exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" - - #配置 Jpa - jpa: - hibernate: - # 生产环境设置成 none,避免程序运行时自动更新数据库结构 - ddl-auto: none +# 登录相关配置 +login: + # 是否限制单用户登录 + single-login: false + # Redis用户登录缓存配置 + user-cache: + # 存活时间/秒 + idle-time: 21600 + # 验证码 + code: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + height: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体,如遇到线上乱码,设置其他字体即可 + font-name: + # 字体大小 + font-size: 25 #jwt jwt: header: Authorization - secret: mySecret - # token 过期时间 2个小时 - expiration: 7200000 - auth: - # 授权路径 - path: /login - # 获取用户信息 - account: /info + # 令牌前缀 + token-start-with: Bearer + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html + token-validity-in-seconds: 7200000 + # 在线用户key + online-key: "online_token:" + # 验证码 + code-key: "captcha_code:" + # token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 + detect: 1800000 + # 续期时间范围,默认 1小时,这里单位毫秒 + renew: 3600000 #是否允许生成代码,生产环境设置为false generator: @@ -68,4 +111,37 @@ generator: #是否开启 swagger-ui swagger: - enabled: false \ No newline at end of file + enabled: false + +# 文件存储路径 +file: + mac: + path: ~/file/ + avatar: ~/avatar/ + linux: + path: /home/eladmin/file/ + avatar: /home/eladmin/avatar/ + windows: + path: C:\eladmin\file\ + avatar: C:\eladmin\avatar\ + # 文件大小 /M + maxSize: 100 + avatarMaxSize: 5 + +# 亚马逊S3协议云存储配置 +#支持七牛云,阿里云OSS,腾讯云COS,华为云OBS,移动云EOS等 +amz: + s3: + # 地域 + region: test + # 地域对应的 endpoint + endPoint: https://s3.test.com + # 访问的域名 + domain: https://s3.test.com + # 账号的认证信息,或者子账号的认证信息 + accessKey: 填写你的AccessKey + secretKey: 填写你的SecretKey + # 存储桶(Bucket) + defaultBucket: 填写你的存储桶名称 + # 文件存储路径 + timeformat: yyyy-MM \ No newline at end of file diff --git a/eladmin-system/src/main/resources/config/application-quartz.yml b/eladmin-system/src/main/resources/config/application-quartz.yml new file mode 100644 index 000000000..561482c6d --- /dev/null +++ b/eladmin-system/src/main/resources/config/application-quartz.yml @@ -0,0 +1,29 @@ +# 配置 quartz 分布式支持, sql 文件在 sql 目录下,需要导入到数据库,并且需要修改 application.yml 文件的 active: dev 配置 +spring: + quartz: + # 必需,指定使用 JDBC 存储 + job-store-type: jdbc + properties: + org: + quartz: + scheduler: + # 必需,指定调度器实例的名称 + instanceName: eladmin + # 必需,指定调度器实例的 ID + instanceId: auto + threadPool: + # 可选,线程池的线程数量,可以根据需要调整 + threadCount: 5 + jobStore: + # 可选,如果你不需要集群,可以去掉 + isClustered: true + # 可选,集群检查间隔时间,可以根据需要调整 + clusterCheckinInterval: 10000 + # 必需,指定 JDBC 驱动代理类 + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate + # 可选,是否使用属性存储,可以根据需要调整 + useProperties: false + # 必需,指定表的前缀 + tablePrefix: qrtz_ + # 可选,指定误触发阈值,可以根据需要调整 + misfireThreshold: 60000 \ No newline at end of file diff --git a/eladmin-system/src/main/resources/config/application.yml b/eladmin-system/src/main/resources/config/application.yml index 2127c638f..3af22f0da 100644 --- a/eladmin-system/src/main/resources/config/application.yml +++ b/eladmin-system/src/main/resources/config/application.yml @@ -1,41 +1,72 @@ server: port: 8000 + http2: + # 启用 HTTP/2 支持,提升传输效率 + enabled: true + compression: + # 启用 GZIP 压缩,减少传输数据量 + enabled: true + # 需要压缩的 MIME 类型 + mime-types: text/html, text/xml, text/plain, application/json + # 最小压缩响应大小(字节) spring: + freemarker: + check-template-location: false profiles: + # 激活的环境,如果需要 quartz 分布式支持,需要修改 active: dev,quartz active: dev - jackson: - time-zone: GMT+8 data: redis: repositories: enabled: false +# pid: +# file: /自行指定位置/eladmin.pid #配置 Jpa jpa: + hibernate: + ddl-auto: none + open-in-view: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5InnoDBDialect - open-in-view: true redis: #数据库索引 - database: 0 - host: 127.0.0.1 - port: 6379 - password: + database: ${REDIS_DB:0} + host: ${REDIS_HOST:127.0.0.1} + port: ${REDIS_PORT:6379} + password: ${REDIS_PWD:} #连接超时时间 timeout: 5000 + # 连接池配置 + lettuce: + pool: + # 连接池最大连接数 + max-active: 30 + # 连接池最大阻塞等待时间(毫秒),负值表示没有限制 + max-wait: -1 + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 1 -#七牛云 -qiniu: - # 文件大小 /M - max-size: 15 +task: + pool: + # 核心线程池大小 + core-pool-size: 10 + # 最大线程数 + max-pool-size: 30 + # 活跃时间 + keep-alive-seconds: 60 + # 队列容量 + queue-capacity: 50 -#邮箱验证码有效时间/分钟 +#邮箱验证码有效时间/秒 code: - expiration: 5 + expiration: 300 -#登录图形验证码有效时间/分钟 -loginCode: - expiration: 2 \ No newline at end of file +#密码加密传输,前端公钥加密,后端私钥解密 +rsa: + private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== \ No newline at end of file diff --git a/eladmin-system/src/main/resources/ip2region/ip2region.db b/eladmin-system/src/main/resources/ip2region/ip2region.db deleted file mode 100644 index 43e1daf59..000000000 Binary files a/eladmin-system/src/main/resources/ip2region/ip2region.db and /dev/null differ diff --git a/eladmin-system/src/main/resources/log4jdbc.log4j2.properties b/eladmin-system/src/main/resources/log4jdbc.log4j2.properties deleted file mode 100644 index 5cb6f9914..000000000 --- a/eladmin-system/src/main/resources/log4jdbc.log4j2.properties +++ /dev/null @@ -1,2 +0,0 @@ -# If you use SLF4J. First, you need to tell log4jdbc-log4j2 that you want to use the SLF4J logger -log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator \ No newline at end of file diff --git a/eladmin-system/src/main/resources/logback.xml b/eladmin-system/src/main/resources/logback.xml index 5d7e59d80..6ed7c43dd 100644 --- a/eladmin-system/src/main/resources/logback.xml +++ b/eladmin-system/src/main/resources/logback.xml @@ -1,12 +1,14 @@ - + elAdmin + + - %black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n) - utf-8 + ${log.pattern} + ${log.charset} @@ -15,28 +17,13 @@ - - - - + + - - - + + - - - - - - - - - - - - - - - - \ No newline at end of file + + + + diff --git a/eladmin-system/src/main/resources/spy.properties b/eladmin-system/src/main/resources/spy.properties new file mode 100644 index 000000000..fc814804a --- /dev/null +++ b/eladmin-system/src/main/resources/spy.properties @@ -0,0 +1,29 @@ +# 应用的拦截模块 +modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory + +# 自定义日志打印 +logMessageFormat=me.zhengjie.config.CustomP6SpyLogger + +# 日志输出到控制台 +appender=com.p6spy.engine.spy.appender.Slf4JLogger + +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss + +# 实际驱动可多个 +driverlist=com.mysql.cj.jdbc.Driver + +# 是否开启慢SQL记录 +outagedetection=true + +# 慢SQL记录标准 2 秒 +outagedetectioninterval=2 + +# 是否过滤 Log +filter=true + +# 过滤 Log 时所排除的 sql 关键字,以逗号分隔 +exclude=SELECT 1,INSERT INTO sys_log + +# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/email/email.ftl b/eladmin-system/src/main/resources/template/email.ftl similarity index 77% rename from eladmin-system/src/main/resources/template/email/email.ftl rename to eladmin-system/src/main/resources/template/email.ftl index 3a1863582..606d490e0 100644 --- a/eladmin-system/src/main/resources/template/email/email.ftl +++ b/eladmin-system/src/main/resources/template/email.ftl @@ -21,7 +21,7 @@

尊敬的用户,您好:

您正在申请邮箱验证,您的验证码为:

-

${code}

-
- Copyright ©${.now?string("yyyy")} EL-ADMIN 后台管理系统 All Rights Reserved. + Copyright ©${.now?string("yyyy")} ELADMIN 后台管理系统 All Rights Reserved.
diff --git a/eladmin-system/src/main/resources/template/generator/admin/Controller.ftl b/eladmin-system/src/main/resources/template/generator/admin/Controller.ftl deleted file mode 100644 index 9ec0b10c9..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/Controller.ftl +++ /dev/null @@ -1,61 +0,0 @@ -package ${package}.rest; - -import me.zhengjie.aop.log.Log; -import ${package}.domain.${className}; -import ${package}.service.${className}Service; -import ${package}.service.dto.${className}QueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import io.swagger.annotations.*; - -/** -* @author ${author} -* @date ${date} -*/ -@Api(tags = "${className}管理") -@RestController -@RequestMapping("api") -public class ${className}Controller { - - @Autowired - private ${className}Service ${changeClassName}Service; - - @Log("查询${className}") - @ApiOperation(value = "查询${className}") - @GetMapping(value = "/${changeClassName}") - @PreAuthorize("hasAnyRole('ADMIN','${upperCaseClassName}_ALL','${upperCaseClassName}_SELECT')") - public ResponseEntity get${className}s(${className}QueryCriteria criteria, Pageable pageable){ - return new ResponseEntity(${changeClassName}Service.queryAll(criteria,pageable),HttpStatus.OK); - } - - @Log("新增${className}") - @ApiOperation(value = "新增${className}") - @PostMapping(value = "/${changeClassName}") - @PreAuthorize("hasAnyRole('ADMIN','${upperCaseClassName}_ALL','${upperCaseClassName}_CREATE')") - public ResponseEntity create(@Validated @RequestBody ${className} resources){ - return new ResponseEntity(${changeClassName}Service.create(resources),HttpStatus.CREATED); - } - - @Log("修改${className}") - @ApiOperation(value = "修改${className}") - @PutMapping(value = "/${changeClassName}") - @PreAuthorize("hasAnyRole('ADMIN','${upperCaseClassName}_ALL','${upperCaseClassName}_EDIT')") - public ResponseEntity update(@Validated @RequestBody ${className} resources){ - ${changeClassName}Service.update(resources); - return new ResponseEntity(HttpStatus.NO_CONTENT); - } - - @Log("删除${className}") - @ApiOperation(value = "删除${className}") - @DeleteMapping(value = "/${changeClassName}/{${pkChangeColName}}") - @PreAuthorize("hasAnyRole('ADMIN','${upperCaseClassName}_ALL','${upperCaseClassName}_DELETE')") - public ResponseEntity delete(@PathVariable ${pkColumnType} ${pkChangeColName}){ - ${changeClassName}Service.delete(${pkChangeColName}); - return new ResponseEntity(HttpStatus.OK); - } -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/Dto.ftl b/eladmin-system/src/main/resources/template/generator/admin/Dto.ftl deleted file mode 100644 index d57910b40..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/Dto.ftl +++ /dev/null @@ -1,38 +0,0 @@ -package ${package}.service.dto; - -import lombok.Data; -<#if hasTimestamp> -import java.sql.Timestamp; - -<#if hasBigDecimal> -import java.math.BigDecimal; - -import java.io.Serializable; -<#if !auto && pkColumnType = 'Long'> -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; - - - -/** -* @author ${author} -* @date ${date} -*/ -@Data -public class ${className}DTO implements Serializable { -<#if columns??> - <#list columns as column> - - <#if column.columnComment != ''> - // ${column.columnComment} - - <#if column.columnKey = 'PRI'> - <#if !auto && pkColumnType = 'Long'> - // 处理精度丢失问题 - @JsonSerialize(using= ToStringSerializer.class) - - - private ${column.columnType} ${column.changeColumnName}; - - -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/Entity.ftl b/eladmin-system/src/main/resources/template/generator/admin/Entity.ftl deleted file mode 100644 index 5bf7eab48..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/Entity.ftl +++ /dev/null @@ -1,43 +0,0 @@ -package ${package}.domain; - -import lombok.Data; -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.bean.copier.CopyOptions; -import javax.persistence.*; -<#if hasTimestamp> -import java.sql.Timestamp; - -<#if hasBigDecimal> -import java.math.BigDecimal; - -import java.io.Serializable; - -/** -* @author ${author} -* @date ${date} -*/ -@Entity -@Data -@Table(name="${tableName}") -public class ${className} implements Serializable { -<#if columns??> - <#list columns as column> - - <#if column.columnComment != ''> - // ${column.columnComment} - - <#if column.columnKey = 'PRI'> - @Id - <#if auto> - @GeneratedValue(strategy = GenerationType.IDENTITY) - - - @Column(name = "${column.columnName}"<#if column.columnKey = 'UNI'>,unique = true<#if column.isNullable = 'NO' && column.columnKey != 'PRI'>,nullable = false) - private ${column.columnType} ${column.changeColumnName}; - - - - public void copy(${className} source){ - BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); - } -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/Mapper.ftl b/eladmin-system/src/main/resources/template/generator/admin/Mapper.ftl deleted file mode 100644 index 2f067d30e..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/Mapper.ftl +++ /dev/null @@ -1,16 +0,0 @@ -package ${package}.service.mapper; - -import me.zhengjie.mapper.EntityMapper; -import ${package}.domain.${className}; -import ${package}.service.dto.${className}DTO; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -/** -* @author ${author} -* @date ${date} -*/ -@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface ${className}Mapper extends EntityMapper<${className}DTO, ${className}> { - -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/QueryCriteria.ftl b/eladmin-system/src/main/resources/template/generator/admin/QueryCriteria.ftl deleted file mode 100644 index 514e92cdd..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/QueryCriteria.ftl +++ /dev/null @@ -1,34 +0,0 @@ -package ${package}.service.dto; - -import lombok.Data; -<#if hasTimestamp> -import java.sql.Timestamp; - -<#if hasBigDecimal> -import java.math.BigDecimal; - -<#if queryColumns??> -import me.zhengjie.annotation.Query; - - -/** -* @author ${author} -* @date ${date} -*/ -@Data -public class ${className}QueryCriteria{ -<#if queryColumns??> - <#list queryColumns as column> - - <#if column.columnQuery = '1'> - // 模糊 - @Query(type = Query.Type.INNER_LIKE) - - <#if column.columnQuery = '2'> - // 精确 - @Query - - private ${column.columnType} ${column.changeColumnName}; - - -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/Repository.ftl b/eladmin-system/src/main/resources/template/generator/admin/Repository.ftl deleted file mode 100644 index 9b8968189..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/Repository.ftl +++ /dev/null @@ -1,25 +0,0 @@ -package ${package}.repository; - -import ${package}.domain.${className}; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -/** -* @author ${author} -* @date ${date} -*/ -public interface ${className}Repository extends JpaRepository<${className}, ${pkColumnType}>, JpaSpecificationExecutor { -<#if columns??> - <#list columns as column> - <#if column.columnKey = 'UNI'> - - /** - * findBy${column.capitalColumnName} - * @param ${column.columnName} - * @return - */ - ${className} findBy${column.capitalColumnName}(${column.columnType} ${column.columnName}); - - - -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/admin/Service.ftl b/eladmin-system/src/main/resources/template/generator/admin/Service.ftl deleted file mode 100644 index e6aa6b9ee..000000000 --- a/eladmin-system/src/main/resources/template/generator/admin/Service.ftl +++ /dev/null @@ -1,64 +0,0 @@ -package ${package}.service; - -import ${package}.domain.${className}; -import ${package}.service.dto.${className}DTO; -import ${package}.service.dto.${className}QueryCriteria; -//import org.springframework.cache.annotation.CacheConfig; -//import org.springframework.cache.annotation.CacheEvict; -//import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.Pageable; - -/** -* @author ${author} -* @date ${date} -*/ -//@CacheConfig(cacheNames = "${changeClassName}") -public interface ${className}Service { - - /** - * queryAll 分页 - * @param criteria - * @param pageable - * @return - */ - //@Cacheable(keyGenerator = "keyGenerator") - Object queryAll(${className}QueryCriteria criteria, Pageable pageable); - - /** - * queryAll 不分页 - * @param criteria - * @return - */ - //@Cacheable(keyGenerator = "keyGenerator") - public Object queryAll(${className}QueryCriteria criteria); - - /** - * findById - * @param ${pkChangeColName} - * @return - */ - //@Cacheable(key = "#p0") - ${className}DTO findById(${pkColumnType} ${pkChangeColName}); - - /** - * create - * @param resources - * @return - */ - //@CacheEvict(allEntries = true) - ${className}DTO create(${className} resources); - - /** - * update - * @param resources - */ - //@CacheEvict(allEntries = true) - void update(${className} resources); - - /** - * delete - * @param ${pkChangeColName} - */ - //@CacheEvict(allEntries = true) - void delete(${pkColumnType} ${pkChangeColName}); -} \ No newline at end of file diff --git a/eladmin-system/src/main/resources/template/generator/front/eForm.ftl b/eladmin-system/src/main/resources/template/generator/front/eForm.ftl deleted file mode 100644 index 84acd88fb..000000000 --- a/eladmin-system/src/main/resources/template/generator/front/eForm.ftl +++ /dev/null @@ -1,108 +0,0 @@ - - - - - diff --git a/eladmin-system/src/main/resources/template/generator/front/index.ftl b/eladmin-system/src/main/resources/template/generator/front/index.ftl deleted file mode 100644 index c5e6ab88e..000000000 --- a/eladmin-system/src/main/resources/template/generator/front/index.ftl +++ /dev/null @@ -1,160 +0,0 @@ -<#--noinspection ALL--> - - - - - diff --git a/eladmin-system/src/main/resources/template/taskAlarm.ftl b/eladmin-system/src/main/resources/template/taskAlarm.ftl new file mode 100644 index 000000000..a29b07801 --- /dev/null +++ b/eladmin-system/src/main/resources/template/taskAlarm.ftl @@ -0,0 +1,69 @@ + + + + + + + +
+
+

任务信息:

+ + + + + + + + + + + + + + + + + +
任务名称Bean名称执行方法参数内容Cron表达式描述内容
${task.jobName}${task.beanName}${task.methodName}${(task.params)!""}${task.cronExpression}${(task.description)!""}
+
+
+

异常信息:

+
+                ${msg}
+            
+
+
+
+
+ Copyright ©${.now?string("yyyy")} ELADMIN 后台管理系统 All Rights Reserved. +
+ +
+ + + diff --git a/eladmin-system/src/test/java/me/zhengjie/EladminSystemApplicationTests.java b/eladmin-system/src/test/java/me/zhengjie/EladminSystemApplicationTests.java index c78714a45..d3986f0f2 100644 --- a/eladmin-system/src/test/java/me/zhengjie/EladminSystemApplicationTests.java +++ b/eladmin-system/src/test/java/me/zhengjie/EladminSystemApplicationTests.java @@ -1,17 +1,16 @@ package me.zhengjie; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringRunner.class) -@SpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class EladminSystemApplicationTests { @Test public void contextLoads() { } + public static void main(String[] args) { + } } diff --git a/eladmin-tools/pom.xml b/eladmin-tools/pom.xml index dbefcd2b3..a53107979 100644 --- a/eladmin-tools/pom.xml +++ b/eladmin-tools/pom.xml @@ -1,20 +1,18 @@ - + eladmin me.zhengjie - 2.1 + 2.7 4.0.0 eladmin-tools + 工具模块 1.4.7 - [7.2.0, 7.2.99] - 3.1.0 + 4.22.57.ALL @@ -22,7 +20,7 @@ me.zhengjie eladmin-logging - 2.1 + 2.7 @@ -32,18 +30,19 @@ ${mail.version} - - - com.qiniu - qiniu-java-sdk - ${qiniu.version} - - com.alipay.sdk alipay-sdk-java ${alipay.version} + + + + software.amazon.awssdk + s3 + 2.30.13 + compile + - \ No newline at end of file + diff --git a/eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java b/eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java new file mode 100644 index 000000000..fcf952a8a --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java @@ -0,0 +1,78 @@ +package me.zhengjie.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import java.net.URI; + +/** + * @author Zheng Jie + * @date 2025-06-25 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "amz.s3") +public class AmzS3Config { + + /** + * Amazon S3 的区域配置,例如 "us-west-2"。 + * 该区域决定了 S3 存储桶的地理位置。 + */ + private String region; + + /** + * Amazon S3 的端点 URL + * 该端点用于访问 S3 服务。 + */ + private String endPoint; + + /** + * Amazon S3 的域名 + * 该域名用于构建访问 S3 服务的完整 URL。 + */ + private String domain; + + /** + * Amazon S3 的访问密钥 ID,用于身份验证。 + * 该密钥与 secretKey 一起使用来授权对 S3 服务的访问。 + */ + private String accessKey; + + /** + * Amazon S3 的秘密访问密钥,用于身份验证。 + * 该密钥与 accessKey 一起使用来授权对 S3 服务的访问。 + */ + private String secretKey; + + /** + * 默认的 S3 存储桶名称。 + * 该存储桶用于存储上传的文件和数据。 + */ + private String defaultBucket; + + /** + * 文件上传后存储的文件夹格式,默认为 "yyyy-MM"。 + */ + private String timeformat; + + /** + * 创建并返回一个 AmazonS3 客户端实例。 + * 使用当前配置类的 endPoint, region, accessKey 和 secretKey。 + * 声明为 @Bean 后,Spring 会将其作为单例管理,并在需要时自动注入。 + * + * @return 配置好的 AmazonS3 客户端实例 + */ + @Bean + public S3Client amazonS3Client() { + return S3Client.builder().region(Region.of(region)) + .endpointOverride(URI.create(endPoint)) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))) + .build(); + } +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/config/MultipartConfig.java b/eladmin-tools/src/main/java/me/zhengjie/config/MultipartConfig.java deleted file mode 100644 index f0893fc33..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/config/MultipartConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.zhengjie.config; - -import org.springframework.boot.web.servlet.MultipartConfigFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import javax.servlet.MultipartConfigElement; -import java.io.File; - -/** - * @date 2018-12-28 - * @author https://blog.csdn.net/llibin1024530411/article/details/79474953 - */ -@Configuration -public class MultipartConfig { - - /** - * 文件上传临时路径 - */ - @Bean - MultipartConfigElement multipartConfigElement() { - MultipartConfigFactory factory = new MultipartConfigFactory(); - String location = System.getProperty("user.home") + "/.eladmin/file/tmp"; - File tmpFile = new File(location); - if (!tmpFile.exists()) { - tmpFile.mkdirs(); - } - factory.setLocation(location); - return factory.createMultipartConfig(); - } -} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/AlipayConfig.java b/eladmin-tools/src/main/java/me/zhengjie/domain/AlipayConfig.java index ac3f48618..deabb360d 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/AlipayConfig.java +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/AlipayConfig.java @@ -1,5 +1,21 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.domain; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.persistence.*; import javax.validation.constraints.NotBlank; @@ -12,74 +28,49 @@ */ @Data @Entity -@Table(name = "alipay_config") +@Table(name = "tool_alipay_config") public class AlipayConfig implements Serializable { @Id + @Column(name = "config_id") + @ApiModelProperty(value = "ID", hidden = true) private Long id; - /** - * 应用ID,APPID,收款账号既是APPID对应支付宝账号 - */ @NotBlank - @Column(name = "app_id") - private String appID; + @ApiModelProperty(value = "应用ID") + private String appId; - /** - * 商户私钥,您的PKCS8格式RSA2私钥 - */ @NotBlank - @Column(name = "private_key", columnDefinition = "text") + @ApiModelProperty(value = "商户私钥") private String privateKey; - /** - * 支付宝公钥 - */ @NotBlank - @Column(name = "public_key", columnDefinition = "text") + @ApiModelProperty(value = "支付宝公钥") private String publicKey; - /** - * 签名方式,固定格式 - */ - @Column(name = "sign_type") + @ApiModelProperty(value = "签名方式") private String signType="RSA2"; - /** - * 支付宝开放安全地址,一般不会变 - */ @Column(name = "gateway_url") + @ApiModelProperty(value = "支付宝开放安全地址", hidden = true) private String gatewayUrl = "https://openapi.alipaydev.com/gateway.do"; - /** - * 编码,固定格式 - */ + @ApiModelProperty(value = "编码", hidden = true) private String charset= "utf-8"; - /** - * 异步通知地址 - */ @NotBlank - @Column(name = "notify_url") + @ApiModelProperty(value = "异步通知地址") private String notifyUrl; - /** - * 订单完成后返回的页面 - */ @NotBlank - @Column(name = "return_url") + @ApiModelProperty(value = "订单完成后返回的页面") private String returnUrl; - /** - * 类型,固定格式 - */ + @ApiModelProperty(value = "类型") private String format="JSON"; - /** - * 商户号 - */ @NotBlank - @Column(name = "sys_service_provider_id") + @ApiModelProperty(value = "商户号") private String sysServiceProviderId; } diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/EmailConfig.java b/eladmin-tools/src/main/java/me/zhengjie/domain/EmailConfig.java index 04157e783..3f8bd18b4 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/EmailConfig.java +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/EmailConfig.java @@ -1,7 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.domain; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; - import javax.persistence.*; import javax.validation.constraints.NotBlank; import java.io.Serializable; @@ -13,37 +28,31 @@ */ @Entity @Data -@Table(name = "email_config") +@Table(name = "tool_email_config") public class EmailConfig implements Serializable { @Id + @Column(name = "config_id") + @ApiModelProperty(value = "ID", hidden = true) private Long id; - /** - *邮件服务器SMTP地址 - */ @NotBlank + @ApiModelProperty(value = "邮件服务器SMTP地址") private String host; - /** - * 邮件服务器SMTP端口 - */ @NotBlank + @ApiModelProperty(value = "邮件服务器 SMTP 端口") private String port; - /** - * 发件者用户名,默认为发件人邮箱前缀 - */ @NotBlank + @ApiModelProperty(value = "发件者用户名") private String user; @NotBlank + @ApiModelProperty(value = "密码") private String pass; - /** - * 收件人 - */ @NotBlank - @Column(name = "from_user") + @ApiModelProperty(value = "收件人") private String fromUser; } diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/LocalStorage.java b/eladmin-tools/src/main/java/me/zhengjie/domain/LocalStorage.java new file mode 100644 index 000000000..51d5cf6c9 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/LocalStorage.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.domain; + +import io.swagger.annotations.ApiModelProperty; +import lombok.*; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +@Getter +@Setter +@Entity +@Table(name="tool_local_storage") +@NoArgsConstructor +public class LocalStorage extends BaseEntity implements Serializable { + + @Id + @Column(name = "storage_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ApiModelProperty(value = "真实文件名") + private String realName; + + @ApiModelProperty(value = "文件名") + private String name; + + @ApiModelProperty(value = "后缀") + private String suffix; + + @ApiModelProperty(value = "路径") + private String path; + + @ApiModelProperty(value = "类型") + private String type; + + @ApiModelProperty(value = "大小") + private String size; + + public LocalStorage(String realName,String name, String suffix, String path, String type, String size) { + this.realName = realName; + this.name = name; + this.suffix = suffix; + this.path = path; + this.type = type; + this.size = size; + } + + public void copy(LocalStorage source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/Picture.java b/eladmin-tools/src/main/java/me/zhengjie/domain/Picture.java deleted file mode 100644 index 8999cd848..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/Picture.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.zhengjie.domain; - -import lombok.Data; -import org.hibernate.annotations.CreationTimestamp; - -import javax.persistence.*; -import java.io.Serializable; -import java.sql.Timestamp; - -/** - * sm.ms图床 - * - * @author Zheng Jie - * @date 2018-12-27 - */ -@Data -@Entity -@Table(name = "picture") -public class Picture implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String filename; - - private String url; - - private String size; - - private String height; - - private String width; - - /** - * delete URl - */ - @Column(name = "delete_url") - private String delete; - - private String username; - - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; - - @Override - public String toString() { - return "Picture{" + - "filename='" + filename + '\'' + - '}'; - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuConfig.java b/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuConfig.java deleted file mode 100644 index 0fdd67dcf..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuConfig.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.zhengjie.domain; - -import lombok.Data; -import javax.persistence.*; -import javax.validation.constraints.NotBlank; -import java.io.Serializable; - -/** - * 七牛云对象存储配置类 - * @author Zheng Jie - * @date 2018-12-31 - */ -@Data -@Entity -@Table(name = "qiniu_config") -public class QiniuConfig implements Serializable { - - @Id - private Long id; - - /** - * 一个账号最多拥有两对密钥(Access/Secret Key) - */ - @NotBlank - @Column(name = "access_key", columnDefinition = "text") - private String accessKey; - - /** - * 一个账号最多拥有两对密钥(Access/Secret Key) - */ - @NotBlank - @Column(name = "secret_key", columnDefinition = "text") - private String secretKey; - - /** - * 存储空间名称作为唯一的 Bucket 识别符 - */ - @NotBlank - private String bucket; - - /** - * Zone表示与机房的对应关系 - * 华东 Zone.zone0() - * 华北 Zone.zone1() - * 华南 Zone.zone2() - * 北美 Zone.zoneNa0() - * 东南亚 Zone.zoneAs0() - */ - @NotBlank - private String zone; - - /** - * 外链域名,可自定义,需在七牛云绑定 - */ - @NotBlank - private String host; - - /** - * 空间类型:公开/私有 - */ - private String type = "公开"; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuContent.java b/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuContent.java deleted file mode 100644 index 1a806bb70..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuContent.java +++ /dev/null @@ -1,56 +0,0 @@ -package me.zhengjie.domain; - -import lombok.Data; -import org.hibernate.annotations.UpdateTimestamp; - -import javax.persistence.*; -import java.io.Serializable; -import java.sql.Timestamp; - -/** - * 上传成功后,存储结果 - * @author Zheng Jie - * @date 2018-12-31 - */ -@Data -@Entity -@Table(name = "qiniu_content") -public class QiniuContent implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * 文件名,如qiniu.jpg - */ - @Column(name = "name",unique = false) - private String key; - - /** - * 空间名 - */ - private String bucket; - - /** - * 大小 - */ - private String size; - - /** - * 文件地址 - */ - private String url; - - /** - * 空间类型:公开/私有 - */ - private String type = "公开"; - - /** - * 更新时间 - */ - @UpdateTimestamp - @Column(name = "update_time") - private Timestamp updateTime; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java b/eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java new file mode 100644 index 000000000..389f6273c --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java @@ -0,0 +1,72 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package me.zhengjie.domain; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** +* @description S3存储实体类 +* @author Zheng Jie +* @date 2025-06-25 +**/ +@Data +@Entity +@Table(name = "tool_s3_storage") +@EqualsAndHashCode(callSuper = true) +public class S3Storage extends BaseEntity implements Serializable { + + @Id + @Column(name = "storage_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank + @ApiModelProperty(value = "文件名称") + private String fileName; + + @NotBlank + @ApiModelProperty(value = "真实存储的名称") + private String fileRealName; + + @NotBlank + @ApiModelProperty(value = "文件大小") + private String fileSize; + + @NotBlank + @ApiModelProperty(value = "文件MIME 类型") + private String fileMimeType; + + @NotBlank + @ApiModelProperty(value = "文件类型") + private String fileType; + + @NotBlank + @ApiModelProperty(value = "文件路径") + private String filePath; + + public void copy(S3Storage source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/VerificationCode.java b/eladmin-tools/src/main/java/me/zhengjie/domain/VerificationCode.java deleted file mode 100644 index 6fd573a18..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/VerificationCode.java +++ /dev/null @@ -1,65 +0,0 @@ -package me.zhengjie.domain; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.CreationTimestamp; - -import javax.persistence.*; -import javax.validation.constraints.NotBlank; -import java.sql.Timestamp; - -/** - * @author Zheng Jie - * @date 2018-12-26 - */ -@Data -@Entity -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "verification_code") -public class VerificationCode { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String code; - - /** - * 使用场景,自己定义 - */ - private String scenes; - - /** - * true 为有效,false 为无效,验证时状态+时间+具体的邮箱或者手机号 - */ - private Boolean status = true; - - - /** - * 类型 :phone 和 email - */ - @NotBlank - private String type; - - /** - * 具体的phone与email - */ - @NotBlank - private String value; - - /** - * 创建日期 - */ - @CreationTimestamp - @Column(name = "create_time") - private Timestamp createTime; - - public VerificationCode(String code, String scenes, @NotBlank String type, @NotBlank String value) { - this.code = code; - this.scenes = scenes; - this.type = type; - this.value = value; - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/enums/AliPayStatusEnum.java b/eladmin-tools/src/main/java/me/zhengjie/domain/enums/AliPayStatusEnum.java new file mode 100644 index 000000000..23870f89e --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/enums/AliPayStatusEnum.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.domain.enums; + +/** + * 支付状态 + * @author zhengjie + * @date 2018/08/01 16:45:43 + */ +public enum AliPayStatusEnum { + + /** 交易成功 */ + FINISHED("TRADE_FINISHED"), + + /** 支付成功 */ + SUCCESS("TRADE_SUCCESS"), + + /** 交易创建 */ + BUYER_PAY("WAIT_BUYER_PAY"), + + /** 交易关闭 */ + CLOSED("TRADE_CLOSED"); + + private final String value; + + AliPayStatusEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/vo/EmailVo.java b/eladmin-tools/src/main/java/me/zhengjie/domain/vo/EmailVo.java index a0767cbbe..40896e70e 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/vo/EmailVo.java +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/vo/EmailVo.java @@ -1,9 +1,24 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.domain.vo; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; - import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import java.util.List; @@ -18,15 +33,15 @@ @NoArgsConstructor public class EmailVo { - /** - * 收件人,支持多个收件人,用逗号分隔 - */ @NotEmpty + @ApiModelProperty(value = "收件人") private List tos; @NotBlank + @ApiModelProperty(value = "主题") private String subject; @NotBlank + @ApiModelProperty(value = "内容") private String content; } diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/vo/TradeVo.java b/eladmin-tools/src/main/java/me/zhengjie/domain/vo/TradeVo.java index 6927dafe2..b8f5e6c83 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/vo/TradeVo.java +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/vo/TradeVo.java @@ -1,8 +1,22 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.domain.vo; import io.swagger.annotations.ApiModelProperty; import lombok.Data; - import javax.validation.constraints.NotBlank; import java.sql.Date; import java.sql.Timestamp; @@ -15,51 +29,30 @@ @Data public class TradeVo { - /** - * (必填)商品描述 - */ @NotBlank + @ApiModelProperty(value = "商品描述") private String body; - /** - * (必填)商品名称 - */ @NotBlank + @ApiModelProperty(value = "商品名称") private String subject; - /** - * (必填)商户订单号,应该由后台生成 - */ - @ApiModelProperty(hidden = true) + @ApiModelProperty(value = "商户订单号", hidden = true) private String outTradeNo; - /** - * (必填)第三方订单号 - */ - @ApiModelProperty(hidden = true) + @ApiModelProperty(value = "第三方订单号", hidden = true) private String tradeNo; - /** - * (必填)价格 - */ @NotBlank + @ApiModelProperty(value = "价格") private String totalAmount; - /** - * 订单状态,已支付,未支付,作废 - */ - @ApiModelProperty(hidden = true) + @ApiModelProperty(value = "订单状态,已支付,未支付,作废", hidden = true) private String state; - /** - * 创建时间,存入数据库时需要 - */ - @ApiModelProperty(hidden = true) + @ApiModelProperty(value = "创建时间", hidden = true) private Timestamp createTime; - /** - * 作废时间,存入数据库时需要 - */ - @ApiModelProperty(hidden = true) + @ApiModelProperty(value = "作废时间", hidden = true) private Date cancelTime; } diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/AliPayRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/AliPayRepository.java new file mode 100644 index 000000000..4d8e36f4c --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/repository/AliPayRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.repository; + +import me.zhengjie.domain.AlipayConfig; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author Zheng Jie + * @date 2018-12-31 + */ +public interface AliPayRepository extends JpaRepository { +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/AlipayRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/AlipayRepository.java deleted file mode 100644 index fe1c00806..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/AlipayRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package me.zhengjie.repository; - -import me.zhengjie.domain.AlipayConfig; -import org.springframework.data.jpa.repository.JpaRepository; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -public interface AlipayRepository extends JpaRepository { -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/EmailRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/EmailRepository.java index d5fb22fcf..ba8d1da2e 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/EmailRepository.java +++ b/eladmin-tools/src/main/java/me/zhengjie/repository/EmailRepository.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.repository; import me.zhengjie.domain.EmailConfig; diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/LocalStorageRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/LocalStorageRepository.java new file mode 100644 index 000000000..8978b4fba --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/repository/LocalStorageRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.repository; + +import me.zhengjie.domain.LocalStorage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +public interface LocalStorageRepository extends JpaRepository, JpaSpecificationExecutor { +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/PictureRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/PictureRepository.java deleted file mode 100644 index 27ab03513..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/PictureRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.zhengjie.repository; - -import me.zhengjie.domain.Picture; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -/** - * @author Zheng Jie - * @date 2018-12-27 - */ -public interface PictureRepository extends JpaRepository, JpaSpecificationExecutor { -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/QiNiuConfigRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/QiNiuConfigRepository.java deleted file mode 100644 index c238e468a..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/QiNiuConfigRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package me.zhengjie.repository; - -import me.zhengjie.domain.QiniuConfig; -import org.springframework.data.jpa.repository.JpaRepository; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -public interface QiNiuConfigRepository extends JpaRepository { -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/QiniuContentRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/QiniuContentRepository.java deleted file mode 100644 index 1bd887e6f..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/QiniuContentRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.zhengjie.repository; - -import me.zhengjie.domain.QiniuContent; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -public interface QiniuContentRepository extends JpaRepository, JpaSpecificationExecutor { - - /** - * 根据key查询 - * @param key - * @return - */ - QiniuContent findByKey(String key); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java new file mode 100644 index 000000000..9ad9f2a84 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.repository; + +import me.zhengjie.domain.S3Storage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; + +/** +* @author Zheng Jie +* @date 2025-06-25 +*/ +public interface S3StorageRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据ID查询文件路径 + * @param id 文件ID + * @return 文件路径 + */ + @Query(value = "SELECT file_path FROM s3_storage WHERE id = ?1", nativeQuery = true) + String selectFilePathById(Long id); +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/VerificationCodeRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/VerificationCodeRepository.java deleted file mode 100644 index 9085d9289..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/VerificationCodeRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.zhengjie.repository; - -import me.zhengjie.domain.VerificationCode; -import org.springframework.data.jpa.repository.JpaRepository; - -/** - * @author Zheng Jie - * @date 2018-12-26 - */ -public interface VerificationCodeRepository extends JpaRepository { - - /** - * 获取有效的验证码 - * @param scenes 业务场景,如重置密码,重置邮箱等等 - * @param type - * @param value - * @return - */ - VerificationCode findByScenesAndTypeAndValueAndStatusIsTrue(String scenes, String type, String value); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java index 681a07c7c..7a065f191 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java +++ b/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java @@ -1,21 +1,41 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.rest; +import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import me.zhengjie.aop.log.Log; -import me.zhengjie.domain.AlipayConfig; +import me.zhengjie.annotation.rest.AnonymousAccess; +import me.zhengjie.annotation.Log; +import me.zhengjie.annotation.rest.AnonymousGetMapping; import me.zhengjie.domain.vo.TradeVo; -import me.zhengjie.service.AlipayService; -import me.zhengjie.utils.AliPayStatusEnum; +import me.zhengjie.domain.AlipayConfig; +import me.zhengjie.domain.enums.AliPayStatusEnum; import me.zhengjie.utils.AlipayUtils; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.service.AliPayService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import springfox.documentation.annotations.ApiIgnore; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; import java.util.Map; /** @@ -24,100 +44,92 @@ */ @Slf4j @RestController -@RequestMapping("/api") +@RequiredArgsConstructor +@RequestMapping("/api/aliPay") +@Api(tags = "工具:支付宝管理") public class AliPayController { - @Autowired - AlipayUtils alipayUtils; - - @Autowired - private AlipayService alipayService; + private final AlipayUtils alipayUtils; + private final AliPayService alipayService; - @GetMapping(value = "/aliPay") - public ResponseEntity get(){ - return new ResponseEntity<>(alipayService.find(),HttpStatus.OK); + @GetMapping + public ResponseEntity queryAliConfig() { + return new ResponseEntity<>(alipayService.find(), HttpStatus.OK); } @Log("配置支付宝") - @PutMapping(value = "/aliPay") - public ResponseEntity payConfig(@Validated @RequestBody AlipayConfig alipayConfig){ - alipayConfig.setId(1L); - alipayService.update(alipayConfig); - return new ResponseEntity(HttpStatus.OK); + @ApiOperation("配置支付宝") + @PutMapping + public ResponseEntity updateAliPayConfig(@Validated @RequestBody AlipayConfig alipayConfig) { + alipayService.config(alipayConfig); + return new ResponseEntity<>(HttpStatus.OK); } @Log("支付宝PC网页支付") - @ApiOperation(value = "PC网页支付") - @PostMapping(value = "/aliPay/toPayAsPC") - public ResponseEntity toPayAsPC(@Validated@RequestBody TradeVo trade) throws Exception{ - AlipayConfig alipay = alipayService.find(); + @ApiOperation("PC网页支付") + @PostMapping(value = "/toPayAsPC") + public ResponseEntity toPayAsPc(@Validated @RequestBody TradeVo trade) throws Exception { + AlipayConfig aliPay = alipayService.find(); trade.setOutTradeNo(alipayUtils.getOrderCode()); - String payUrl = alipayService.toPayAsPC(alipay,trade); + String payUrl = alipayService.toPayAsPc(aliPay, trade); return ResponseEntity.ok(payUrl); } @Log("支付宝手机网页支付") - @ApiOperation(value = "手机网页支付") - @PostMapping(value = "/aliPay/toPayAsWeb") - public ResponseEntity toPayAsWeb(@Validated @RequestBody TradeVo trade) throws Exception{ + @ApiOperation("手机网页支付") + @PostMapping(value = "/toPayAsWeb") + public ResponseEntity toPayAsWeb(@Validated @RequestBody TradeVo trade) throws Exception { AlipayConfig alipay = alipayService.find(); trade.setOutTradeNo(alipayUtils.getOrderCode()); - String payUrl = alipayService.toPayAsWeb(alipay,trade); + String payUrl = alipayService.toPayAsWeb(alipay, trade); return ResponseEntity.ok(payUrl); } @ApiIgnore - @GetMapping("/aliPay/return") - @ApiOperation(value = "支付之后跳转的链接") - public ResponseEntity returnPage(HttpServletRequest request, HttpServletResponse response) throws Exception { + @AnonymousGetMapping("/return") + @ApiOperation("支付之后跳转的链接") + public ResponseEntity returnPage(HttpServletRequest request, HttpServletResponse response) { AlipayConfig alipay = alipayService.find(); response.setContentType("text/html;charset=" + alipay.getCharset()); //内容验签,防止黑客篡改参数 - if(alipayUtils.rsaCheck(request,alipay)){ + if (alipayUtils.rsaCheck(request, alipay)) { //商户订单号 - String outTradeNo = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8"); + String outTradeNo = new String(request.getParameter("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); //支付宝交易号 - String tradeNo = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8"); - System.out.println("商户订单号"+outTradeNo+" "+"第三方交易号"+tradeNo); + String tradeNo = new String(request.getParameter("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); + System.out.println("商户订单号" + outTradeNo + " " + "第三方交易号" + tradeNo); - /** - * 根据业务需要返回数据,这里统一返回OK - */ - return new ResponseEntity<>("payment successful",HttpStatus.OK); - }else{ - /** - * 根据业务需要返回数据 - */ + // 根据业务需要返回数据,这里统一返回OK + return new ResponseEntity<>("payment successful", HttpStatus.OK); + } else { + // 根据业务需要返回数据 return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } } @ApiIgnore - @RequestMapping("/aliPay/notify") - @ApiOperation(value = "支付异步通知(要公网访问),接收异步通知,检查通知内容app_id、out_trade_no、total_amount是否与请求中的一致,根据trade_status进行后续业务处理") - public ResponseEntity notify(HttpServletRequest request) throws Exception{ + @RequestMapping("/notify") + @AnonymousAccess + @ApiOperation("支付异步通知(要公网访问),接收异步通知,检查通知内容app_id、out_trade_no、total_amount是否与请求中的一致,根据trade_status进行后续业务处理") + public ResponseEntity notify(HttpServletRequest request) { AlipayConfig alipay = alipayService.find(); Map parameterMap = request.getParameterMap(); - StringBuilder notifyBuild = new StringBuilder("/****************************** pay notify ******************************/\n"); - parameterMap.forEach((key, value) -> notifyBuild.append(key + "=" + value[0] + "\n") ); //内容验签,防止黑客篡改参数 - if (alipayUtils.rsaCheck(request,alipay)) { + if (alipayUtils.rsaCheck(request, alipay)) { //交易状态 - String tradeStatus = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8"); + String tradeStatus = new String(request.getParameter("trade_status").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); // 商户订单号 - String outTradeNo = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8"); + String outTradeNo = new String(request.getParameter("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); //支付宝交易号 - String tradeNo = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8"); + String tradeNo = new String(request.getParameter("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); //付款金额 - String totalAmount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8"); + String totalAmount = new String(request.getParameter("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); //验证 - if(tradeStatus.equals(AliPayStatusEnum.SUCCESS.getValue())||tradeStatus.equals(AliPayStatusEnum.FINISHED.getValue())){ - /** - * 验证通过后应该根据业务需要处理订单 - */ + if (tradeStatus.equals(AliPayStatusEnum.SUCCESS.getValue()) || tradeStatus.equals(AliPayStatusEnum.FINISHED.getValue())) { + // 验证通过后应该根据业务需要处理订单 } - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } - return new ResponseEntity(HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } } diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/EmailController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/EmailController.java index 4efcf4c83..9ce5258c0 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/EmailController.java +++ b/eladmin-tools/src/main/java/me/zhengjie/rest/EmailController.java @@ -1,11 +1,27 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.rest; -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.aop.log.Log; -import me.zhengjie.domain.EmailConfig; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; import me.zhengjie.domain.vo.EmailVo; +import me.zhengjie.domain.EmailConfig; import me.zhengjie.service.EmailService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -16,31 +32,32 @@ * @author 郑杰 * @date 2018/09/28 6:55:53 */ -@Slf4j @RestController -@RequestMapping("api") +@RequiredArgsConstructor +@RequestMapping("api/email") +@Api(tags = "工具:邮件管理") public class EmailController { - @Autowired - private EmailService emailService; + private final EmailService emailService; - @GetMapping(value = "/email") - public ResponseEntity get(){ - return new ResponseEntity(emailService.find(),HttpStatus.OK); + @GetMapping + public ResponseEntity queryEmailConfig(){ + return new ResponseEntity<>(emailService.find(),HttpStatus.OK); } @Log("配置邮件") - @PutMapping(value = "/email") - public ResponseEntity emailConfig(@Validated @RequestBody EmailConfig emailConfig){ - emailService.update(emailConfig,emailService.find()); - return new ResponseEntity(HttpStatus.OK); + @PutMapping + @ApiOperation("配置邮件") + public ResponseEntity updateEmailConfig(@Validated @RequestBody EmailConfig emailConfig) throws Exception { + emailService.config(emailConfig,emailService.find()); + return new ResponseEntity<>(HttpStatus.OK); } @Log("发送邮件") - @PostMapping(value = "/email") - public ResponseEntity send(@Validated @RequestBody EmailVo emailVo) throws Exception { - log.warn("REST request to send Email : {}" +emailVo); + @PostMapping + @ApiOperation("发送邮件") + public ResponseEntity sendEmail(@Validated @RequestBody EmailVo emailVo){ emailService.send(emailVo,emailService.find()); - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } } diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/LocalStorageController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/LocalStorageController.java new file mode 100644 index 000000000..beb0ec3c0 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/rest/LocalStorageController.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.rest; + +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import me.zhengjie.domain.LocalStorage; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.service.LocalStorageService; +import me.zhengjie.service.dto.LocalStorageDto; +import me.zhengjie.service.dto.LocalStorageQueryCriteria; +import me.zhengjie.utils.FileUtil; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import io.swagger.annotations.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +@RestController +@RequiredArgsConstructor +@Api(tags = "工具:本地存储管理") +@RequestMapping("/api/localStorage") +public class LocalStorageController { + + private final LocalStorageService localStorageService; + + @GetMapping + @ApiOperation("查询文件") + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryFile(LocalStorageQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(localStorageService.queryAll(criteria,pageable),HttpStatus.OK); + } + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportFile(HttpServletResponse response, LocalStorageQueryCriteria criteria) throws IOException { + localStorageService.download(localStorageService.queryAll(criteria), response); + } + + @PostMapping + @ApiOperation("上传文件") + @PreAuthorize("@el.check('storage:add')") + public ResponseEntity createFile(@RequestParam String name, @RequestParam("file") MultipartFile file){ + localStorageService.create(name, file); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @ApiOperation("上传图片") + @PostMapping("/pictures") + public ResponseEntity uploadPicture(@RequestParam MultipartFile file){ + // 判断文件是否为图片 + String suffix = FileUtil.getExtensionName(file.getOriginalFilename()); + if(!FileUtil.IMAGE.equals(FileUtil.getFileType(suffix))){ + throw new BadRequestException("只能上传图片"); + } + LocalStorage localStorage = localStorageService.create(null, file); + return new ResponseEntity<>(localStorage, HttpStatus.OK); + } + + @PutMapping + @Log("修改文件") + @ApiOperation("修改文件") + @PreAuthorize("@el.check('storage:edit')") + public ResponseEntity updateFile(@Validated @RequestBody LocalStorage resources){ + localStorageService.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Log("删除文件") + @DeleteMapping + @ApiOperation("多选删除") + public ResponseEntity deleteFile(@RequestBody Long[] ids) { + localStorageService.deleteAll(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/PictureController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/PictureController.java deleted file mode 100644 index 7e63a9ab1..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/PictureController.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.zhengjie.rest; - -import me.zhengjie.aop.log.Log; -import me.zhengjie.domain.Picture; -import me.zhengjie.service.PictureService; -import me.zhengjie.service.dto.PictureQueryCriteria; -import me.zhengjie.utils.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import java.util.HashMap; -import java.util.Map; - -/** - * @author 郑杰 - * @date 2018/09/20 14:13:32 - */ -@RestController -@RequestMapping("/api") -public class PictureController { - - @Autowired - private PictureService pictureService; - - @Log("查询图片") - @PreAuthorize("hasAnyRole('ADMIN','PICTURE_ALL','PICTURE_SELECT')") - @GetMapping(value = "/pictures") - public ResponseEntity getRoles(PictureQueryCriteria criteria, Pageable pageable){ - return new ResponseEntity(pictureService.queryAll(criteria,pageable),HttpStatus.OK); - } - - /** - * 上传图片 - * @param file - * @return - * @throws Exception - */ - @Log("上传图片") - @PreAuthorize("hasAnyRole('ADMIN','PICTURE_ALL','PICTURE_UPLOAD')") - @PostMapping(value = "/pictures") - public ResponseEntity upload(@RequestParam MultipartFile file){ - String userName = SecurityUtils.getUsername(); - Picture picture = pictureService.upload(file,userName); - Map map = new HashMap(3); - map.put("errno",0); - map.put("id",picture.getId()); - map.put("data",new String[]{picture.getUrl()}); - return new ResponseEntity(map,HttpStatus.OK); - } - - /** - * 删除图片 - * @param id - * @return - */ - @Log("删除图片") - @PreAuthorize("hasAnyRole('ADMIN','PICTURE_ALL','PICTURE_DELETE')") - @DeleteMapping(value = "/pictures/{id}") - public ResponseEntity delete(@PathVariable Long id) { - pictureService.delete(pictureService.findById(id)); - return new ResponseEntity(HttpStatus.OK); - } - - /** - * 删除多张图片 - * @param ids - * @return - */ - @Log("删除图片") - @PreAuthorize("hasAnyRole('ADMIN','PICTURE_ALL','PICTURE_DELETE')") - @DeleteMapping(value = "/pictures") - public ResponseEntity deleteAll(@RequestBody Long[] ids) { - pictureService.deleteAll(ids); - return new ResponseEntity(HttpStatus.OK); - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/QiniuController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/QiniuController.java deleted file mode 100644 index 99367ad90..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/QiniuController.java +++ /dev/null @@ -1,116 +0,0 @@ -package me.zhengjie.rest; - -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.aop.log.Log; -import me.zhengjie.domain.QiniuConfig; -import me.zhengjie.domain.QiniuContent; -import me.zhengjie.service.QiNiuService; -import me.zhengjie.service.dto.QiniuQueryCriteria; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import java.util.HashMap; -import java.util.Map; - -/** - * 发送邮件 - * @author 郑杰 - * @date 2018/09/28 6:55:53 - */ -@Slf4j -@RestController -@RequestMapping("api") -public class QiniuController { - - @Autowired - private QiNiuService qiNiuService; - - @GetMapping(value = "/qiNiuConfig") - public ResponseEntity get(){ - return new ResponseEntity(qiNiuService.find(), HttpStatus.OK); - } - - @Log("配置七牛云存储") - @PutMapping(value = "/qiNiuConfig") - public ResponseEntity emailConfig(@Validated @RequestBody QiniuConfig qiniuConfig){ - qiNiuService.update(qiniuConfig); - return new ResponseEntity(HttpStatus.OK); - } - - @Log("查询文件") - @GetMapping(value = "/qiNiuContent") - public ResponseEntity getRoles(QiniuQueryCriteria criteria, Pageable pageable){ - return new ResponseEntity(qiNiuService.queryAll(criteria,pageable),HttpStatus.OK); - } - - /** - * 上传文件到七牛云 - * @param file - * @return - */ - @Log("上传文件") - @PostMapping(value = "/qiNiuContent") - public ResponseEntity upload(@RequestParam MultipartFile file){ - QiniuContent qiniuContent = qiNiuService.upload(file,qiNiuService.find()); - Map map = new HashMap(3); - map.put("id",qiniuContent.getId()); - map.put("errno",0); - map.put("data",new String[]{qiniuContent.getUrl()}); - return new ResponseEntity(map,HttpStatus.OK); - } - - /** - * 同步七牛云数据到数据库 - * @return - */ - @Log("同步七牛云数据") - @PostMapping(value = "/qiNiuContent/synchronize") - public ResponseEntity synchronize(){ - log.warn("REST request to synchronize qiNiu : {}"); - qiNiuService.synchronize(qiNiuService.find()); - return new ResponseEntity(HttpStatus.OK); - } - - /** - * 下载七牛云文件 - * @param id - * @return - * @throws Exception - */ - @Log("下载文件") - @GetMapping(value = "/qiNiuContent/download/{id}") - public ResponseEntity download(@PathVariable Long id){ - Map map = new HashMap(1); - map.put("url", qiNiuService.download(qiNiuService.findByContentId(id),qiNiuService.find())); - return new ResponseEntity(map,HttpStatus.OK); - } - - /** - * 删除七牛云文件 - * @param id - * @return - * @throws Exception - */ - @Log("删除文件") - @DeleteMapping(value = "/qiNiuContent/{id}") - public ResponseEntity delete(@PathVariable Long id){ - qiNiuService.delete(qiNiuService.findByContentId(id),qiNiuService.find()); - return new ResponseEntity(HttpStatus.OK); - } - - /** - * 删除多张图片 - * @param ids - * @return - */ - @Log("删除图片") - @DeleteMapping(value = "/qiNiuContent") - public ResponseEntity deleteAll(@RequestBody Long[] ids) { - qiNiuService.deleteAll(ids, qiNiuService.find()); - return new ResponseEntity(HttpStatus.OK); - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java new file mode 100644 index 000000000..964976e19 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.Log; +import me.zhengjie.config.AmzS3Config; +import me.zhengjie.domain.S3Storage; +import me.zhengjie.service.S3StorageService; +import me.zhengjie.service.dto.S3StorageQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * amz S3 协议云存储管理 + * @author 郑杰 + * @date 2025-06-25 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/s3Storage") +@Api(tags = "工具:S3协议云存储管理") +public class S3StorageController { + + private final AmzS3Config amzS3Config; + private final S3StorageService s3StorageService; + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportS3Storage(HttpServletResponse response, S3StorageQueryCriteria criteria) throws IOException { + s3StorageService.download(s3StorageService.queryAll(criteria), response); + } + + @GetMapping + @ApiOperation("查询文件") + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryS3Storage(S3StorageQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(s3StorageService.queryAll(criteria, pageable),HttpStatus.OK); + } + + @PostMapping + @ApiOperation("上传文件") + public ResponseEntity uploadS3Storage(@RequestParam MultipartFile file){ + S3Storage storage = s3StorageService.upload(file); + Map map = new HashMap<>(3); + map.put("id",storage.getId()); + map.put("errno",0); + map.put("data",new String[]{amzS3Config.getDomain() + "/" + storage.getFilePath()}); + return new ResponseEntity<>(map,HttpStatus.OK); + } + + @Log("下载文件") + @ApiOperation("下载文件") + @GetMapping(value = "/download/{id}") + public ResponseEntity downloadS3Storage(@PathVariable Long id){ + Map map = new HashMap<>(1); + S3Storage storage = s3StorageService.getById(id); + if (storage == null) { + map.put("message", "文件不存在或已被删除"); + return new ResponseEntity<>(map, HttpStatus.NOT_FOUND); + } + // 仅适合公开文件访问,私有文件可以使用服务中的 privateDownload 方法 + String url = amzS3Config.getDomain() + "/" + storage.getFilePath(); + map.put("url", url); + return new ResponseEntity<>(map,HttpStatus.OK); + } + + @Log("删除多个文件") + @DeleteMapping + @ApiOperation("删除多个文件") + @PreAuthorize("@el.check('storage:del')") + public ResponseEntity deleteAllS3Storage(@RequestBody List ids) { + s3StorageService.deleteAll(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/VerificationCodeController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/VerificationCodeController.java deleted file mode 100644 index 557631add..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/VerificationCodeController.java +++ /dev/null @@ -1,57 +0,0 @@ -package me.zhengjie.rest; - -import me.zhengjie.domain.VerificationCode; -import me.zhengjie.domain.vo.EmailVo; -import me.zhengjie.service.EmailService; -import me.zhengjie.service.VerificationCodeService; -import me.zhengjie.utils.ElAdminConstant; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.web.bind.annotation.*; - -/** - * @author Zheng Jie - * @date 2018-12-26 - */ -@RestController -@RequestMapping("api") -public class VerificationCodeController { - - @Autowired - private VerificationCodeService verificationCodeService; - - @Autowired - @Qualifier("jwtUserDetailsService") - private UserDetailsService userDetailsService; - - @Autowired - private EmailService emailService; - - @PostMapping(value = "/code/resetEmail") - public ResponseEntity resetEmail(@RequestBody VerificationCode code) throws Exception { - code.setScenes(ElAdminConstant.RESET_MAIL); - EmailVo emailVo = verificationCodeService.sendEmail(code); - emailService.send(emailVo,emailService.find()); - return new ResponseEntity(HttpStatus.OK); - } - - @PostMapping(value = "/code/email/resetPass") - public ResponseEntity resetPass(@RequestParam String email) throws Exception { - VerificationCode code = new VerificationCode(); - code.setType("email"); - code.setValue(email); - code.setScenes(ElAdminConstant.RESET_MAIL); - EmailVo emailVo = verificationCodeService.sendEmail(code); - emailService.send(emailVo,emailService.find()); - return new ResponseEntity(HttpStatus.OK); - } - - @GetMapping(value = "/code/validated") - public ResponseEntity validated(VerificationCode code){ - verificationCodeService.validated(code); - return new ResponseEntity(HttpStatus.OK); - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/AliPayService.java b/eladmin-tools/src/main/java/me/zhengjie/service/AliPayService.java new file mode 100644 index 000000000..51e8951c9 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/AliPayService.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service; + +import me.zhengjie.domain.vo.TradeVo; +import me.zhengjie.domain.AlipayConfig; + +/** + * @author Zheng Jie + * @date 2018-12-31 + */ +public interface AliPayService { + + /** + * 查询配置 + * @return AlipayConfig + */ + AlipayConfig find(); + + /** + * 更新配置 + * @param alipayConfig 支付宝配置 + * @return AlipayConfig + */ + AlipayConfig config(AlipayConfig alipayConfig); + + /** + * 处理来自PC的交易请求 + * @param alipay 支付宝配置 + * @param trade 交易详情 + * @return String + * @throws Exception 异常 + */ + String toPayAsPc(AlipayConfig alipay, TradeVo trade) throws Exception; + + /** + * 处理来自手机网页的交易请求 + * @param alipay 支付宝配置 + * @param trade 交易详情 + * @return String + * @throws Exception 异常 + */ + String toPayAsWeb(AlipayConfig alipay, TradeVo trade) throws Exception; +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/AlipayService.java b/eladmin-tools/src/main/java/me/zhengjie/service/AlipayService.java deleted file mode 100644 index f4e094231..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/AlipayService.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.zhengjie.service; - -import me.zhengjie.domain.AlipayConfig; -import me.zhengjie.domain.vo.TradeVo; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -@CacheConfig(cacheNames = "alipay") -public interface AlipayService { - - /** - * 处理来自PC的交易请求 - * @param alipay - * @param trade - * @return - * @throws Exception - */ - String toPayAsPC(AlipayConfig alipay, TradeVo trade) throws Exception; - - /** - * 处理来自手机网页的交易请求 - * @param alipay - * @param trade - * @return - * @throws Exception - */ - String toPayAsWeb(AlipayConfig alipay, TradeVo trade) throws Exception; - - /** - * 查询配置 - * @return - */ - @Cacheable(key = "'1'") - AlipayConfig find(); - - /** - * 更新配置 - * @param alipayConfig - * @return - */ - @CachePut(key = "'1'") - AlipayConfig update(AlipayConfig alipayConfig); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/EmailService.java b/eladmin-tools/src/main/java/me/zhengjie/service/EmailService.java index deae90c1a..d055eed08 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/service/EmailService.java +++ b/eladmin-tools/src/main/java/me/zhengjie/service/EmailService.java @@ -1,41 +1,48 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service; -import me.zhengjie.domain.EmailConfig; import me.zhengjie.domain.vo.EmailVo; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.scheduling.annotation.Async; +import me.zhengjie.domain.EmailConfig; /** * @author Zheng Jie * @date 2018-12-26 */ -@CacheConfig(cacheNames = "email") public interface EmailService { /** * 更新邮件配置 - * @param emailConfig - * @param old - * @return + * @param emailConfig 邮箱配置 + * @param old / + * @return / + * @throws Exception / */ - @CachePut(key = "'1'") - EmailConfig update(EmailConfig emailConfig, EmailConfig old); + EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception; /** * 查询配置 - * @return + * @return EmailConfig 邮件配置 */ - @Cacheable(key = "'1'") EmailConfig find(); /** * 发送邮件 - * @param emailVo - * @param emailConfig - * @throws Exception + * @param emailVo 邮件发送的内容 + * @param emailConfig 邮件配置 */ - @Async - void send(EmailVo emailVo, EmailConfig emailConfig) throws Exception; + void send(EmailVo emailVo, EmailConfig emailConfig); } diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/LocalStorageService.java b/eladmin-tools/src/main/java/me/zhengjie/service/LocalStorageService.java new file mode 100644 index 000000000..08e722705 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/LocalStorageService.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service; + +import me.zhengjie.domain.LocalStorage; +import me.zhengjie.service.dto.LocalStorageDto; +import me.zhengjie.service.dto.LocalStorageQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +public interface LocalStorageService { + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + PageResult queryAll(LocalStorageQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria 条件 + * @return / + */ + List queryAll(LocalStorageQueryCriteria criteria); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + LocalStorageDto findById(Long id); + + /** + * 上传 + * @param name 文件名称 + * @param file 文件 + * @return / + */ + LocalStorage create(String name, MultipartFile file); + + /** + * 编辑 + * @param resources 文件信息 + */ + void update(LocalStorage resources); + + /** + * 多选删除 + * @param ids / + */ + void deleteAll(Long[] ids); + + /** + * 导出数据 + * @param localStorageDtos 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List localStorageDtos, HttpServletResponse response) throws IOException; +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/PictureService.java b/eladmin-tools/src/main/java/me/zhengjie/service/PictureService.java deleted file mode 100644 index 27091829c..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/PictureService.java +++ /dev/null @@ -1,56 +0,0 @@ -package me.zhengjie.service; - -import me.zhengjie.domain.Picture; -import me.zhengjie.service.dto.PictureQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.Pageable; -import org.springframework.web.multipart.MultipartFile; -/** - * @author Zheng Jie - * @date 2018-12-27 - */ -@CacheConfig(cacheNames = "picture") -public interface PictureService { - - /** - * 查询图片 - * @param criteria - * @param pageable - * @return - */ - @Cacheable(keyGenerator = "keyGenerator") - Object queryAll(PictureQueryCriteria criteria, Pageable pageable); - - /** - * 上传图片 - * @param file - * @param username - * @return - */ - @CacheEvict(allEntries = true) - Picture upload(MultipartFile file, String username); - - /** - * 根据ID查询 - * @param id - * @return - */ - @Cacheable(key = "#p0") - Picture findById(Long id); - - /** - * 删除图片 - * @param picture - */ - @CacheEvict(allEntries = true) - void delete(Picture picture); - - /** - * 删除图片 - * @param ids - */ - @CacheEvict(allEntries = true) - void deleteAll(Long[] ids); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/QiNiuService.java b/eladmin-tools/src/main/java/me/zhengjie/service/QiNiuService.java deleted file mode 100644 index 99a9a2af2..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/QiNiuService.java +++ /dev/null @@ -1,92 +0,0 @@ -package me.zhengjie.service; - -import me.zhengjie.domain.QiniuConfig; -import me.zhengjie.domain.QiniuContent; -import me.zhengjie.service.dto.QiniuQueryCriteria; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.Pageable; -import org.springframework.web.multipart.MultipartFile; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -@CacheConfig(cacheNames = "qiNiu") -public interface QiNiuService { - - /** - * 查询文件 - * @param criteria - * @param pageable - * @return - */ - @Cacheable(keyGenerator = "keyGenerator") - Object queryAll(QiniuQueryCriteria criteria, Pageable pageable); - - /** - * 查配置 - * @return - */ - @Cacheable(cacheNames = "qiNiuConfig", key = "'1'") - QiniuConfig find(); - - /** - * 修改配置 - * @param qiniuConfig - * @return - */ - @CachePut(cacheNames = "qiNiuConfig", key = "'1'") - QiniuConfig update(QiniuConfig qiniuConfig); - - /** - * 上传文件 - * @param file - * @param qiniuConfig - * @return - */ - @CacheEvict(allEntries = true) - QiniuContent upload(MultipartFile file, QiniuConfig qiniuConfig); - - /** - * 查询文件 - * @param id - * @return - */ - @Cacheable(key = "'content:'+#p0") - QiniuContent findByContentId(Long id); - - /** - * 下载文件 - * @param content - * @param config - * @return - */ - String download(QiniuContent content, QiniuConfig config); - - /** - * 删除文件 - * @param content - * @param config - * @return - */ - @CacheEvict(allEntries = true) - void delete(QiniuContent content, QiniuConfig config); - - /** - * 同步数据 - * @param config - */ - @CacheEvict(allEntries = true) - void synchronize(QiniuConfig config); - - /** - * 删除文件 - * @param ids - * @param config - */ - @CacheEvict(allEntries = true) - void deleteAll(Long[] ids, QiniuConfig config); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java b/eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java new file mode 100644 index 000000000..1cb3d3eb9 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java @@ -0,0 +1,83 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package me.zhengjie.service; + +import me.zhengjie.domain.S3Storage; +import me.zhengjie.service.dto.S3StorageQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** +* @description 服务接口 +* @author Zheng Jie +* @date 2025-06-25 +**/ +public interface S3StorageService { + + /** + * 查询数据分页 + * @param criteria 条件 + * @param pageable 分页参数 + * @return PageResult + */ + PageResult queryAll(S3StorageQueryCriteria criteria, Pageable pageable); + + /** + * 查询所有数据不分页 + * @param criteria 条件参数 + * @return List + */ + List queryAll(S3StorageQueryCriteria criteria); + + /** + * 多选删除 + * @param ids / + */ + void deleteAll(List ids); + + /** + * 导出数据 + * @param all 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List all, HttpServletResponse response) throws IOException; + + /** + * 私有化下载,仅供参考,还有许多方式 + * @param id 文件ID + */ + Map privateDownload(Long id); + + /** + * 上传文件 + * @param file 上传的文件 + * @return S3Storage 对象,包含文件存储信息 + */ + S3Storage upload(MultipartFile file); + + /** + * 根据ID获取文件信息 + * @param id 文件ID + * @return S3Storage 对象,包含文件存储信息 + */ + S3Storage getById(Long id); +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/VerificationCodeService.java b/eladmin-tools/src/main/java/me/zhengjie/service/VerificationCodeService.java deleted file mode 100644 index eaf8f0ec5..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/VerificationCodeService.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.zhengjie.service; - -import me.zhengjie.domain.VerificationCode; -import me.zhengjie.domain.vo.EmailVo; - -/** - * @author Zheng Jie - * @date 2018-12-26 - */ -public interface VerificationCodeService { - - /** - * 发送邮件验证码 - * @param code - */ - EmailVo sendEmail(VerificationCode code); - - /** - * 验证 - * @param code - */ - void validated(VerificationCode code); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageDto.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageDto.java new file mode 100644 index 000000000..32642b3e6 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageDto.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import me.zhengjie.base.BaseDTO; +import java.io.Serializable; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +@Getter +@Setter +public class LocalStorageDto extends BaseDTO implements Serializable { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "真实文件名") + private String realName; + + @ApiModelProperty(value = "文件名") + private String name; + + @ApiModelProperty(value = "后缀") + private String suffix; + + @ApiModelProperty(value = "文件类型") + private String type; + + @ApiModelProperty(value = "文件大小") + private String size; +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageQueryCriteria.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageQueryCriteria.java new file mode 100644 index 000000000..ae939945e --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/dto/LocalStorageQueryCriteria.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.sql.Timestamp; +import java.util.List; + +import me.zhengjie.annotation.Query; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +@Data +public class LocalStorageQueryCriteria{ + + @ApiModelProperty(value = "模糊查询") + @Query(blurry = "name,suffix,type,createBy,size") + private String blurry; + + @ApiModelProperty(value = "创建时间") + @Query(type = Query.Type.BETWEEN) + private List createTime; +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/PictureQueryCriteria.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/PictureQueryCriteria.java deleted file mode 100644 index f2d2c6ef5..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/dto/PictureQueryCriteria.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.zhengjie.service.dto; - -import lombok.Data; -import me.zhengjie.annotation.Query; - -/** - * sm.ms图床 - * - * @author Zheng Jie - * @date 2019-6-4 09:52:09 - */ -@Data -public class PictureQueryCriteria{ - - @Query(type = Query.Type.INNER_LIKE) - private String filename; - - @Query(type = Query.Type.INNER_LIKE) - private String username; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/QiniuQueryCriteria.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/QiniuQueryCriteria.java deleted file mode 100644 index f3195651f..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/dto/QiniuQueryCriteria.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.zhengjie.service.dto; - -import lombok.Data; -import me.zhengjie.annotation.Query; - -/** - * @author Zheng Jie - * @date 2019-6-4 09:54:37 - */ -@Data -public class QiniuQueryCriteria{ - - @Query(type = Query.Type.INNER_LIKE) - private String key; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java new file mode 100644 index 000000000..539a1bdcc --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java @@ -0,0 +1,39 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author Zheng Jie +* @date 2025-06-25 +**/ +@Data +public class S3StorageQueryCriteria { + + @Query(type = Query.Type.INNER_LIKE) + @ApiModelProperty(value = "文件名称") + private String fileName; + + @Query(type = Query.Type.BETWEEN) + @ApiModelProperty(value = "创建时间") + private List createTime; + +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/AlipayServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/AliPayServiceImpl.java similarity index 63% rename from eladmin-tools/src/main/java/me/zhengjie/service/impl/AlipayServiceImpl.java rename to eladmin-tools/src/main/java/me/zhengjie/service/impl/AliPayServiceImpl.java index 63715b5aa..c3ac26074 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/service/impl/AlipayServiceImpl.java +++ b/eladmin-tools/src/main/java/me/zhengjie/service/impl/AliPayServiceImpl.java @@ -1,18 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service.impl; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.request.AlipayTradePagePayRequest; import com.alipay.api.request.AlipayTradeWapPayRequest; -import me.zhengjie.domain.AlipayConfig; +import lombok.RequiredArgsConstructor; import me.zhengjie.domain.vo.TradeVo; +import me.zhengjie.domain.AlipayConfig; import me.zhengjie.exception.BadRequestException; -import me.zhengjie.repository.AlipayRepository; -import me.zhengjie.service.AlipayService; -import me.zhengjie.utils.AlipayUtils; -import org.springframework.beans.factory.annotation.Autowired; +import me.zhengjie.repository.AliPayRepository; +import me.zhengjie.service.AliPayService; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -21,38 +37,42 @@ * @date 2018-12-31 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class AlipayServiceImpl implements AlipayService { +@RequiredArgsConstructor +@CacheConfig(cacheNames = "aliPay") +public class AliPayServiceImpl implements AliPayService { + + private final AliPayRepository alipayRepository; - @Autowired - AlipayUtils alipayUtils; + @Override + @Cacheable(key = "'config'") + public AlipayConfig find() { + Optional alipayConfig = alipayRepository.findById(1L); + return alipayConfig.orElseGet(AlipayConfig::new); + } - @Autowired - private AlipayRepository alipayRepository; + @Override + @CachePut(key = "'config'") + @Transactional(rollbackFor = Exception.class) + public AlipayConfig config(AlipayConfig alipayConfig) { + alipayConfig.setId(1L); + return alipayRepository.save(alipayConfig); + } @Override - public String toPayAsPC(AlipayConfig alipay, TradeVo trade) throws Exception { + public String toPayAsPc(AlipayConfig alipay, TradeVo trade) throws Exception { if(alipay.getId() == null){ throw new BadRequestException("请先添加相应配置,再操作"); } - AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppID(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType()); - - double money = Double.parseDouble(trade.getTotalAmount()); + AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppId(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType()); - /** - * 创建API对应的request(电脑网页版) - */ + // 创建API对应的request(电脑网页版) AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); - /** - * 订单完成后返回的页面和异步通知地址 - */ + // 订单完成后返回的页面和异步通知地址 request.setReturnUrl(alipay.getReturnUrl()); request.setNotifyUrl(alipay.getNotifyUrl()); - /** - * 填充订单参数 - */ + // 填充订单参数 request.setBizContent("{" + " \"out_trade_no\":\""+trade.getOutTradeNo()+"\"," + " \"product_code\":\"FAST_INSTANT_TRADE_PAY\"," + @@ -63,10 +83,7 @@ public String toPayAsPC(AlipayConfig alipay, TradeVo trade) throws Exception { " \"sys_service_provider_id\":\""+alipay.getSysServiceProviderId()+"\"" + " }"+ " }");//填充业务参数 - /** - * 调用SDK生成表单 - * 通过GET方式,口可以获取url - */ + // 调用SDK生成表单, 通过GET方式,口可以获取url return alipayClient.pageExecute(request, "GET").getBody(); } @@ -76,26 +93,17 @@ public String toPayAsWeb(AlipayConfig alipay, TradeVo trade) throws Exception { if(alipay.getId() == null){ throw new BadRequestException("请先添加相应配置,再操作"); } - AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppID(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType()); + AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppId(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType()); double money = Double.parseDouble(trade.getTotalAmount()); - if(money <= 0 || money >= 5000){ + double maxMoney = 5000; + if(money <= 0 || money >= maxMoney){ throw new BadRequestException("测试金额过大"); } - - /** - * 创建API对应的request(手机网页版) - */ + // 创建API对应的request(手机网页版) AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); - - /** - * 订单完成后返回的页面和异步通知地址 - */ request.setReturnUrl(alipay.getReturnUrl()); request.setNotifyUrl(alipay.getNotifyUrl()); - /** - * 填充订单参数 - */ request.setBizContent("{" + " \"out_trade_no\":\""+trade.getOutTradeNo()+"\"," + " \"product_code\":\"FAST_INSTANT_TRADE_PAY\"," + @@ -105,27 +113,7 @@ public String toPayAsWeb(AlipayConfig alipay, TradeVo trade) throws Exception { " \"extend_params\":{" + " \"sys_service_provider_id\":\""+alipay.getSysServiceProviderId()+"\"" + " }"+ - " }");//填充业务参数 - /** - * 调用SDK生成表单 - * 通过GET方式,口可以获取url - */ + " }"); return alipayClient.pageExecute(request, "GET").getBody(); } - - @Override - public AlipayConfig find() { - Optional alipayConfig = alipayRepository.findById(1L); - if (alipayConfig.isPresent()){ - return alipayConfig.get(); - } else { - return new AlipayConfig(); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public AlipayConfig update(AlipayConfig alipayConfig) { - return alipayRepository.save(alipayConfig); - } } diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/EmailServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/EmailServiceImpl.java index 862d7ec8a..0615e4e95 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/service/impl/EmailServiceImpl.java +++ b/eladmin-tools/src/main/java/me/zhengjie/service/impl/EmailServiceImpl.java @@ -1,18 +1,33 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.service.impl; import cn.hutool.extra.mail.Mail; import cn.hutool.extra.mail.MailAccount; -import cn.hutool.extra.mail.MailUtil; +import lombok.RequiredArgsConstructor; import me.zhengjie.domain.EmailConfig; import me.zhengjie.domain.vo.EmailVo; import me.zhengjie.exception.BadRequestException; import me.zhengjie.repository.EmailRepository; import me.zhengjie.service.EmailService; -import me.zhengjie.utils.ElAdminConstant; import me.zhengjie.utils.EncryptUtils; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -21,46 +36,42 @@ * @date 2018-12-26 */ @Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) +@RequiredArgsConstructor +@CacheConfig(cacheNames = "email") public class EmailServiceImpl implements EmailService { - @Autowired - private EmailRepository emailRepository; + private final EmailRepository emailRepository; @Override + @CachePut(key = "'config'") @Transactional(rollbackFor = Exception.class) - public EmailConfig update(EmailConfig emailConfig, EmailConfig old) { - try { - if(!emailConfig.getPass().equals(old.getPass())){ - // 对称加密 - emailConfig.setPass(EncryptUtils.desEncrypt(emailConfig.getPass())); - } - } catch (Exception e) { - e.printStackTrace(); + public EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception { + emailConfig.setId(1L); + if(!emailConfig.getPass().equals(old.getPass())){ + // 对称加密 + emailConfig.setPass(EncryptUtils.desEncrypt(emailConfig.getPass())); } return emailRepository.save(emailConfig); } @Override + @Cacheable(key = "'config'") public EmailConfig find() { Optional emailConfig = emailRepository.findById(1L); - if(emailConfig.isPresent()){ - return emailConfig.get(); - } else { - return new EmailConfig(); - } + return emailConfig.orElseGet(EmailConfig::new); } @Override @Transactional(rollbackFor = Exception.class) public void send(EmailVo emailVo, EmailConfig emailConfig){ - if(emailConfig == null){ + if(emailConfig.getId() == null){ throw new BadRequestException("请先配置,再操作"); } - /** - * 封装 - */ + // 封装 MailAccount account = new MailAccount(); + // 设置用户 + String user = emailConfig.getFromUser().split("@")[0]; + account.setUser(user); account.setHost(emailConfig.getHost()); account.setPort(Integer.parseInt(emailConfig.getPort())); account.setAuth(true); @@ -71,15 +82,18 @@ public void send(EmailVo emailVo, EmailConfig emailConfig){ throw new BadRequestException(e.getMessage()); } account.setFrom(emailConfig.getUser()+"<"+emailConfig.getFromUser()+">"); - //ssl方式发送 - account.setStartttlsEnable(true); + // ssl方式发送 + account.setSslEnable(true); + // 使用STARTTLS安全连接 + account.setStarttlsEnable(true); + // 解决jdk8之后默认禁用部分tls协议,导致邮件发送失败的问题 + account.setSslProtocols("TLSv1 TLSv1.1 TLSv1.2"); String content = emailVo.getContent(); - /** - * 发送 - */ + // 发送 try { + int size = emailVo.getTos().size(); Mail.create(account) - .setTos(emailVo.getTos().toArray(new String[emailVo.getTos().size()])) + .setTos(emailVo.getTos().toArray(new String[size])) .setTitle(emailVo.getSubject()) .setContent(content) .setHtml(true) diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/LocalStorageServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/LocalStorageServiceImpl.java new file mode 100644 index 000000000..5ef939d34 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/impl/LocalStorageServiceImpl.java @@ -0,0 +1,133 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; +import me.zhengjie.config.properties.FileProperties; +import me.zhengjie.domain.LocalStorage; +import me.zhengjie.service.dto.LocalStorageDto; +import me.zhengjie.service.dto.LocalStorageQueryCriteria; +import me.zhengjie.service.mapstruct.LocalStorageMapper; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.utils.*; +import me.zhengjie.repository.LocalStorageRepository; +import me.zhengjie.service.LocalStorageService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +@Service +@RequiredArgsConstructor +public class LocalStorageServiceImpl implements LocalStorageService { + + private final LocalStorageRepository localStorageRepository; + private final LocalStorageMapper localStorageMapper; + private final FileProperties properties; + + @Override + public PageResult queryAll(LocalStorageQueryCriteria criteria, Pageable pageable){ + Page page = localStorageRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(localStorageMapper::toDto)); + } + + @Override + public List queryAll(LocalStorageQueryCriteria criteria){ + return localStorageMapper.toDto(localStorageRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + } + + @Override + public LocalStorageDto findById(Long id){ + LocalStorage localStorage = localStorageRepository.findById(id).orElseGet(LocalStorage::new); + ValidationUtil.isNull(localStorage.getId(),"LocalStorage","id",id); + return localStorageMapper.toDto(localStorage); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public LocalStorage create(String name, MultipartFile multipartFile) { + FileUtil.checkSize(properties.getMaxSize(), multipartFile.getSize()); + String suffix = FileUtil.getExtensionName(multipartFile.getOriginalFilename()); + String type = FileUtil.getFileType(suffix); + File file = FileUtil.upload(multipartFile, properties.getPath().getPath() + type + File.separator); + if(ObjectUtil.isNull(file)){ + throw new BadRequestException("上传失败"); + } + try { + name = StringUtils.isBlank(name) ? FileUtil.getFileNameNoEx(multipartFile.getOriginalFilename()) : name; + LocalStorage localStorage = new LocalStorage( + file.getName(), + name, + suffix, + file.getPath(), + type, + FileUtil.getSize(multipartFile.getSize()) + ); + return localStorageRepository.save(localStorage); + }catch (Exception e){ + FileUtil.del(file); + throw e; + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(LocalStorage resources) { + LocalStorage localStorage = localStorageRepository.findById(resources.getId()).orElseGet(LocalStorage::new); + ValidationUtil.isNull( localStorage.getId(),"LocalStorage","id",resources.getId()); + localStorage.copy(resources); + localStorageRepository.save(localStorage); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteAll(Long[] ids) { + for (Long id : ids) { + LocalStorage storage = localStorageRepository.findById(id).orElseGet(LocalStorage::new); + FileUtil.del(storage.getPath()); + localStorageRepository.delete(storage); + } + } + + @Override + public void download(List queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (LocalStorageDto localStorageDTO : queryAll) { + Map map = new LinkedHashMap<>(); + map.put("文件名", localStorageDTO.getRealName()); + map.put("备注名", localStorageDTO.getName()); + map.put("文件类型", localStorageDTO.getType()); + map.put("文件大小", localStorageDTO.getSize()); + map.put("创建者", localStorageDTO.getCreateBy()); + map.put("创建日期", localStorageDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/PictureServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/PictureServiceImpl.java deleted file mode 100644 index f6aa81649..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/impl/PictureServiceImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -package me.zhengjie.service.impl; - -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.alibaba.fastjson.JSON; -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.domain.Picture; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.repository.PictureRepository; -import me.zhengjie.service.PictureService; -import me.zhengjie.service.dto.PictureQueryCriteria; -import me.zhengjie.utils.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.util.HashMap; -import java.util.Optional; - -/** - * @author Zheng Jie - * @date 2018-12-27 - */ -@Slf4j -@Service(value = "pictureService") -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class PictureServiceImpl implements PictureService { - - @Autowired - private PictureRepository pictureRepository; - - public static final String SUCCESS = "success"; - - public static final String CODE = "code"; - - public static final String MSG = "msg"; - - @Override - public Object queryAll(PictureQueryCriteria criteria, Pageable pageable){ - return PageUtil.toPage(pictureRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable)); - } - - @Override - @Transactional(rollbackFor = Throwable.class) - public Picture upload(MultipartFile multipartFile, String username) { - File file = FileUtil.toFile(multipartFile); - HashMap paramMap = new HashMap<>(1); - - paramMap.put("smfile", file); - String result= HttpUtil.post(ElAdminConstant.Url.SM_MS_URL, paramMap); - - JSONObject jsonObject = JSONUtil.parseObj(result); - Picture picture = null; - if(!jsonObject.get(CODE).toString().equals(SUCCESS)){ - throw new BadRequestException(jsonObject.get(MSG).toString()); - } - //转成实体类 - picture = JSON.parseObject(jsonObject.get("data").toString(), Picture.class); - picture.setSize(FileUtil.getSize(Integer.valueOf(picture.getSize()))); - picture.setUsername(username); - picture.setFilename(FileUtil.getFileNameNoEx(multipartFile.getOriginalFilename())+"."+FileUtil.getExtensionName(multipartFile.getOriginalFilename())); - pictureRepository.save(picture); - //删除临时文件 - FileUtil.del(file); - return picture; - - } - - @Override - public Picture findById(Long id) { - Optional picture = pictureRepository.findById(id); - ValidationUtil.isNull(picture,"Picture","id",id); - return picture.get(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void delete(Picture picture) { - try { - String result= HttpUtil.get(picture.getDelete()); - pictureRepository.delete(picture); - } catch(Exception e){ - pictureRepository.delete(picture); - } - - } - - @Override - public void deleteAll(Long[] ids) { - for (Long id : ids) { - delete(findById(id)); - } - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/QiNiuServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/QiNiuServiceImpl.java deleted file mode 100644 index bebb5b8e1..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/impl/QiNiuServiceImpl.java +++ /dev/null @@ -1,192 +0,0 @@ -package me.zhengjie.service.impl; - -import com.google.gson.Gson; -import com.qiniu.common.QiniuException; -import com.qiniu.http.Response; -import com.qiniu.storage.BucketManager; -import com.qiniu.storage.Configuration; -import com.qiniu.storage.UploadManager; -import com.qiniu.storage.model.DefaultPutRet; -import com.qiniu.storage.model.FileInfo; -import com.qiniu.util.Auth; -import me.zhengjie.domain.QiniuConfig; -import me.zhengjie.domain.QiniuContent; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.repository.QiNiuConfigRepository; -import me.zhengjie.repository.QiniuContentRepository; -import me.zhengjie.service.QiNiuService; -import me.zhengjie.service.dto.QiniuQueryCriteria; -import me.zhengjie.utils.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import java.util.Optional; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -@Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class QiNiuServiceImpl implements QiNiuService { - - @Autowired - private QiNiuConfigRepository qiNiuConfigRepository; - - @Autowired - private QiniuContentRepository qiniuContentRepository; - - @Value("${qiniu.max-size}") - private Long maxSize; - - private final String TYPE = "公开"; - - @Override - public Object queryAll(QiniuQueryCriteria criteria, Pageable pageable){ - return PageUtil.toPage(qiniuContentRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable)); - } - - @Override - public QiniuConfig find() { - Optional qiniuConfig = qiNiuConfigRepository.findById(1L); - if(qiniuConfig.isPresent()){ - return qiniuConfig.get(); - } else { - return new QiniuConfig(); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public QiniuConfig update(QiniuConfig qiniuConfig) { - if (!(qiniuConfig.getHost().toLowerCase().startsWith("http://")||qiniuConfig.getHost().toLowerCase().startsWith("https://"))) { - throw new BadRequestException("外链域名必须以http://或者https://开头"); - } - qiniuConfig.setId(1L); - return qiNiuConfigRepository.save(qiniuConfig); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public QiniuContent upload(MultipartFile file, QiniuConfig qiniuConfig) { - - Long size = maxSize * 1024 * 1024; - if(file.getSize() > size){ - throw new BadRequestException("文件超出规定大小"); - } - if(qiniuConfig.getId() == null){ - throw new BadRequestException("请先添加相应配置,再操作"); - } - /** - * 构造一个带指定Zone对象的配置类 - */ - Configuration cfg = new Configuration(QiNiuUtil.getRegion(qiniuConfig.getZone())); - UploadManager uploadManager = new UploadManager(cfg); - Auth auth = Auth.create(qiniuConfig.getAccessKey(), qiniuConfig.getSecretKey()); - String upToken = auth.uploadToken(qiniuConfig.getBucket()); - try { - String key = file.getOriginalFilename(); - if(qiniuContentRepository.findByKey(key) != null) { - key = QiNiuUtil.getKey(key); - } - Response response = uploadManager.put(file.getBytes(), key, upToken); - //解析上传成功的结果 - DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); - //存入数据库 - QiniuContent qiniuContent = new QiniuContent(); - qiniuContent.setBucket(qiniuConfig.getBucket()); - qiniuContent.setType(qiniuConfig.getType()); - qiniuContent.setKey(putRet.key); - qiniuContent.setUrl(qiniuConfig.getHost()+"/"+putRet.key); - qiniuContent.setSize(FileUtil.getSize(Integer.parseInt(file.getSize()+""))); - return qiniuContentRepository.save(qiniuContent); - } catch (Exception e) { - throw new BadRequestException(e.getMessage()); - } - } - - @Override - public QiniuContent findByContentId(Long id) { - Optional qiniuContent = qiniuContentRepository.findById(id); - ValidationUtil.isNull(qiniuContent,"QiniuContent", "id",id); - return qiniuContent.get(); - } - - @Override - public String download(QiniuContent content,QiniuConfig config){ - String finalUrl = null; - if(TYPE.equals(content.getType())){ - finalUrl = content.getUrl(); - } else { - Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); - /** - * 1小时,可以自定义链接过期时间 - */ - long expireInSeconds = 3600; - finalUrl = auth.privateDownloadUrl(content.getUrl(), expireInSeconds); - } - return finalUrl; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void delete(QiniuContent content, QiniuConfig config) { - //构造一个带指定Zone对象的配置类 - Configuration cfg = new Configuration(QiNiuUtil.getRegion(config.getZone())); - Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); - BucketManager bucketManager = new BucketManager(auth, cfg); - try { - bucketManager.delete(content.getBucket(), content.getKey()); - qiniuContentRepository.delete(content); - } catch (QiniuException ex) { - qiniuContentRepository.delete(content); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void synchronize(QiniuConfig config) { - if(config.getId() == null){ - throw new BadRequestException("请先添加相应配置,再操作"); - } - //构造一个带指定Zone对象的配置类 - Configuration cfg = new Configuration(QiNiuUtil.getRegion(config.getZone())); - Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); - BucketManager bucketManager = new BucketManager(auth, cfg); - //文件名前缀 - String prefix = ""; - //每次迭代的长度限制,最大1000,推荐值 1000 - int limit = 1000; - //指定目录分隔符,列出所有公共前缀(模拟列出目录效果)。缺省值为空字符串 - String delimiter = ""; - //列举空间文件列表 - BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(config.getBucket(), prefix, limit, delimiter); - while (fileListIterator.hasNext()) { - //处理获取的file list结果 - QiniuContent qiniuContent = null; - FileInfo[] items = fileListIterator.next(); - for (FileInfo item : items) { - if(qiniuContentRepository.findByKey(item.key) == null){ - qiniuContent = new QiniuContent(); - qiniuContent.setSize(FileUtil.getSize(Integer.parseInt(item.fsize+""))); - qiniuContent.setKey(item.key); - qiniuContent.setType(config.getType()); - qiniuContent.setBucket(config.getBucket()); - qiniuContent.setUrl(config.getHost()+"/"+item.key); - qiniuContentRepository.save(qiniuContent); - } - } - } - } - - @Override - public void deleteAll(Long[] ids, QiniuConfig config) { - for (Long id : ids) { - delete(findByContentId(id), config); - } - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java new file mode 100644 index 000000000..56d3ab3bb --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java @@ -0,0 +1,264 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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. +*/ +package me.zhengjie.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.config.AmzS3Config; +import me.zhengjie.domain.S3Storage; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.repository.S3StorageRepository; +import me.zhengjie.service.S3StorageService; +import me.zhengjie.service.dto.S3StorageQueryCriteria; +import me.zhengjie.utils.*; +import org.apache.commons.io.IOUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.*; +import software.amazon.awssdk.services.s3.waiters.S3Waiter; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** +* @description 服务实现 +* @author Zheng Jie +* @date 2025-06-25 +**/ +@Slf4j +@Service +@RequiredArgsConstructor +public class S3StorageServiceImpl implements S3StorageService { + + private final S3Client s3Client; + private final AmzS3Config amzS3Config; + private final S3StorageRepository s3StorageRepository; + + @Override + public S3Storage getById(Long id) { + return s3StorageRepository.findById(id).orElse(null); + } + + @Override + public PageResult queryAll(S3StorageQueryCriteria criteria, Pageable pageable){ + Page page = s3StorageRepository.findAll((root, criteriaQuery, criteriaBuilder) + -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page); + } + + @Override + public List queryAll(S3StorageQueryCriteria criteria){ + return s3StorageRepository.findAll((root, criteriaQuery, criteriaBuilder) + -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteAll(List ids) { + // 检查桶是否存在 + String bucketName = amzS3Config.getDefaultBucket(); + if (!bucketExists(bucketName)) { + throw new BadRequestException("存储桶不存在,请检查配置或权限。"); + } + // 遍历 ID 列表,删除对应的文件和数据库记录 + for (Long id : ids) { + String filePath = s3StorageRepository.selectFilePathById(id); + if (filePath == null) { + System.err.println("未找到 ID 为 " + id + " 的文件记录,无法删除。"); + continue; + } + try { + // 创建 DeleteObjectRequest,指定存储桶和文件键 + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() + .bucket(bucketName) + .key(filePath) + .build(); + // 调用 deleteObject 方法 + s3Client.deleteObject(deleteObjectRequest); + // 删除数据库数据 + s3StorageRepository.deleteById(id); + } catch (S3Exception e) { + // 处理 AWS 特定的异常 + log.error("从 S3 删除文件时出错: {}", e.awsErrorDetails().errorMessage(), e); + } + } + } + + @Override + public S3Storage upload(MultipartFile file) { + String bucketName = amzS3Config.getDefaultBucket(); + // 检查存储桶是否存在 + if (!bucketExists(bucketName)) { + log.warn("存储桶 {} 不存在,尝试创建...", bucketName); + if (createBucket(bucketName)){ + log.info("存储桶 {} 创建成功。", bucketName); + } else { + throw new BadRequestException("存储桶创建失败,请检查配置或权限。"); + } + } + // 获取文件名 + String originalName = file.getOriginalFilename(); + if (StringUtils.isBlank(originalName)) { + throw new IllegalArgumentException("文件名不能为空"); + } + // 生成存储路径和文件名 + String folder = DateUtil.format(new Date(), amzS3Config.getTimeformat()); + String fileName = IdUtil.simpleUUID() + "." + FileUtil.getExtensionName(originalName); + String filePath = folder + "/" + fileName; + // 构建上传请求 + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(amzS3Config.getDefaultBucket()) + .key(filePath) + .build(); + // 创建 S3Storage 实例 + S3Storage s3Storage = new S3Storage(); + try { + // 上传文件到 S3 + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); + // 设置 S3Storage 属性 + s3Storage.setFileMimeType(FileUtil.getMimeType(originalName)); + s3Storage.setFileName(originalName); + s3Storage.setFileRealName(fileName); + s3Storage.setFileSize(FileUtil.getSize(file.getSize())); + s3Storage.setFileType(FileUtil.getExtensionName(originalName)); + s3Storage.setFilePath(filePath); + // 保存入库 + s3StorageRepository.save(s3Storage); + } catch (IOException e) { + throw new RuntimeException(e); + } + // 设置地址 + return s3Storage; + } + + @Override + public void download(List all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (S3Storage s3Storage : all) { + Map map = new LinkedHashMap<>(); + map.put("文件名称", s3Storage.getFileName()); + map.put("真实存储的名称", s3Storage.getFileRealName()); + map.put("文件大小", s3Storage.getFileSize()); + map.put("文件MIME 类型", s3Storage.getFileMimeType()); + map.put("文件类型", s3Storage.getFileType()); + map.put("文件路径", s3Storage.getFilePath()); + map.put("创建者", s3Storage.getCreateBy()); + map.put("更新者", s3Storage.getUpdateBy()); + map.put("创建日期", s3Storage.getCreateTime()); + map.put("更新时间", s3Storage.getUpdateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + public Map privateDownload(Long id) { + S3Storage storage = s3StorageRepository.findById(id).orElse(null); + if (storage == null) { + throw new BadRequestException("文件不存在或已被删除"); + } + // 创建 GetObjectRequest,指定存储桶和文件键 + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(amzS3Config.getDefaultBucket()) + .key(storage.getFilePath()) + .build(); + String base64Data; + // 使用 try-with-resources 确保流能被自动关闭 + // s3Client.getObject() 返回一个 ResponseInputStream,它是一个包含S3对象数据的输入流 + try (ResponseInputStream s3InputStream = s3Client.getObject(getObjectRequest)) { + // 使用 IOUtils.toByteArray 将输入流直接转换为字节数组 + byte[] fileBytes = IOUtils.toByteArray(s3InputStream); + // 使用 Java 内置的 Base64 编码器将字节数组转换为 Base64 字符串 + base64Data = Base64.getEncoder().encodeToString(fileBytes); + } catch (S3Exception e) { + // 处理 AWS 特定的异常 + throw new BadRequestException("从 S3 下载文件时出错: " + e.awsErrorDetails().errorMessage()); + } catch (IOException e) { + // 处理通用的 IO 异常 (IOUtils.toByteArray 可能会抛出) + throw new BadRequestException("读取 S3 输入流时出错: " + e.getMessage()); + } + // 构造返回数据 + Map responseData = new HashMap<>(); + // 文件名 + responseData.put("fileName", storage.getFileName()); + // 文件类型 + responseData.put("fileMimeType", storage.getFileMimeType()); + // 文件内容 + responseData.put("base64Data", base64Data); + return responseData; + } + + /** + * 检查云存储桶是否存在 + * @param bucketName 存储桶名称 + */ + @SuppressWarnings({"all"}) + private boolean bucketExists(String bucketName) { + try { + HeadBucketRequest headBucketRequest = HeadBucketRequest.builder() + .bucket(bucketName) + .build(); + s3Client.headBucket(headBucketRequest); + return true; + } catch (S3Exception e) { + // 如果状态码是 404 (Not Found), 说明存储桶不存在 + if (e.statusCode() == 404) { + log.error("存储桶 '{}' 不存在。", bucketName); + return false; + } + // 其他异常 (如 403 Forbidden) 说明存在问题,但不能断定它不存在 + throw new BadRequestException("检查存储桶时出错: " + e.awsErrorDetails().errorMessage()); + } + } + + /** + * 创建云存储桶 + * @param bucketName 存储桶名称 + */ + private boolean createBucket(String bucketName) { + try { + // 使用 S3Waiter 等待存储桶创建完成 + S3Waiter s3Waiter = s3Client.waiter(); + CreateBucketRequest bucketRequest = CreateBucketRequest.builder() + .bucket(bucketName) + .acl(BucketCannedACL.PRIVATE) + .build(); + s3Client.createBucket(bucketRequest); + // 等待直到存储桶创建完成 + HeadBucketRequest bucketRequestWait = HeadBucketRequest.builder() + .bucket(bucketName) + .build(); + // 使用 WaiterResponse 等待存储桶存在 + WaiterResponse waiterResponse = s3Waiter.waitUntilBucketExists(bucketRequestWait); + waiterResponse.matched().response().ifPresent(response -> + log.info("存储桶 '{}' 创建成功,状态: {}", bucketName, response.sdkHttpResponse().statusCode()) + ); + } catch (BucketAlreadyOwnedByYouException e) { + log.warn("存储桶 '{}' 已经被您拥有,无需重复创建。", bucketName); + } catch (S3Exception e) { + throw new BadRequestException("创建存储桶时出错: " + e.awsErrorDetails().errorMessage()); + } + return true; + } +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/VerificationCodeServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/VerificationCodeServiceImpl.java deleted file mode 100644 index ebac5e1ae..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/impl/VerificationCodeServiceImpl.java +++ /dev/null @@ -1,85 +0,0 @@ -package me.zhengjie.service.impl; - -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.extra.template.Template; -import cn.hutool.extra.template.TemplateConfig; -import cn.hutool.extra.template.TemplateEngine; -import cn.hutool.extra.template.TemplateUtil; -import me.zhengjie.domain.VerificationCode; -import me.zhengjie.domain.vo.EmailVo; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.repository.VerificationCodeRepository; -import me.zhengjie.service.VerificationCodeService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; -import java.util.concurrent.*; - -/** - * @author Zheng Jie - * @date 2018-12-26 - */ -@Service -@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class VerificationCodeServiceImpl implements VerificationCodeService { - - @Autowired - private VerificationCodeRepository verificationCodeRepository; - - @Value("${code.expiration}") - private Integer expiration; - - @Override - @Transactional(rollbackFor = Exception.class) - public EmailVo sendEmail(VerificationCode code) { - EmailVo emailVo = null; - String content = ""; - VerificationCode verificationCode = verificationCodeRepository.findByScenesAndTypeAndValueAndStatusIsTrue(code.getScenes(),code.getType(),code.getValue()); - // 如果不存在有效的验证码,就创建一个新的 - TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); - Template template = engine.getTemplate("email/email.ftl"); - if(verificationCode == null){ - code.setCode(RandomUtil.randomNumbers (6)); - content = template.render(Dict.create().set("code",code.getCode())); - emailVo = new EmailVo(Arrays.asList(code.getValue()),"eladmin后台管理系统",content); - timedDestruction(verificationCodeRepository.save(code)); - // 存在就再次发送原来的验证码 - } else { - content = template.render(Dict.create().set("code",verificationCode.getCode())); - emailVo = new EmailVo(Arrays.asList(verificationCode.getValue()),"eladmin后台管理系统",content); - } - return emailVo; - } - - @Override - public void validated(VerificationCode code) { - VerificationCode verificationCode = verificationCodeRepository.findByScenesAndTypeAndValueAndStatusIsTrue(code.getScenes(),code.getType(),code.getValue()); - if(verificationCode == null || !verificationCode.getCode().equals(code.getCode())){ - throw new BadRequestException("无效验证码"); - } else { - verificationCode.setStatus(false); - verificationCodeRepository.save(verificationCode); - } - } - - /** - * 定时任务,指定分钟后改变验证码状态 - * @param verifyCode - */ - private void timedDestruction(VerificationCode verifyCode) { - //以下示例为程序调用结束继续运行 - ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - try { - executorService.schedule(() -> { - verifyCode.setStatus(false); - verificationCodeRepository.save(verifyCode); - }, expiration * 60 * 1000L, TimeUnit.MILLISECONDS); - }catch (Exception e){ - e.printStackTrace(); - } - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/mapstruct/LocalStorageMapper.java b/eladmin-tools/src/main/java/me/zhengjie/service/mapstruct/LocalStorageMapper.java new file mode 100644 index 000000000..0d54c53ec --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/mapstruct/LocalStorageMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ +package me.zhengjie.service.mapstruct; + +import me.zhengjie.base.BaseMapper; +import me.zhengjie.service.dto.LocalStorageDto; +import me.zhengjie.domain.LocalStorage; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @author Zheng Jie +* @date 2019-09-05 +*/ +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface LocalStorageMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/utils/AliPayStatusEnum.java b/eladmin-tools/src/main/java/me/zhengjie/utils/AliPayStatusEnum.java deleted file mode 100644 index 46e6b7cfc..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/utils/AliPayStatusEnum.java +++ /dev/null @@ -1,41 +0,0 @@ -package me.zhengjie.utils; - -/** - * 支付状态 - * @author zhengjie - * @date 2018/08/01 16:45:43 - */ -public enum AliPayStatusEnum { - - /** - * 交易成功 - */ - FINISHED("交易成功", "TRADE_FINISHED"), - - /** - * 支付成功 - */ - SUCCESS("支付成功", "TRADE_SUCCESS"), - - /** - * 交易创建 - */ - BUYER_PAY("交易创建", "WAIT_BUYER_PAY"), - - /** - * 交易关闭 - */ - CLOSED("交易关闭", "TRADE_CLOSED"); - - private String name; - private String value; - - AliPayStatusEnum(String name, String value) { - this.name = name; - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/utils/AlipayUtils.java b/eladmin-tools/src/main/java/me/zhengjie/utils/AlipayUtils.java index 6c6b2dccb..1ae60382d 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/utils/AlipayUtils.java +++ b/eladmin-tools/src/main/java/me/zhengjie/utils/AlipayUtils.java @@ -1,6 +1,20 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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. + */ package me.zhengjie.utils; -import cn.hutool.core.util.StrUtil; import com.alipay.api.AlipayApiException; import com.alipay.api.internal.util.AlipaySignature; import me.zhengjie.domain.AlipayConfig; @@ -9,7 +23,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; /** @@ -22,7 +35,7 @@ public class AlipayUtils { /** * 生成订单号 - * @return + * @return String */ public String getOrderCode() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -35,25 +48,23 @@ public String getOrderCode() { String[] split1 = s.split(" "); String s1 = split1[0] + split1[1]; String[] split2 = s1.split(":"); - String s2 = split2[0] + split2[1] + split2[2] + a; - return s2; + return split2[0] + split2[1] + split2[2] + a; } /** * 校验签名 - * @param request - * @return + * @param request HttpServletRequest + * @param alipay 阿里云配置 + * @return boolean */ public boolean rsaCheck(HttpServletRequest request, AlipayConfig alipay){ - /** - * 获取支付宝POST过来反馈信息 - */ + // 获取支付宝POST过来反馈信息 Map params = new HashMap<>(1); - Map requestParams = request.getParameterMap(); - for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) { - String name = (String) iter.next(); - String[] values = (String[]) requestParams.get(name); + Map requestParams = request.getParameterMap(); + for (Object o : requestParams.keySet()) { + String name = (String) o; + String[] values = requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] @@ -63,17 +74,12 @@ public boolean rsaCheck(HttpServletRequest request, AlipayConfig alipay){ } try { - boolean verifyResult = AlipaySignature.rsaCheckV1(params, + return AlipaySignature.rsaCheckV1(params, alipay.getPublicKey(), alipay.getCharset(), alipay.getSignType()); - return verifyResult; } catch (AlipayApiException e) { return false; } } - - public boolean isEmpty(String str){ - return StrUtil.isEmpty(str); - } } diff --git a/eladmin-tools/src/main/java/me/zhengjie/utils/QiNiuUtil.java b/eladmin-tools/src/main/java/me/zhengjie/utils/QiNiuUtil.java deleted file mode 100644 index 4ff7dc834..000000000 --- a/eladmin-tools/src/main/java/me/zhengjie/utils/QiNiuUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.zhengjie.utils; - -import com.qiniu.storage.Region; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * 七牛云存储工具类 - * @author Zheng Jie - * @date 2018-12-31 - */ -public class QiNiuUtil { - - public static final String HUAD = "华东"; - - public static final String HUAB = "华北"; - - public static final String HUAN = "华南"; - - public static final String BEIM = "北美"; - - /** - * 得到机房的对应关系 - * @param zone - * @return - */ - public static Region getRegion(String zone){ - - if(HUAD.equals(zone)){ - return Region.huadong(); - } else if(HUAB.equals(zone)){ - return Region.huabei(); - } else if(HUAN.equals(zone)){ - return Region.huanan(); - } else if (BEIM.equals(zone)){ - return Region.beimei(); - // 否则就是东南亚 - } else { - return Region.qvmHuadong(); - } - } - - /** - * 默认不指定key的情况下,以文件内容的hash值作为文件名 - * @param file - * @return - */ - public static String getKey(String file){ - StringBuffer key = new StringBuffer(FileUtil.getFileNameNoEx(file)); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); - Date date = new Date(); - key.append("-"); - key.append(sdf.format(date)); - key.append("."); - key.append(FileUtil.getExtensionName(file)); - return key.toString(); - } -} diff --git a/pom.xml b/pom.xml index 0127ac165..31438c6b0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,11 @@ - + 4.0.0 me.zhengjie eladmin pom - 2.1 + 2.7 eladmin-common @@ -17,63 +15,86 @@ eladmin-generator - el-admin - http://auauz.net + ELADMIN 后台管理 + https://eladmin.vip org.springframework.boot spring-boot-starter-parent - 2.1.0.RELEASE - + 2.7.18 + 1.2.9 UTF-8 UTF-8 1.8 - 2.9.0 - 1.16 - 2.9.2 - 1.2.54 - 1.1.10 - [4.1.12,) - 2.5.0 - 1.2.0.Final + 2.0.54 + 1.2.19 + 2.11.1 + 1.4.2.Final - - + org.springframework.boot spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-json + + + + org.springframework.boot spring-boot-starter-test test + + org.springframework.boot spring-boot-starter-security - + + org.springframework.boot spring-boot-starter-cache - + + org.springframework.boot spring-boot-starter-data-redis - - + + + org.redisson + redisson-spring-boot-starter + 3.17.1 + + + + org.apache.commons commons-pool2 @@ -86,49 +107,40 @@ - org.bgee.log4jdbc-log4j2 - log4jdbc-log4j2-jdbc4.1 - ${log4jdbc.version} + p6spy + p6spy + 3.9.1 - + - io.springfox - springfox-swagger2 - ${swagger.version} + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + io.swagger swagger-annotations - - io.swagger - swagger-models - - - io.springfox - springfox-swagger-ui - ${swagger.version} - + + io.swagger swagger-annotations - 1.5.21 - - - io.swagger - swagger-models - 1.5.21 + 1.5.22 - mysql - mysql-connector-java + com.mysql + mysql-connector-j + 9.2.0 runtime + com.alibaba @@ -136,6 +148,13 @@ ${druid.version} + + + net.dreamlu + mica-ip2region + 2.7.18.9 + + org.projectlombok @@ -143,29 +162,53 @@ true - + - cn.hutool - hutool-all - ${hutool.version} + org.apache.poi + poi + 5.4.0 - - org.lionsoul - ip2region - 1.7.2 + org.apache.poi + poi-ooxml + 5.4.0 + + + xerces + xercesImpl + 2.12.2 - + - com.alibaba - fastjson - ${fastjson.version} + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + com.alibaba.fastjson2 + fastjson2-extension-spring5 + ${fastjson2.version} + + + + + com.github.whvcse + easy-captcha + 1.6.2 + + + + org.apache.commons + commons-text + 1.13.0 + + org.mapstruct - mapstruct-jdk8 + mapstruct ${mapstruct.version} diff --git a/sql/eladmin.sql b/sql/eladmin.sql index 2aa9b3a1e..aea1ee750 100644 --- a/sql/eladmin.sql +++ b/sql/eladmin.sql @@ -1,642 +1,848 @@ /* -Navicat MySQL Data Transfer + Navicat Premium Dump SQL -Source Server : localhost -Source Server Version : 50562 -Source Host : localhost:3306 -Source Database : eladmin + Source Server : localhost + Source Server Type : MariaDB + Source Server Version : 110206 (11.2.6-MariaDB) + Source Host : localhost:3306 + Source Schema : eladmin -Target Server Type : MYSQL -Target Server Version : 50562 -File Encoding : 65001 + Target Server Type : MariaDB + Target Server Version : 110206 (11.2.6-MariaDB) + File Encoding : 65001 -Date: 2019-06-05 11:13:41 + Date: 25/06/2025 15:56:51 */ -SET FOREIGN_KEY_CHECKS=0; +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for code_column +-- ---------------------------- +DROP TABLE IF EXISTS `code_column`; +CREATE TABLE `code_column` ( + `column_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `table_name` varchar(180) DEFAULT NULL COMMENT '表名', + `column_name` varchar(255) DEFAULT NULL COMMENT '数据库字段名称', + `column_type` varchar(255) DEFAULT NULL COMMENT '数据库字段类型', + `dict_name` varchar(255) DEFAULT NULL COMMENT '字典名称', + `extra` varchar(255) DEFAULT NULL COMMENT '字段额外的参数', + `form_show` bit(1) DEFAULT NULL COMMENT '是否表单显示', + `form_type` varchar(255) DEFAULT NULL COMMENT '表单类型', + `key_type` varchar(255) DEFAULT NULL COMMENT '数据库字段键类型', + `list_show` bit(1) DEFAULT NULL COMMENT '是否在列表显示', + `not_null` bit(1) DEFAULT NULL COMMENT '是否为空', + `query_type` varchar(255) DEFAULT NULL COMMENT '查询类型', + `remark` varchar(255) DEFAULT NULL COMMENT '描述', + `date_annotation` varchar(255) DEFAULT NULL COMMENT '日期注解', + PRIMARY KEY (`column_id`) USING BTREE, + KEY `idx_table_name` (`table_name`) +) ENGINE=InnoDB AUTO_INCREMENT=266 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='代码生成字段信息存储'; -- ---------------------------- --- Table structure for alipay_config +-- Records of code_column -- ---------------------------- -DROP TABLE IF EXISTS `alipay_config`; -CREATE TABLE `alipay_config` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', - `app_id` varchar(255) DEFAULT NULL COMMENT '应用ID', - `charset` varchar(255) DEFAULT NULL COMMENT '编码', - `format` varchar(255) DEFAULT NULL COMMENT '类型 固定格式json', - `gateway_url` varchar(255) DEFAULT NULL COMMENT '网关地址', - `notify_url` varchar(255) DEFAULT NULL COMMENT '异步回调', - `private_key` text COMMENT '私钥', - `public_key` text COMMENT '公钥', - `return_url` varchar(255) DEFAULT NULL COMMENT '回调地址', - `sign_type` varchar(255) DEFAULT NULL COMMENT '签名方式', - `sys_service_provider_id` varchar(255) DEFAULT NULL COMMENT '商户号', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for code_config +-- ---------------------------- +DROP TABLE IF EXISTS `code_config`; +CREATE TABLE `code_config` ( + `config_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `table_name` varchar(255) DEFAULT NULL COMMENT '表名', + `author` varchar(255) DEFAULT NULL COMMENT '作者', + `cover` bit(1) DEFAULT NULL COMMENT '是否覆盖', + `module_name` varchar(255) DEFAULT NULL COMMENT '模块名称', + `pack` varchar(255) DEFAULT NULL COMMENT '至于哪个包下', + `path` varchar(255) DEFAULT NULL COMMENT '前端代码生成的路径', + `api_path` varchar(255) DEFAULT NULL COMMENT '前端Api文件路径', + `prefix` varchar(255) DEFAULT NULL COMMENT '表前缀', + `api_alias` varchar(255) DEFAULT NULL COMMENT '接口名称', + PRIMARY KEY (`config_id`) USING BTREE, + KEY `idx_table_name` (`table_name`(100)) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='代码生成器配置'; + +-- ---------------------------- +-- Records of code_config +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for mnt_app +-- ---------------------------- +DROP TABLE IF EXISTS `mnt_app`; +CREATE TABLE `mnt_app` ( + `app_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(255) DEFAULT NULL COMMENT '应用名称', + `upload_path` varchar(255) DEFAULT NULL COMMENT '上传目录', + `deploy_path` varchar(255) DEFAULT NULL COMMENT '部署路径', + `backup_path` varchar(255) DEFAULT NULL COMMENT '备份路径', + `port` int(255) DEFAULT NULL COMMENT '应用端口', + `start_script` varchar(4000) DEFAULT NULL COMMENT '启动脚本', + `deploy_script` varchar(4000) DEFAULT NULL COMMENT '部署脚本', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建日期', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`app_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='应用管理'; + +-- ---------------------------- +-- Records of mnt_app +-- ---------------------------- +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for dept +-- Table structure for mnt_database -- ---------------------------- -DROP TABLE IF EXISTS `dept`; -CREATE TABLE `dept` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', +DROP TABLE IF EXISTS `mnt_database`; +CREATE TABLE `mnt_database` ( + `db_id` varchar(50) NOT NULL COMMENT 'ID', `name` varchar(255) NOT NULL COMMENT '名称', - `pid` bigint(20) NOT NULL COMMENT '上级部门', - `create_time` datetime DEFAULT NULL, - `enabled` bit(1) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8; + `jdbc_url` varchar(255) NOT NULL COMMENT 'jdbc连接', + `user_name` varchar(255) NOT NULL COMMENT '账号', + `pwd` varchar(255) NOT NULL COMMENT '密码', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`db_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='数据库管理'; -- ---------------------------- --- Records of dept +-- Records of mnt_database -- ---------------------------- -INSERT INTO `dept` VALUES ('1', 'eladmin', '0', '2019-03-25 09:14:05', ''); -INSERT INTO `dept` VALUES ('2', '研发部', '7', '2019-03-25 09:15:32', ''); -INSERT INTO `dept` VALUES ('5', '运维部', '7', '2019-03-25 09:20:44', ''); -INSERT INTO `dept` VALUES ('6', '测试部', '8', '2019-03-25 09:52:18', ''); -INSERT INTO `dept` VALUES ('7', '华南分部', '1', '2019-03-25 11:04:50', ''); -INSERT INTO `dept` VALUES ('8', '华北分部', '1', '2019-03-25 11:04:53', ''); -INSERT INTO `dept` VALUES ('9', '财务部', '7', '2019-03-25 11:05:34', ''); -INSERT INTO `dept` VALUES ('10', '行政部', '8', '2019-03-25 11:05:58', ''); -INSERT INTO `dept` VALUES ('11', '人事部', '8', '2019-03-25 11:07:58', ''); -INSERT INTO `dept` VALUES ('12', '市场部', '7', '2019-03-25 11:10:24', '\0'); +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for dict +-- Table structure for mnt_deploy -- ---------------------------- -DROP TABLE IF EXISTS `dict`; -CREATE TABLE `dict` ( - `id` bigint(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL COMMENT '字典名称', - `remark` varchar(255) DEFAULT NULL COMMENT '描述', - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `mnt_deploy`; +CREATE TABLE `mnt_deploy` ( + `deploy_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `app_id` bigint(20) DEFAULT NULL COMMENT '应用编号', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL, + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`deploy_id`) USING BTREE, + KEY `idx_app_id` (`app_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='部署管理'; -- ---------------------------- --- Records of dict +-- Records of mnt_deploy -- ---------------------------- -INSERT INTO `dict` VALUES ('1', 'user_status', '用户状态'); -INSERT INTO `dict` VALUES ('4', 'dept_status', '部门状态'); -INSERT INTO `dict` VALUES ('5', 'job_status', '岗位状态'); +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for dict_detail +-- Table structure for mnt_deploy_history -- ---------------------------- -DROP TABLE IF EXISTS `dict_detail`; -CREATE TABLE `dict_detail` ( - `id` bigint(11) NOT NULL AUTO_INCREMENT, - `label` varchar(255) NOT NULL COMMENT '字典标签', - `value` varchar(255) NOT NULL COMMENT '字典值', - `sort` varchar(255) DEFAULT NULL COMMENT '排序', - `dict_id` bigint(11) DEFAULT NULL COMMENT '字典id', - PRIMARY KEY (`id`), - KEY `FK5tpkputc6d9nboxojdbgnpmyb` (`dict_id`), - CONSTRAINT `FK5tpkputc6d9nboxojdbgnpmyb` FOREIGN KEY (`dict_id`) REFERENCES `dict` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `mnt_deploy_history`; +CREATE TABLE `mnt_deploy_history` ( + `history_id` varchar(50) NOT NULL COMMENT 'ID', + `app_name` varchar(255) NOT NULL COMMENT '应用名称', + `deploy_date` datetime NOT NULL COMMENT '部署日期', + `deploy_user` varchar(50) NOT NULL COMMENT '部署用户', + `ip` varchar(20) NOT NULL COMMENT '服务器IP', + `deploy_id` bigint(20) DEFAULT NULL COMMENT '部署编号', + PRIMARY KEY (`history_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='部署历史管理'; -- ---------------------------- --- Records of dict_detail +-- Records of mnt_deploy_history -- ---------------------------- -INSERT INTO `dict_detail` VALUES ('1', '激活', 'true', '1', '1'); -INSERT INTO `dict_detail` VALUES ('2', '锁定', 'false', '2', '1'); -INSERT INTO `dict_detail` VALUES ('11', '正常', 'true', '1', '4'); -INSERT INTO `dict_detail` VALUES ('12', '停用', 'false', '2', '4'); -INSERT INTO `dict_detail` VALUES ('13', '正常', 'true', '1', '5'); -INSERT INTO `dict_detail` VALUES ('14', '停用', 'false', '2', '5'); +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for email_config +-- Table structure for mnt_deploy_server -- ---------------------------- -DROP TABLE IF EXISTS `email_config`; -CREATE TABLE `email_config` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `from_user` varchar(255) DEFAULT NULL COMMENT '收件人', - `host` varchar(255) DEFAULT NULL COMMENT '邮件服务器SMTP地址', - `pass` varchar(255) DEFAULT NULL COMMENT '密码', - `port` varchar(255) DEFAULT NULL COMMENT '端口', - `user` varchar(255) DEFAULT NULL COMMENT '发件者用户名', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `mnt_deploy_server`; +CREATE TABLE `mnt_deploy_server` ( + `deploy_id` bigint(20) NOT NULL COMMENT '部署ID', + `server_id` bigint(20) NOT NULL COMMENT '服务ID', + PRIMARY KEY (`deploy_id`,`server_id`) USING BTREE, + KEY `idx_deploy_id` (`deploy_id`), + KEY `idx_server_id` (`server_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='应用与服务器关联'; -- ---------------------------- --- Table structure for gen_config +-- Records of mnt_deploy_server -- ---------------------------- -DROP TABLE IF EXISTS `gen_config`; -CREATE TABLE `gen_config` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `author` varchar(255) DEFAULT NULL COMMENT '作者', - `cover` bit(1) DEFAULT NULL COMMENT '是否覆盖', - `module_name` varchar(255) DEFAULT NULL COMMENT '模块名称', - `pack` varchar(255) DEFAULT NULL COMMENT '至于哪个包下', - `path` varchar(255) DEFAULT NULL COMMENT '前端代码生成的路径', - `api_path` varchar(255) DEFAULT NULL, - `prefix` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; +BEGIN; +COMMIT; -- ---------------------------- --- Records of gen_config +-- Table structure for mnt_server -- ---------------------------- -INSERT INTO `gen_config` VALUES ('1', 'jie', '\0', 'eladmin-system', 'me.zhengjie.modules.test', 'E:\\workspace\\me\\front\\eladmin-qt\\src\\views\\test', 'E:\\workspace\\me\\front\\eladmin-qt\\src\\api', null); +DROP TABLE IF EXISTS `mnt_server`; +CREATE TABLE `mnt_server` ( + `server_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `account` varchar(50) DEFAULT NULL COMMENT '账号', + `ip` varchar(20) DEFAULT NULL COMMENT 'IP地址', + `name` varchar(100) DEFAULT NULL COMMENT '名称', + `password` varchar(100) DEFAULT NULL COMMENT '密码', + `port` int(11) DEFAULT NULL COMMENT '端口', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`server_id`) USING BTREE, + KEY `idx_ip` (`ip`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='服务器管理'; -- ---------------------------- --- Table structure for job +-- Records of mnt_server -- ---------------------------- -DROP TABLE IF EXISTS `job`; -CREATE TABLE `job` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `enabled` bit(1) NOT NULL, - `create_time` datetime DEFAULT NULL, - `sort` bigint(20) NOT NULL, - `dept_id` bigint(20) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `FKmvhj0rogastlctflsxf1d6k3i` (`dept_id`), - CONSTRAINT `FKmvhj0rogastlctflsxf1d6k3i` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8; +BEGIN; +COMMIT; -- ---------------------------- --- Records of job +-- Table structure for sys_dept -- ---------------------------- -INSERT INTO `job` VALUES ('2', '董事长秘书', '', '2019-03-29 14:01:30', '2', '1'); -INSERT INTO `job` VALUES ('8', '人事专员', '', '2019-03-29 14:52:28', '3', '11'); -INSERT INTO `job` VALUES ('10', '产品经理', '\0', '2019-03-29 14:55:51', '4', '2'); -INSERT INTO `job` VALUES ('11', '全栈开发', '', '2019-03-31 13:39:30', '6', '2'); -INSERT INTO `job` VALUES ('12', '软件测试', '', '2019-03-31 13:39:43', '5', '2'); -INSERT INTO `job` VALUES ('19', '董事长', '', '2019-03-31 14:58:15', '1', '1'); +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `pid` bigint(20) DEFAULT NULL COMMENT '上级部门', + `sub_count` int(5) DEFAULT 0 COMMENT '子部门数目', + `name` varchar(255) NOT NULL COMMENT '名称', + `dept_sort` int(5) DEFAULT 999 COMMENT '排序', + `enabled` bit(1) NOT NULL COMMENT '状态', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建日期', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE, + KEY `idx_pid` (`pid`), + KEY `idx_enabled` (`enabled`) +) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='部门'; -- ---------------------------- --- Table structure for log +-- Records of sys_dept -- ---------------------------- -DROP TABLE IF EXISTS `log`; -CREATE TABLE `log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `create_time` datetime DEFAULT NULL, - `description` varchar(255) DEFAULT NULL, - `exception_detail` text, - `log_type` varchar(255) DEFAULT NULL, - `method` varchar(255) DEFAULT NULL, - `params` text, - `request_ip` varchar(255) DEFAULT NULL, - `time` bigint(20) DEFAULT NULL, - `username` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=10334 DEFAULT CHARSET=utf8; +BEGIN; +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 7, 1, '研发部', 3, b'1', 'admin', 'admin', '2019-03-25 09:15:32', '2020-08-02 14:48:47'); +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 7, 0, '运维部', 4, b'1', 'admin', 'admin', '2019-03-25 09:20:44', '2020-05-17 14:27:27'); +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, 8, 0, '测试部', 6, b'1', 'admin', 'admin', '2019-03-25 09:52:18', '2020-06-08 11:59:21'); +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (7, NULL, 2, '华南分部', 0, b'1', 'admin', 'admin', '2019-03-25 11:04:50', '2020-06-08 12:08:56'); +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (8, NULL, 2, '华北分部', 1, b'1', 'admin', 'admin', '2019-03-25 11:04:53', '2020-05-14 12:54:00'); +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (15, 8, 0, 'UI部门', 7, b'1', 'admin', 'admin', '2020-05-13 22:56:53', '2020-05-14 12:54:13'); +INSERT INTO `sys_dept` (`dept_id`, `pid`, `sub_count`, `name`, `dept_sort`, `enabled`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (17, 2, 0, '研发一组', 999, b'1', 'admin', 'admin', '2020-08-02 14:49:07', '2020-08-02 14:49:07'); +COMMIT; -- ---------------------------- --- Records of log +-- Table structure for sys_dict -- ---------------------------- +DROP TABLE IF EXISTS `sys_dict`; +CREATE TABLE `sys_dict` ( + `dict_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(255) NOT NULL COMMENT '字典名称', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建日期', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dict_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='数据字典'; -- ---------------------------- --- Table structure for menu +-- Records of sys_dict +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dict` (`dict_id`, `name`, `description`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, 'user_status', '用户状态', NULL, NULL, '2019-10-27 20:31:36', NULL); +INSERT INTO `sys_dict` (`dict_id`, `name`, `description`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 'dept_status', '部门状态', NULL, NULL, '2019-10-27 20:31:36', NULL); +INSERT INTO `sys_dict` (`dict_id`, `name`, `description`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 'job_status', '岗位状态', NULL, 'admin', '2019-10-27 20:31:36', '2025-01-14 15:48:29'); +COMMIT; + -- ---------------------------- -DROP TABLE IF EXISTS `menu`; -CREATE TABLE `menu` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', +-- Table structure for sys_dict_detail +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_detail`; +CREATE TABLE `sys_dict_detail` ( + `detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `dict_id` bigint(11) DEFAULT NULL COMMENT '字典id', + `label` varchar(255) NOT NULL COMMENT '字典标签', + `value` varchar(255) NOT NULL COMMENT '字典值', + `dict_sort` int(5) DEFAULT NULL COMMENT '排序', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', `create_time` datetime DEFAULT NULL COMMENT '创建日期', - `i_frame` bit(1) DEFAULT NULL COMMENT '是否外链', - `name` varchar(255) DEFAULT NULL COMMENT '菜单名称', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`detail_id`) USING BTREE, + KEY `idx_dict_id` (`dict_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='数据字典详情'; + +-- ---------------------------- +-- Records of sys_dict_detail +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, 1, '激活', 'true', 1, NULL, NULL, '2019-10-27 20:31:36', NULL); +INSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 1, '禁用', 'false', 2, NULL, NULL, NULL, NULL); +INSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (3, 4, '启用', 'true', 1, NULL, NULL, NULL, NULL); +INSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 4, '停用', 'false', 2, NULL, NULL, '2019-10-27 20:31:36', NULL); +INSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 5, '启用', 'true', 1, NULL, NULL, NULL, NULL); +INSERT INTO `sys_dict_detail` (`detail_id`, `dict_id`, `label`, `value`, `dict_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, 5, '停用', 'false', 2, NULL, NULL, '2019-10-27 20:31:36', NULL); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_job +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job`; +CREATE TABLE `sys_job` ( + `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(180) NOT NULL COMMENT '岗位名称', + `enabled` bit(1) NOT NULL COMMENT '岗位状态', + `job_sort` int(5) DEFAULT NULL COMMENT '排序', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建日期', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`job_id`) USING BTREE, + UNIQUE KEY `uniq_name` (`name`), + KEY `idx_enabled` (`enabled`) +) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='岗位'; + +-- ---------------------------- +-- Records of sys_job +-- ---------------------------- +BEGIN; +INSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (8, '人事专员', b'1', 3, NULL, NULL, '2019-03-29 14:52:28', NULL); +INSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (10, '产品经理', b'1', 4, NULL, NULL, '2019-03-29 14:55:51', NULL); +INSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (11, '全栈开发', b'1', 2, NULL, 'admin', '2019-03-31 13:39:30', '2020-05-05 11:33:43'); +INSERT INTO `sys_job` (`job_id`, `name`, `enabled`, `job_sort`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (12, '软件测试', b'1', 5, NULL, 'admin', '2019-03-31 13:39:43', '2020-05-10 19:56:26'); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_log`; +CREATE TABLE `sys_log` ( + `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `log_type` varchar(10) NOT NULL COMMENT '日志类型:INFI/ERROR', + `method` varchar(255) DEFAULT NULL COMMENT '方法名', + `params` text DEFAULT NULL COMMENT '参数', + `request_ip` varchar(255) DEFAULT NULL COMMENT '请求IP', + `time` bigint(20) DEFAULT NULL COMMENT '执行时间', + `username` varchar(255) DEFAULT NULL COMMENT '用户名', + `address` varchar(255) DEFAULT NULL COMMENT '地址', + `browser` varchar(255) DEFAULT NULL COMMENT '浏览器', + `exception_detail` text DEFAULT NULL COMMENT '异常', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`log_id`) USING BTREE, + KEY `idx_create_time_index` (`create_time`), + KEY `idx_log_type` (`log_type`) +) ENGINE=InnoDB AUTO_INCREMENT=3636 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='系统日志'; + +-- ---------------------------- +-- Records of sys_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `pid` bigint(20) DEFAULT NULL COMMENT '上级菜单ID', + `sub_count` int(5) DEFAULT 0 COMMENT '子菜单数目', + `type` int(11) DEFAULT NULL COMMENT '菜单类型', + `title` varchar(100) DEFAULT NULL COMMENT '菜单标题', + `name` varchar(100) DEFAULT NULL COMMENT '组件名称', `component` varchar(255) DEFAULT NULL COMMENT '组件', - `pid` bigint(20) NOT NULL COMMENT '上级菜单ID', - `sort` bigint(20) NOT NULL COMMENT '排序', + `menu_sort` int(5) DEFAULT NULL COMMENT '排序', `icon` varchar(255) DEFAULT NULL COMMENT '图标', `path` varchar(255) DEFAULT NULL COMMENT '链接地址', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Records of menu --- ---------------------------- -INSERT INTO `menu` VALUES ('1', '2018-12-18 15:11:29', '\0', '系统管理', null, '0', '1', 'system', 'system'); -INSERT INTO `menu` VALUES ('2', '2018-12-18 15:14:44', '\0', '用户管理', 'system/user/index', '1', '2', 'peoples', 'user'); -INSERT INTO `menu` VALUES ('3', '2018-12-18 15:16:07', '\0', '角色管理', 'system/role/index', '1', '3', 'role', 'role'); -INSERT INTO `menu` VALUES ('4', '2018-12-18 15:16:45', '\0', '权限管理', 'system/permission/index', '1', '4', 'permission', 'permission'); -INSERT INTO `menu` VALUES ('5', '2018-12-18 15:17:28', '\0', '菜单管理', 'system/menu/index', '1', '5', 'menu', 'menu'); -INSERT INTO `menu` VALUES ('6', '2018-12-18 15:17:48', '\0', '系统监控', null, '0', '10', 'monitor', 'monitor'); -INSERT INTO `menu` VALUES ('7', '2018-12-18 15:18:26', '\0', '操作日志', 'monitor/log/index', '6', '11', 'log', 'logs'); -INSERT INTO `menu` VALUES ('8', '2018-12-18 15:19:01', '\0', '系统缓存', 'monitor/redis/index', '6', '13', 'redis', 'redis'); -INSERT INTO `menu` VALUES ('9', '2018-12-18 15:19:34', '\0', 'SQL监控', 'monitor/sql/index', '6', '14', 'sqlMonitor', 'druid'); -INSERT INTO `menu` VALUES ('10', '2018-12-19 13:38:16', '\0', '组件管理', null, '0', '50', 'zujian', 'components'); -INSERT INTO `menu` VALUES ('11', '2018-12-19 13:38:49', '\0', '图标库', 'components/IconSelect', '10', '51', 'icon', 'icon'); -INSERT INTO `menu` VALUES ('14', '2018-12-27 10:13:09', '\0', '邮件工具', 'tools/email/index', '36', '24', 'email', 'email'); -INSERT INTO `menu` VALUES ('15', '2018-12-27 11:58:25', '\0', '富文本', 'components/Editor', '10', '52', 'fwb', 'tinymce'); -INSERT INTO `menu` VALUES ('16', '2018-12-28 09:36:53', '\0', '图床管理', 'tools/picture/index', '36', '25', 'image', 'pictures'); -INSERT INTO `menu` VALUES ('17', '2018-12-28 15:09:49', '', '项目地址', '', '0', '0', 'github', 'https://github.com/elunez/eladmin'); -INSERT INTO `menu` VALUES ('18', '2018-12-31 11:12:15', '\0', '七牛云存储', 'tools/qiniu/index', '36', '26', 'qiniu', 'qiniu'); -INSERT INTO `menu` VALUES ('19', '2018-12-31 14:52:38', '\0', '支付宝工具', 'tools/aliPay/index', '36', '27', 'alipay', 'aliPay'); -INSERT INTO `menu` VALUES ('21', '2019-01-04 16:22:03', '\0', '多级菜单', '', '0', '900', 'menu', 'nested'); -INSERT INTO `menu` VALUES ('22', '2019-01-04 16:23:29', '\0', '二级菜单1', 'nested/menu1/index', '21', '999', 'menu', 'menu1'); -INSERT INTO `menu` VALUES ('23', '2019-01-04 16:23:57', '\0', '二级菜单2', 'nested/menu2/index', '21', '999', 'menu', 'menu2'); -INSERT INTO `menu` VALUES ('24', '2019-01-04 16:24:48', '\0', '三级菜单1', 'nested/menu1/menu1-1', '22', '999', 'menu', 'menu1-1'); -INSERT INTO `menu` VALUES ('27', '2019-01-07 17:27:32', '\0', '三级菜单2', 'nested/menu1/menu1-2', '22', '999', 'menu', 'menu1-2'); -INSERT INTO `menu` VALUES ('28', '2019-01-07 20:34:40', '\0', '定时任务', 'system/timing/index', '36', '21', 'timing', 'timing'); -INSERT INTO `menu` VALUES ('30', '2019-01-11 15:45:55', '\0', '代码生成', 'generator/index', '36', '22', 'dev', 'generator'); -INSERT INTO `menu` VALUES ('32', '2019-01-13 13:49:03', '\0', '异常日志', 'monitor/log/errorLog', '6', '12', 'error', 'errorLog'); -INSERT INTO `menu` VALUES ('33', '2019-03-08 13:46:44', '\0', 'Markdown', 'components/MarkDown', '10', '53', 'markdown', 'markdown'); -INSERT INTO `menu` VALUES ('34', '2019-03-08 15:49:40', '\0', 'Yaml编辑器', 'components/YamlEdit', '10', '54', 'dev', 'yaml'); -INSERT INTO `menu` VALUES ('35', '2019-03-25 09:46:00', '\0', '部门管理', 'system/dept/index', '1', '6', 'dept', 'dept'); -INSERT INTO `menu` VALUES ('36', '2019-03-29 10:57:35', '\0', '系统工具', '', '0', '20', 'sys-tools', 'sys-tools'); -INSERT INTO `menu` VALUES ('37', '2019-03-29 13:51:18', '\0', '岗位管理', 'system/job/index', '1', '7', 'Steve-Jobs', 'job'); -INSERT INTO `menu` VALUES ('38', '2019-03-29 19:57:53', '\0', '接口文档', 'tools/swagger/index', '36', '23', 'swagger', 'swagger2'); -INSERT INTO `menu` VALUES ('39', '2019-04-10 11:49:04', '\0', '字典管理', 'system/dict/index', '1', '8', 'dictionary', 'dict'); - --- ---------------------------- --- Table structure for permission --- ---------------------------- -DROP TABLE IF EXISTS `permission`; -CREATE TABLE `permission` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `alias` varchar(255) DEFAULT NULL COMMENT '别名', + `i_frame` bit(1) DEFAULT NULL COMMENT '是否外链', + `cache` bit(1) DEFAULT b'0' COMMENT '缓存', + `hidden` bit(1) DEFAULT b'0' COMMENT '隐藏', + `permission` varchar(255) DEFAULT NULL COMMENT '权限', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', `create_time` datetime DEFAULT NULL COMMENT '创建日期', - `name` varchar(255) DEFAULT NULL COMMENT '名称', - `pid` int(11) NOT NULL COMMENT '上级权限', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Records of permission --- ---------------------------- -INSERT INTO `permission` VALUES ('1', '超级管理员', '2018-12-03 12:27:48', 'ADMIN', '0'); -INSERT INTO `permission` VALUES ('2', '用户管理', '2018-12-03 12:28:19', 'USER_ALL', '0'); -INSERT INTO `permission` VALUES ('3', '用户查询', '2018-12-03 12:31:35', 'USER_SELECT', '2'); -INSERT INTO `permission` VALUES ('4', '用户创建', '2018-12-03 12:31:35', 'USER_CREATE', '2'); -INSERT INTO `permission` VALUES ('5', '用户编辑', '2018-12-03 12:31:35', 'USER_EDIT', '2'); -INSERT INTO `permission` VALUES ('6', '用户删除', '2018-12-03 12:31:35', 'USER_DELETE', '2'); -INSERT INTO `permission` VALUES ('7', '角色管理', '2018-12-03 12:28:19', 'ROLES_ALL', '0'); -INSERT INTO `permission` VALUES ('8', '角色查询', '2018-12-03 12:31:35', 'ROLES_SELECT', '7'); -INSERT INTO `permission` VALUES ('10', '角色创建', '2018-12-09 20:10:16', 'ROLES_CREATE', '7'); -INSERT INTO `permission` VALUES ('11', '角色编辑', '2018-12-09 20:10:42', 'ROLES_EDIT', '7'); -INSERT INTO `permission` VALUES ('12', '角色删除', '2018-12-09 20:11:07', 'ROLES_DELETE', '7'); -INSERT INTO `permission` VALUES ('13', '权限管理', '2018-12-09 20:11:37', 'PERMISSION_ALL', '0'); -INSERT INTO `permission` VALUES ('14', '权限查询', '2018-12-09 20:11:55', 'PERMISSION_SELECT', '13'); -INSERT INTO `permission` VALUES ('15', '权限创建', '2018-12-09 20:14:10', 'PERMISSION_CREATE', '13'); -INSERT INTO `permission` VALUES ('16', '权限编辑', '2018-12-09 20:15:44', 'PERMISSION_EDIT', '13'); -INSERT INTO `permission` VALUES ('17', '权限删除', '2018-12-09 20:15:59', 'PERMISSION_DELETE', '13'); -INSERT INTO `permission` VALUES ('18', '缓存管理', '2018-12-17 13:53:25', 'REDIS_ALL', '0'); -INSERT INTO `permission` VALUES ('20', '缓存查询', '2018-12-17 13:54:07', 'REDIS_SELECT', '18'); -INSERT INTO `permission` VALUES ('22', '缓存删除', '2018-12-17 13:55:04', 'REDIS_DELETE', '18'); -INSERT INTO `permission` VALUES ('23', '图床管理', '2018-12-27 20:31:49', 'PICTURE_ALL', '0'); -INSERT INTO `permission` VALUES ('24', '查询图片', '2018-12-27 20:32:04', 'PICTURE_SELECT', '23'); -INSERT INTO `permission` VALUES ('25', '上传图片', '2018-12-27 20:32:24', 'PICTURE_UPLOAD', '23'); -INSERT INTO `permission` VALUES ('26', '删除图片', '2018-12-27 20:32:45', 'PICTURE_DELETE', '23'); -INSERT INTO `permission` VALUES ('29', '菜单管理', '2018-12-28 17:34:31', 'MENU_ALL', '0'); -INSERT INTO `permission` VALUES ('30', '菜单查询', '2018-12-28 17:34:41', 'MENU_SELECT', '29'); -INSERT INTO `permission` VALUES ('31', '菜单创建', '2018-12-28 17:34:52', 'MENU_CREATE', '29'); -INSERT INTO `permission` VALUES ('32', '菜单编辑', '2018-12-28 17:35:20', 'MENU_EDIT', '29'); -INSERT INTO `permission` VALUES ('33', '菜单删除', '2018-12-28 17:35:29', 'MENU_DELETE', '29'); -INSERT INTO `permission` VALUES ('35', '定时任务管理', '2019-01-08 14:59:57', 'JOB_ALL', '0'); -INSERT INTO `permission` VALUES ('36', '任务查询', '2019-01-08 15:00:09', 'JOB_SELECT', '35'); -INSERT INTO `permission` VALUES ('37', '任务创建', '2019-01-08 15:00:20', 'JOB_CREATE', '35'); -INSERT INTO `permission` VALUES ('38', '任务编辑', '2019-01-08 15:00:33', 'JOB_EDIT', '35'); -INSERT INTO `permission` VALUES ('39', '任务删除', '2019-01-08 15:01:13', 'JOB_DELETE', '35'); -INSERT INTO `permission` VALUES ('40', '部门管理', '2019-03-29 17:06:55', 'DEPT_ALL', '0'); -INSERT INTO `permission` VALUES ('41', '部门查询', '2019-03-29 17:07:09', 'DEPT_SELECT', '40'); -INSERT INTO `permission` VALUES ('42', '部门创建', '2019-03-29 17:07:29', 'DEPT_CREATE', '40'); -INSERT INTO `permission` VALUES ('43', '部门编辑', '2019-03-29 17:07:52', 'DEPT_EDIT', '40'); -INSERT INTO `permission` VALUES ('44', '部门删除', '2019-03-29 17:08:14', 'DEPT_DELETE', '40'); -INSERT INTO `permission` VALUES ('45', '岗位管理', '2019-03-29 17:08:52', 'USERJOB_ALL', '0'); -INSERT INTO `permission` VALUES ('46', '岗位查询', '2019-03-29 17:10:27', 'USERJOB_SELECT', '45'); -INSERT INTO `permission` VALUES ('47', '岗位创建', '2019-03-29 17:10:55', 'USERJOB_CREATE', '45'); -INSERT INTO `permission` VALUES ('48', '岗位编辑', '2019-03-29 17:11:08', 'USERJOB_EDIT', '45'); -INSERT INTO `permission` VALUES ('49', '岗位删除', '2019-03-29 17:11:19', 'USERJOB_DELETE', '45'); -INSERT INTO `permission` VALUES ('50', '字典管理', '2019-04-10 16:24:51', 'DICT_ALL', '0'); -INSERT INTO `permission` VALUES ('51', '字典查询', '2019-04-10 16:25:16', 'DICT_SELECT', '50'); -INSERT INTO `permission` VALUES ('52', '字典创建', '2019-04-10 16:25:29', 'DICT_CREATE', '50'); -INSERT INTO `permission` VALUES ('53', '字典编辑', '2019-04-10 16:27:19', 'DICT_EDIT', '50'); -INSERT INTO `permission` VALUES ('54', '字典删除', '2019-04-10 16:27:30', 'DICT_DELETE', '50'); - --- ---------------------------- --- Table structure for picture --- ---------------------------- -DROP TABLE IF EXISTS `picture`; -CREATE TABLE `picture` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `create_time` datetime DEFAULT NULL COMMENT '上传日期', - `delete_url` varchar(255) DEFAULT NULL COMMENT '删除的URL', - `filename` varchar(255) DEFAULT NULL COMMENT '图片名称', - `height` varchar(255) DEFAULT NULL COMMENT '图片高度', - `size` varchar(255) DEFAULT NULL COMMENT '图片大小', - `url` varchar(255) DEFAULT NULL COMMENT '图片地址', - `username` varchar(255) DEFAULT NULL COMMENT '用户名称', - `width` varchar(255) DEFAULT NULL COMMENT '图片宽度', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for qiniu_config --- ---------------------------- -DROP TABLE IF EXISTS `qiniu_config`; -CREATE TABLE `qiniu_config` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `access_key` text COMMENT 'accessKey', - `bucket` varchar(255) DEFAULT NULL COMMENT 'Bucket 识别符', - `host` varchar(255) NOT NULL COMMENT '外链域名', - `secret_key` text COMMENT 'secretKey', - `type` varchar(255) DEFAULT NULL COMMENT '空间类型', - `zone` varchar(255) DEFAULT NULL COMMENT '机房', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for qiniu_content --- ---------------------------- -DROP TABLE IF EXISTS `qiniu_content`; -CREATE TABLE `qiniu_content` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `bucket` varchar(255) DEFAULT NULL COMMENT 'Bucket 识别符', - `name` varchar(255) DEFAULT NULL COMMENT '文件名称', - `size` varchar(255) DEFAULT NULL COMMENT '文件大小', - `type` varchar(255) DEFAULT NULL COMMENT '文件类型:私有或公开', - `update_time` datetime DEFAULT NULL COMMENT '上传或同步的时间', - `url` varchar(255) DEFAULT NULL COMMENT '文件url', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for quartz_job --- ---------------------------- -DROP TABLE IF EXISTS `quartz_job`; -CREATE TABLE `quartz_job` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`menu_id`) USING BTREE, + UNIQUE KEY `uniq_name` (`name`), + UNIQUE KEY `uniq_title` (`title`), + KEY `idx_pid` (`pid`) +) ENGINE=InnoDB AUTO_INCREMENT=117 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='系统菜单'; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +BEGIN; +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, NULL, 7, 0, '系统管理', NULL, NULL, 1, 'system', 'system', b'0', b'0', b'0', NULL, NULL, 'admin', '2018-12-18 15:11:29', '2025-01-14 15:48:18'); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 1, 3, 1, '用户管理', 'User', 'system/user/index', 2, 'peoples', 'user', b'0', b'0', b'0', 'user:list', NULL, NULL, '2018-12-18 15:14:44', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (3, 1, 3, 1, '角色管理', 'Role', 'system/role/index', 3, 'role', 'role', b'0', b'0', b'0', 'roles:list', NULL, NULL, '2018-12-18 15:16:07', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 1, 3, 1, '菜单管理', 'Menu', 'system/menu/index', 5, 'menu', 'menu', b'0', b'0', b'0', 'menu:list', NULL, NULL, '2018-12-18 15:17:28', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, NULL, 5, 0, '系统监控', NULL, NULL, 10, 'monitor', 'monitor', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-18 15:17:48', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (7, 6, 0, 1, '操作日志', 'Log', 'monitor/log/index', 11, 'log', 'logs', b'0', b'1', b'0', NULL, NULL, 'admin', '2018-12-18 15:18:26', '2020-06-06 13:11:57'); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (9, 6, 0, 1, 'SQL监控', 'Sql', 'monitor/sql/index', 18, 'sqlMonitor', 'druid', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-18 15:19:34', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (10, NULL, 5, 0, '组件管理', NULL, NULL, 50, 'zujian', 'components', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-19 13:38:16', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (11, 10, 0, 1, '图标库', 'Icons', 'components/icons/index', 51, 'icon', 'icon', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-19 13:38:49', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (14, 36, 0, 1, '邮件工具', 'Email', 'tools/email/index', 35, 'email', 'email', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-27 10:13:09', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (15, 10, 0, 1, '富文本', 'Editor', 'components/Editor', 52, 'fwb', 'tinymce', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-27 11:58:25', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (18, 36, 3, 1, '存储管理', 'Storage', 'tools/storage/index', 34, 'qiniu', 'storage', b'0', b'0', b'0', 'storage:list', NULL, NULL, '2018-12-31 11:12:15', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (19, 36, 0, 1, '支付宝工具', 'AliPay', 'tools/aliPay/index', 37, 'alipay', 'aliPay', b'0', b'0', b'0', NULL, NULL, NULL, '2018-12-31 14:52:38', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (21, NULL, 2, 0, '多级菜单', NULL, '', 900, 'menu', 'nested', b'0', b'0', b'0', NULL, NULL, 'admin', '2019-01-04 16:22:03', '2020-06-21 17:27:35'); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (22, 21, 2, 0, '二级菜单1', NULL, '', 999, 'menu', 'menu1', b'0', b'0', b'0', NULL, NULL, 'admin', '2019-01-04 16:23:29', '2020-06-21 17:27:20'); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (23, 21, 0, 1, '二级菜单2', NULL, 'nested/menu2/index', 999, 'menu', 'menu2', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-04 16:23:57', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (24, 22, 0, 1, '三级菜单1', 'Test', 'nested/menu1/menu1-1', 999, 'menu', 'menu1-1', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-04 16:24:48', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (27, 22, 0, 1, '三级菜单2', NULL, 'nested/menu1/menu1-2', 999, 'menu', 'menu1-2', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-07 17:27:32', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (28, 1, 3, 1, '任务调度', 'Timing', 'system/timing/index', 999, 'timing', 'timing', b'0', b'0', b'0', 'timing:list', NULL, NULL, '2019-01-07 20:34:40', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (30, 36, 0, 1, '代码生成', 'GeneratorIndex', 'generator/index', 32, 'dev', 'generator', b'0', b'1', b'0', NULL, NULL, NULL, '2019-01-11 15:45:55', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (32, 6, 0, 1, '异常日志', 'ErrorLog', 'monitor/log/errorLog', 12, 'error', 'errorLog', b'0', b'0', b'0', NULL, NULL, NULL, '2019-01-13 13:49:03', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (33, 10, 0, 1, 'Markdown', 'Markdown', 'components/MarkDown', 53, 'markdown', 'markdown', b'0', b'0', b'0', NULL, NULL, NULL, '2019-03-08 13:46:44', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (34, 10, 0, 1, 'Yaml编辑器', 'YamlEdit', 'components/YamlEdit', 54, 'dev', 'yaml', b'0', b'0', b'0', NULL, NULL, NULL, '2019-03-08 15:49:40', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (35, 1, 3, 1, '部门管理', 'Dept', 'system/dept/index', 6, 'dept', 'dept', b'0', b'0', b'0', 'dept:list', NULL, NULL, '2019-03-25 09:46:00', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (36, NULL, 6, 0, '系统工具', NULL, '', 30, 'sys-tools', 'sys-tools', b'0', b'0', b'0', NULL, NULL, NULL, '2019-03-29 10:57:35', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (37, 1, 3, 1, '岗位管理', 'Job', 'system/job/index', 7, 'Steve-Jobs', 'job', b'0', b'0', b'0', 'job:list', NULL, NULL, '2019-03-29 13:51:18', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (39, 1, 3, 1, '字典管理', 'Dict', 'system/dict/index', 8, 'dictionary', 'dict', b'0', b'0', b'0', 'dict:list', NULL, NULL, '2019-04-10 11:49:04', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (41, 6, 0, 1, '在线用户', 'OnlineUser', 'monitor/online/index', 10, 'Steve-Jobs', 'online', b'0', b'0', b'0', NULL, NULL, NULL, '2019-10-26 22:08:43', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (44, 2, 0, 2, '用户新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'user:add', NULL, NULL, '2019-10-29 10:59:46', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (45, 2, 0, 2, '用户编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'user:edit', NULL, NULL, '2019-10-29 11:00:08', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (46, 2, 0, 2, '用户删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'user:del', NULL, NULL, '2019-10-29 11:00:23', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (48, 3, 0, 2, '角色创建', NULL, '', 2, '', '', b'0', b'0', b'0', 'roles:add', NULL, NULL, '2019-10-29 12:45:34', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (49, 3, 0, 2, '角色修改', NULL, '', 3, '', '', b'0', b'0', b'0', 'roles:edit', NULL, NULL, '2019-10-29 12:46:16', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (50, 3, 0, 2, '角色删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'roles:del', NULL, NULL, '2019-10-29 12:46:51', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (52, 5, 0, 2, '菜单新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'menu:add', NULL, NULL, '2019-10-29 12:55:07', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (53, 5, 0, 2, '菜单编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'menu:edit', NULL, NULL, '2019-10-29 12:55:40', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (54, 5, 0, 2, '菜单删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'menu:del', NULL, NULL, '2019-10-29 12:56:00', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (56, 35, 0, 2, '部门新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'dept:add', NULL, NULL, '2019-10-29 12:57:09', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (57, 35, 0, 2, '部门编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'dept:edit', NULL, NULL, '2019-10-29 12:57:27', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (58, 35, 0, 2, '部门删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'dept:del', NULL, NULL, '2019-10-29 12:57:41', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (60, 37, 0, 2, '岗位新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'job:add', NULL, NULL, '2019-10-29 12:58:27', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (61, 37, 0, 2, '岗位编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'job:edit', NULL, NULL, '2019-10-29 12:58:45', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (62, 37, 0, 2, '岗位删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'job:del', NULL, NULL, '2019-10-29 12:59:04', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (64, 39, 0, 2, '字典新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'dict:add', NULL, NULL, '2019-10-29 13:00:17', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (65, 39, 0, 2, '字典编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'dict:edit', NULL, NULL, '2019-10-29 13:00:42', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (66, 39, 0, 2, '字典删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'dict:del', NULL, NULL, '2019-10-29 13:00:59', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (73, 28, 0, 2, '任务新增', NULL, '', 2, '', '', b'0', b'0', b'0', 'timing:add', NULL, NULL, '2019-10-29 13:07:28', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (74, 28, 0, 2, '任务编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'timing:edit', NULL, NULL, '2019-10-29 13:07:41', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (75, 28, 0, 2, '任务删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'timing:del', NULL, NULL, '2019-10-29 13:07:54', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (77, 18, 0, 2, '上传文件', NULL, '', 2, '', '', b'0', b'0', b'0', 'storage:add', NULL, NULL, '2019-10-29 13:09:09', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (78, 18, 0, 2, '文件编辑', NULL, '', 3, '', '', b'0', b'0', b'0', 'storage:edit', NULL, NULL, '2019-10-29 13:09:22', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (79, 18, 0, 2, '文件删除', NULL, '', 4, '', '', b'0', b'0', b'0', 'storage:del', NULL, NULL, '2019-10-29 13:09:34', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (80, 6, 0, 1, '服务监控', 'ServerMonitor', 'monitor/server/index', 14, 'codeConsole', 'server', b'0', b'0', b'0', 'monitor:list', NULL, 'admin', '2019-11-07 13:06:39', '2020-05-04 18:20:50'); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (82, 36, 0, 1, '生成配置', 'GeneratorConfig', 'generator/config', 33, 'dev', 'generator/config/:tableName', b'0', b'1', b'1', '', NULL, NULL, '2019-11-17 20:08:56', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (83, 10, 0, 1, '图表库', 'Echarts', 'components/Echarts', 50, 'chart', 'echarts', b'0', b'1', b'0', '', NULL, NULL, '2019-11-21 09:04:32', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (90, NULL, 5, 1, '运维管理', 'Mnt', '', 20, 'mnt', 'mnt', b'0', b'0', b'0', NULL, NULL, NULL, '2019-11-09 10:31:08', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (92, 90, 3, 1, '服务器', 'ServerDeploy', 'maint/server/index', 22, 'server', 'maint/serverDeploy', b'0', b'0', b'0', 'serverDeploy:list', NULL, NULL, '2019-11-10 10:29:25', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (93, 90, 3, 1, '应用管理', 'App', 'maint/app/index', 23, 'app', 'maint/app', b'0', b'0', b'0', 'app:list', NULL, NULL, '2019-11-10 11:05:16', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (94, 90, 3, 1, '部署管理', 'Deploy', 'maint/deploy/index', 24, 'deploy', 'maint/deploy', b'0', b'0', b'0', 'deploy:list', NULL, NULL, '2019-11-10 15:56:55', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (97, 90, 1, 1, '部署备份', 'DeployHistory', 'maint/deployHistory/index', 25, 'backup', 'maint/deployHistory', b'0', b'0', b'0', 'deployHistory:list', NULL, NULL, '2019-11-10 16:49:44', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (98, 90, 3, 1, '数据库管理', 'Database', 'maint/database/index', 26, 'database', 'maint/database', b'0', b'0', b'0', 'database:list', NULL, NULL, '2019-11-10 20:40:04', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (102, 97, 0, 2, '删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'deployHistory:del', NULL, NULL, '2019-11-17 09:32:48', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (103, 92, 0, 2, '服务器新增', NULL, '', 999, '', '', b'0', b'0', b'0', 'serverDeploy:add', NULL, NULL, '2019-11-17 11:08:33', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (104, 92, 0, 2, '服务器编辑', NULL, '', 999, '', '', b'0', b'0', b'0', 'serverDeploy:edit', NULL, NULL, '2019-11-17 11:08:57', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (105, 92, 0, 2, '服务器删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'serverDeploy:del', NULL, NULL, '2019-11-17 11:09:15', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (106, 93, 0, 2, '应用新增', NULL, '', 999, '', '', b'0', b'0', b'0', 'app:add', NULL, NULL, '2019-11-17 11:10:03', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (107, 93, 0, 2, '应用编辑', NULL, '', 999, '', '', b'0', b'0', b'0', 'app:edit', NULL, NULL, '2019-11-17 11:10:28', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (108, 93, 0, 2, '应用删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'app:del', NULL, NULL, '2019-11-17 11:10:55', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (109, 94, 0, 2, '部署新增', NULL, '', 999, '', '', b'0', b'0', b'0', 'deploy:add', NULL, NULL, '2019-11-17 11:11:22', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (110, 94, 0, 2, '部署编辑', NULL, '', 999, '', '', b'0', b'0', b'0', 'deploy:edit', NULL, NULL, '2019-11-17 11:11:41', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (111, 94, 0, 2, '部署删除', NULL, '', 999, '', '', b'0', b'0', b'0', 'deploy:del', NULL, NULL, '2019-11-17 11:12:01', NULL); +INSERT INTO `sys_menu` (`menu_id`, `pid`, `sub_count`, `type`, `title`, `name`, `component`, `menu_sort`, `icon`, `path`, `i_frame`, `cache`, `hidden`, `permission`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (116, 36, 0, 1, '生成预览', 'Preview', 'generator/preview', 999, 'java', 'generator/preview/:tableName', b'0', b'1', b'1', NULL, NULL, NULL, '2019-11-26 14:54:36', NULL); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_quartz_job +-- ---------------------------- +DROP TABLE IF EXISTS `sys_quartz_job`; +CREATE TABLE `sys_quartz_job` ( + `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `bean_name` varchar(255) DEFAULT NULL COMMENT 'Spring Bean名称', `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron 表达式', `is_pause` bit(1) DEFAULT NULL COMMENT '状态:1暂停、0启用', `job_name` varchar(255) DEFAULT NULL COMMENT '任务名称', `method_name` varchar(255) DEFAULT NULL COMMENT '方法名称', `params` varchar(255) DEFAULT NULL COMMENT '参数', - `remark` varchar(255) DEFAULT NULL COMMENT '备注', - `update_time` datetime DEFAULT NULL COMMENT '创建或更新日期', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; + `description` varchar(255) DEFAULT NULL COMMENT '备注', + `person_in_charge` varchar(100) DEFAULT NULL COMMENT '负责人', + `email` varchar(100) DEFAULT NULL COMMENT '报警邮箱', + `sub_task` varchar(100) DEFAULT NULL COMMENT '子任务ID', + `pause_after_failure` bit(1) DEFAULT NULL COMMENT '任务失败后是否暂停', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `create_time` datetime DEFAULT NULL COMMENT '创建日期', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`job_id`) USING BTREE, + KEY `idx_is_pause` (`is_pause`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='定时任务'; -- ---------------------------- --- Records of quartz_job +-- Records of sys_quartz_job -- ---------------------------- -INSERT INTO `quartz_job` VALUES ('1', 'visitsTask', '0 0 0 * * ?', '\0', '更新访客记录', 'run', null, '每日0点创建新的访客记录', '2019-01-08 14:53:31'); -INSERT INTO `quartz_job` VALUES ('2', 'testTask', '0/5 * * * * ?', '', '测试1', 'run1', 'test', '带参测试,多参使用json', '2019-01-13 14:20:50'); -INSERT INTO `quartz_job` VALUES ('3', 'testTask', '0/5 * * * * ?', '', '测试', 'run', '', '不带参测试', '2019-04-09 16:16:44'); +BEGIN; +INSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, 'testTask', '0/5 * * * * ?', b'1', '测试1', 'run1', 'test', '带参测试,多参使用json', '测试', NULL, NULL, NULL, NULL, 'admin', '2019-08-22 14:08:29', '2020-05-24 13:58:33'); +INSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (3, 'testTask', '0/5 * * * * ?', b'1', '测试', 'run', '', '不带参测试', 'Zheng Jie', '', '6', b'1', NULL, 'admin', '2019-09-26 16:44:39', '2020-05-24 14:48:12'); +INSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (5, 'Test', '0/5 * * * * ?', b'1', '任务告警测试', 'run', NULL, '测试', 'test', '', NULL, b'1', 'admin', 'admin', '2020-05-05 20:32:41', '2020-05-05 20:36:13'); +INSERT INTO `sys_quartz_job` (`job_id`, `bean_name`, `cron_expression`, `is_pause`, `job_name`, `method_name`, `params`, `description`, `person_in_charge`, `email`, `sub_task`, `pause_after_failure`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (6, 'testTask', '0/5 * * * * ?', b'1', '测试3', 'run2', NULL, '测试3', 'Zheng Jie', '', NULL, b'1', 'admin', 'admin', '2020-05-05 20:35:41', '2020-05-05 20:36:07'); +COMMIT; -- ---------------------------- --- Table structure for quartz_log +-- Table structure for sys_quartz_log -- ---------------------------- -DROP TABLE IF EXISTS `quartz_log`; -CREATE TABLE `quartz_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `baen_name` varchar(255) DEFAULT NULL, - `create_time` datetime DEFAULT NULL, - `cron_expression` varchar(255) DEFAULT NULL, - `exception_detail` text, - `is_success` bit(1) DEFAULT NULL, - `job_name` varchar(255) DEFAULT NULL, - `method_name` varchar(255) DEFAULT NULL, - `params` varchar(255) DEFAULT NULL, - `time` bigint(20) DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `sys_quartz_log`; +CREATE TABLE `sys_quartz_log` ( + `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `bean_name` varchar(255) DEFAULT NULL COMMENT 'Bean名称', + `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron 表达式', + `is_success` bit(1) DEFAULT NULL COMMENT '是否执行成功', + `job_name` varchar(255) DEFAULT NULL COMMENT '任务名称', + `method_name` varchar(255) DEFAULT NULL COMMENT '方法名称', + `params` varchar(255) DEFAULT NULL COMMENT '参数', + `time` bigint(20) DEFAULT NULL COMMENT '执行耗时', + `exception_detail` text DEFAULT NULL COMMENT '异常详情', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`log_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=262 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='定时任务日志'; -- ---------------------------- --- Records of quartz_log +-- Records of sys_quartz_log -- ---------------------------- +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for role +-- Table structure for sys_role -- ---------------------------- -DROP TABLE IF EXISTS `role`; -CREATE TABLE `role` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(100) NOT NULL COMMENT '名称', + `level` int(50) DEFAULT NULL COMMENT '角色级别', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `data_scope` varchar(255) DEFAULT NULL COMMENT '数据权限', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', `create_time` datetime DEFAULT NULL COMMENT '创建日期', - `name` varchar(255) NOT NULL COMMENT '名称', - `remark` varchar(255) DEFAULT NULL COMMENT '备注', - `data_scope` varchar(255) DEFAULT NULL, - `level` int(255) DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`role_id`) USING BTREE, + UNIQUE KEY `uniq_name` (`name`), + KEY `idx_level` (`level`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色表'; -- ---------------------------- --- Records of role +-- Records of sys_role -- ---------------------------- -INSERT INTO `role` VALUES ('1', '2018-11-23 11:04:37', '超级管理员', '系统所有权', '全部', '1'); -INSERT INTO `role` VALUES ('2', '2018-11-23 13:09:06', '普通用户', '用于测试菜单与权限', '自定义', '3'); -INSERT INTO `role` VALUES ('4', '2019-05-13 14:16:15', '普通管理员', '普通管理员级别为2,使用该角色新增用户时只能赋予比普通管理员级别低的角色', '自定义', '2'); +BEGIN; +INSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, '管理员', 1, '-', '全部', NULL, 'admin', '2018-11-23 11:04:37', '2025-01-21 14:53:13'); +INSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, '普通用户', 2, '-', '本级', NULL, 'admin', '2018-11-23 13:09:06', '2020-09-05 10:45:12'); +COMMIT; -- ---------------------------- --- Table structure for roles_depts +-- Table structure for sys_roles_depts -- ---------------------------- -DROP TABLE IF EXISTS `roles_depts`; -CREATE TABLE `roles_depts` ( - `role_id` bigint(20) NOT NULL, - `dept_id` bigint(20) NOT NULL, - PRIMARY KEY (`role_id`,`dept_id`), - KEY `FK7qg6itn5ajdoa9h9o78v9ksur` (`dept_id`), - CONSTRAINT `FK7qg6itn5ajdoa9h9o78v9ksur` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`id`), - CONSTRAINT `FKrg1ci4cxxfbja0sb0pddju7k` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `sys_roles_depts`; +CREATE TABLE `sys_roles_depts` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `dept_id` bigint(20) NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`,`dept_id`) USING BTREE, + KEY `idx_role_id` (`role_id`), + KEY `idx_dept_id` (`dept_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色部门关联'; -- ---------------------------- --- Records of roles_depts +-- Records of sys_roles_depts -- ---------------------------- -INSERT INTO `roles_depts` VALUES ('2', '5'); -INSERT INTO `roles_depts` VALUES ('4', '6'); -INSERT INTO `roles_depts` VALUES ('4', '7'); -INSERT INTO `roles_depts` VALUES ('2', '8'); +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for roles_menus +-- Table structure for sys_roles_menus -- ---------------------------- -DROP TABLE IF EXISTS `roles_menus`; -CREATE TABLE `roles_menus` ( +DROP TABLE IF EXISTS `sys_roles_menus`; +CREATE TABLE `sys_roles_menus` ( `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', `role_id` bigint(20) NOT NULL COMMENT '角色ID', PRIMARY KEY (`menu_id`,`role_id`) USING BTREE, - KEY `FKcngg2qadojhi3a651a5adkvbq` (`role_id`) USING BTREE, - CONSTRAINT `FKcngg2qadojhi3a651a5adkvbq` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`), - CONSTRAINT `FKq1knxf8ykt26we8k331naabjx` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- ---------------------------- --- Records of roles_menus --- ---------------------------- -INSERT INTO `roles_menus` VALUES ('1', '1'); -INSERT INTO `roles_menus` VALUES ('2', '1'); -INSERT INTO `roles_menus` VALUES ('3', '1'); -INSERT INTO `roles_menus` VALUES ('4', '1'); -INSERT INTO `roles_menus` VALUES ('5', '1'); -INSERT INTO `roles_menus` VALUES ('6', '1'); -INSERT INTO `roles_menus` VALUES ('7', '1'); -INSERT INTO `roles_menus` VALUES ('8', '1'); -INSERT INTO `roles_menus` VALUES ('9', '1'); -INSERT INTO `roles_menus` VALUES ('10', '1'); -INSERT INTO `roles_menus` VALUES ('11', '1'); -INSERT INTO `roles_menus` VALUES ('14', '1'); -INSERT INTO `roles_menus` VALUES ('15', '1'); -INSERT INTO `roles_menus` VALUES ('16', '1'); -INSERT INTO `roles_menus` VALUES ('17', '1'); -INSERT INTO `roles_menus` VALUES ('18', '1'); -INSERT INTO `roles_menus` VALUES ('19', '1'); -INSERT INTO `roles_menus` VALUES ('21', '1'); -INSERT INTO `roles_menus` VALUES ('22', '1'); -INSERT INTO `roles_menus` VALUES ('23', '1'); -INSERT INTO `roles_menus` VALUES ('24', '1'); -INSERT INTO `roles_menus` VALUES ('27', '1'); -INSERT INTO `roles_menus` VALUES ('28', '1'); -INSERT INTO `roles_menus` VALUES ('30', '1'); -INSERT INTO `roles_menus` VALUES ('32', '1'); -INSERT INTO `roles_menus` VALUES ('33', '1'); -INSERT INTO `roles_menus` VALUES ('34', '1'); -INSERT INTO `roles_menus` VALUES ('35', '1'); -INSERT INTO `roles_menus` VALUES ('36', '1'); -INSERT INTO `roles_menus` VALUES ('37', '1'); -INSERT INTO `roles_menus` VALUES ('38', '1'); -INSERT INTO `roles_menus` VALUES ('39', '1'); -INSERT INTO `roles_menus` VALUES ('1', '2'); -INSERT INTO `roles_menus` VALUES ('2', '2'); -INSERT INTO `roles_menus` VALUES ('3', '2'); -INSERT INTO `roles_menus` VALUES ('4', '2'); -INSERT INTO `roles_menus` VALUES ('5', '2'); -INSERT INTO `roles_menus` VALUES ('6', '2'); -INSERT INTO `roles_menus` VALUES ('8', '2'); -INSERT INTO `roles_menus` VALUES ('9', '2'); -INSERT INTO `roles_menus` VALUES ('10', '2'); -INSERT INTO `roles_menus` VALUES ('11', '2'); -INSERT INTO `roles_menus` VALUES ('14', '2'); -INSERT INTO `roles_menus` VALUES ('15', '2'); -INSERT INTO `roles_menus` VALUES ('16', '2'); -INSERT INTO `roles_menus` VALUES ('17', '2'); -INSERT INTO `roles_menus` VALUES ('18', '2'); -INSERT INTO `roles_menus` VALUES ('19', '2'); -INSERT INTO `roles_menus` VALUES ('21', '2'); -INSERT INTO `roles_menus` VALUES ('22', '2'); -INSERT INTO `roles_menus` VALUES ('23', '2'); -INSERT INTO `roles_menus` VALUES ('24', '2'); -INSERT INTO `roles_menus` VALUES ('27', '2'); -INSERT INTO `roles_menus` VALUES ('28', '2'); -INSERT INTO `roles_menus` VALUES ('30', '2'); -INSERT INTO `roles_menus` VALUES ('33', '2'); -INSERT INTO `roles_menus` VALUES ('34', '2'); -INSERT INTO `roles_menus` VALUES ('35', '2'); -INSERT INTO `roles_menus` VALUES ('36', '2'); -INSERT INTO `roles_menus` VALUES ('37', '2'); -INSERT INTO `roles_menus` VALUES ('38', '2'); -INSERT INTO `roles_menus` VALUES ('39', '2'); -INSERT INTO `roles_menus` VALUES ('1', '4'); -INSERT INTO `roles_menus` VALUES ('2', '4'); - --- ---------------------------- --- Table structure for roles_permissions --- ---------------------------- -DROP TABLE IF EXISTS `roles_permissions`; -CREATE TABLE `roles_permissions` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `permission_id` bigint(20) NOT NULL COMMENT '权限ID', - PRIMARY KEY (`role_id`,`permission_id`) USING BTREE, - KEY `FKboeuhl31go7wer3bpy6so7exi` (`permission_id`) USING BTREE, - CONSTRAINT `FK4hrolwj4ned5i7qe8kyiaak6m` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`), - CONSTRAINT `FKboeuhl31go7wer3bpy6so7exi` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- ---------------------------- --- Records of roles_permissions --- ---------------------------- -INSERT INTO `roles_permissions` VALUES ('1', '1'); -INSERT INTO `roles_permissions` VALUES ('2', '3'); -INSERT INTO `roles_permissions` VALUES ('4', '3'); -INSERT INTO `roles_permissions` VALUES ('4', '4'); -INSERT INTO `roles_permissions` VALUES ('4', '5'); -INSERT INTO `roles_permissions` VALUES ('2', '8'); -INSERT INTO `roles_permissions` VALUES ('2', '14'); -INSERT INTO `roles_permissions` VALUES ('2', '20'); -INSERT INTO `roles_permissions` VALUES ('2', '23'); -INSERT INTO `roles_permissions` VALUES ('2', '24'); -INSERT INTO `roles_permissions` VALUES ('2', '25'); -INSERT INTO `roles_permissions` VALUES ('2', '26'); -INSERT INTO `roles_permissions` VALUES ('2', '30'); -INSERT INTO `roles_permissions` VALUES ('2', '36'); -INSERT INTO `roles_permissions` VALUES ('2', '41'); -INSERT INTO `roles_permissions` VALUES ('2', '46'); -INSERT INTO `roles_permissions` VALUES ('2', '51'); - --- ---------------------------- --- Table structure for user --- ---------------------------- -DROP TABLE IF EXISTS `user`; -CREATE TABLE `user` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', - `create_time` datetime DEFAULT NULL COMMENT '创建日期', - `email` varchar(255) DEFAULT NULL COMMENT '邮箱', - `enabled` bigint(20) DEFAULT NULL COMMENT '状态:1启用、0禁用', + KEY `idx_menu_id` (`menu_id`), + KEY `idx_role_id` (`role_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色菜单关联'; + +-- ---------------------------- +-- Records of sys_roles_menus +-- ---------------------------- +BEGIN; +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (1, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (1, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (2, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (2, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (3, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (5, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (6, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (6, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (7, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (7, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (9, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (9, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (10, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (10, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (11, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (11, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (14, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (14, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (15, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (15, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (18, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (19, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (19, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (21, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (21, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (22, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (22, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (23, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (23, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (24, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (24, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (27, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (27, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (28, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (30, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (30, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (32, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (32, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (33, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (33, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (34, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (34, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (35, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (36, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (36, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (37, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (39, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (41, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (44, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (45, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (46, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (48, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (49, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (50, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (52, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (53, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (54, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (56, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (57, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (58, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (60, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (61, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (62, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (64, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (65, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (66, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (73, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (74, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (75, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (77, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (78, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (79, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (80, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (80, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (82, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (82, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (83, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (83, 2); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (90, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (92, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (93, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (94, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (97, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (98, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (102, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (103, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (104, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (105, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (106, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (107, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (108, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (109, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (110, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (111, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (116, 1); +INSERT INTO `sys_roles_menus` (`menu_id`, `role_id`) VALUES (116, 2); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `dept_id` bigint(20) DEFAULT NULL COMMENT '部门名称', + `username` varchar(180) DEFAULT NULL COMMENT '用户名', + `nick_name` varchar(255) DEFAULT NULL COMMENT '昵称', + `gender` varchar(2) DEFAULT NULL COMMENT '性别', + `phone` varchar(255) DEFAULT NULL COMMENT '手机号码', + `email` varchar(180) DEFAULT NULL COMMENT '邮箱', + `avatar_name` varchar(255) DEFAULT NULL COMMENT '头像地址', + `avatar_path` varchar(255) DEFAULT NULL COMMENT '头像真实路径', `password` varchar(255) DEFAULT NULL COMMENT '密码', - `username` varchar(255) DEFAULT NULL COMMENT '用户名', - `last_password_reset_time` datetime DEFAULT NULL COMMENT '最后修改密码的日期', - `dept_id` bigint(20) DEFAULT NULL, - `phone` varchar(255) DEFAULT NULL, - `job_id` bigint(20) DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE KEY `UK_kpubos9gc2cvtkb0thktkbkes` (`email`) USING BTREE, - UNIQUE KEY `username` (`username`) USING BTREE, - KEY `FK5rwmryny6jthaaxkogownknqp` (`dept_id`), - KEY `FKfftoc2abhot8f2wu6cl9a5iky` (`job_id`), - CONSTRAINT `FK5rwmryny6jthaaxkogownknqp` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`id`), - CONSTRAINT `FKfftoc2abhot8f2wu6cl9a5iky` FOREIGN KEY (`job_id`) REFERENCES `job` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; + `is_admin` bit(1) DEFAULT b'0' COMMENT '是否为admin账号', + `enabled` bit(1) DEFAULT NULL COMMENT '状态:1启用、0禁用', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', + `pwd_reset_time` datetime DEFAULT NULL COMMENT '修改密码的时间', + `create_time` datetime DEFAULT NULL COMMENT '创建日期', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`user_id`) USING BTREE, + UNIQUE KEY `uniq_email` (`email`) USING BTREE, + UNIQUE KEY `uniq_username` (`username`) USING BTREE, + KEY `idx_dept_id` (`dept_id`) USING BTREE, + KEY `idx_enabled` (`enabled`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='系统用户'; -- ---------------------------- --- Records of user +-- Records of sys_user -- ---------------------------- -INSERT INTO `user` VALUES ('1', 'https://i.loli.net/2019/04/04/5ca5b971e1548.jpeg', '2018-08-23 09:11:56', 'admin@eladmin.net', '1', 'e10adc3949ba59abbe56e057f20f883e', 'admin', '2019-05-18 17:34:21', '2', '18888888888', '11'); -INSERT INTO `user` VALUES ('3', 'https://aurora-1255840532.cos.ap-chengdu.myqcloud.com/8918a306ea314404835a9196585c4b75.jpeg', '2018-12-27 20:05:26', 'test@eladmin.net', '1', 'e10adc3949ba59abbe56e057f20f883e', 'test', '2019-04-01 09:15:24', '2', '17777777777', '12'); -INSERT INTO `user` VALUES ('5', 'https://aurora-1255840532.cos.ap-chengdu.myqcloud.com/8918a306ea314404835a9196585c4b75.jpeg', '2019-04-02 10:07:12', 'hr@eladmin.net', '1', 'e10adc3949ba59abbe56e057f20f883e', 'hr', null, '11', '15555555555', '8'); +BEGIN; +INSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (1, 2, 'admin', '管理员', '男', '18888888888', '201507802@qq.com', 'avatar-20250122102642222.png', '/Users/jie/Documents/work/private/eladmin/~/avatar/avatar-20250122102642222.png', '$2a$10$Egp1/gvFlt7zhlXVfEFw4OfWQCGPw0ClmMcc6FjTnvXNRVf9zdMRa', b'1', b'1', NULL, 'admin', '2020-05-03 16:38:31', '2018-08-23 09:11:56', '2025-01-22 10:26:42'); +INSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (2, 7, 'test', '测试', '男', '19999999999', '231@qq.com', NULL, NULL, '$2a$10$4XcyudOYTSz6fue6KFNMHeUQnCX5jbBQypLEnGk1PmekXt5c95JcK', b'0', b'1', 'admin', 'admin', NULL, '2020-05-05 11:15:49', '2025-01-21 14:53:04'); +COMMIT; -- ---------------------------- --- Table structure for users_roles +-- Table structure for sys_users_jobs -- ---------------------------- -DROP TABLE IF EXISTS `users_roles`; -CREATE TABLE `users_roles` ( +DROP TABLE IF EXISTS `sys_users_jobs`; +CREATE TABLE `sys_users_jobs` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `job_id` bigint(20) NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`,`job_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_job_id` (`job_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci COMMENT='用户与岗位关联表'; + +-- ---------------------------- +-- Records of sys_users_jobs +-- ---------------------------- +BEGIN; +INSERT INTO `sys_users_jobs` (`user_id`, `job_id`) VALUES (1, 11); +INSERT INTO `sys_users_jobs` (`user_id`, `job_id`) VALUES (2, 12); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_users_roles +-- ---------------------------- +DROP TABLE IF EXISTS `sys_users_roles`; +CREATE TABLE `sys_users_roles` ( `user_id` bigint(20) NOT NULL COMMENT '用户ID', `role_id` bigint(20) NOT NULL COMMENT '角色ID', PRIMARY KEY (`user_id`,`role_id`) USING BTREE, - KEY `FKq4eq273l04bpu4efj0jd0jb98` (`role_id`) USING BTREE, - CONSTRAINT `users_roles_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`), - CONSTRAINT `users_roles_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; + KEY `idx_user_id` (`user_id`), + KEY `idx_role_id` (`role_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='用户角色关联'; + +-- ---------------------------- +-- Records of sys_users_roles +-- ---------------------------- +BEGIN; +INSERT INTO `sys_users_roles` (`user_id`, `role_id`) VALUES (1, 1); +INSERT INTO `sys_users_roles` (`user_id`, `role_id`) VALUES (2, 2); +COMMIT; -- ---------------------------- --- Records of users_roles +-- Table structure for tool_alipay_config -- ---------------------------- -INSERT INTO `users_roles` VALUES ('1', '1'); -INSERT INTO `users_roles` VALUES ('3', '2'); -INSERT INTO `users_roles` VALUES ('5', '4'); +DROP TABLE IF EXISTS `tool_alipay_config`; +CREATE TABLE `tool_alipay_config` ( + `config_id` bigint(20) NOT NULL COMMENT 'ID', + `app_id` varchar(255) DEFAULT NULL COMMENT '应用ID', + `charset` varchar(255) DEFAULT NULL COMMENT '编码', + `format` varchar(255) DEFAULT NULL COMMENT '类型 固定格式json', + `gateway_url` varchar(255) DEFAULT NULL COMMENT '网关地址', + `notify_url` varchar(255) DEFAULT NULL COMMENT '异步回调', + `private_key` text DEFAULT NULL COMMENT '私钥', + `public_key` text DEFAULT NULL COMMENT '公钥', + `return_url` varchar(255) DEFAULT NULL COMMENT '回调地址', + `sign_type` varchar(255) DEFAULT NULL COMMENT '签名方式', + `sys_service_provider_id` varchar(255) DEFAULT NULL COMMENT '商户号', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='支付宝配置类'; + +-- ---------------------------- +-- Records of tool_alipay_config +-- ---------------------------- +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for verification_code +-- Table structure for tool_email_config -- ---------------------------- -DROP TABLE IF EXISTS `verification_code`; -CREATE TABLE `verification_code` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `code` varchar(255) DEFAULT NULL COMMENT '验证码', +DROP TABLE IF EXISTS `tool_email_config`; +CREATE TABLE `tool_email_config` ( + `config_id` bigint(20) NOT NULL COMMENT 'ID', + `from_user` varchar(255) DEFAULT NULL COMMENT '收件人', + `host` varchar(255) DEFAULT NULL COMMENT '邮件服务器SMTP地址', + `pass` varchar(255) DEFAULT NULL COMMENT '密码', + `port` varchar(255) DEFAULT NULL COMMENT '端口', + `user` varchar(255) DEFAULT NULL COMMENT '发件者用户名', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='邮箱配置'; + +-- ---------------------------- +-- Records of tool_email_config +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for tool_local_storage +-- ---------------------------- +DROP TABLE IF EXISTS `tool_local_storage`; +CREATE TABLE `tool_local_storage` ( + `storage_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `real_name` varchar(255) DEFAULT NULL COMMENT '文件真实的名称', + `name` varchar(255) DEFAULT NULL COMMENT '文件名', + `suffix` varchar(255) DEFAULT NULL COMMENT '后缀', + `path` varchar(255) DEFAULT NULL COMMENT '路径', + `type` varchar(255) DEFAULT NULL COMMENT '类型', + `size` varchar(100) DEFAULT NULL COMMENT '大小', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', + `update_by` varchar(255) DEFAULT NULL COMMENT '更新者', `create_time` datetime DEFAULT NULL COMMENT '创建日期', - `status` bit(1) DEFAULT NULL COMMENT '状态:1有效、0过期', - `type` varchar(255) DEFAULT NULL COMMENT '验证码类型:email或者短信', - `value` varchar(255) DEFAULT NULL COMMENT '接收邮箱或者手机号码', - `scenes` varchar(255) DEFAULT NULL COMMENT '业务名称:如重置邮箱、重置密码等', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`storage_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='本地存储'; -- ---------------------------- --- Records of verification_code +-- Records of tool_local_storage -- ---------------------------- +BEGIN; +COMMIT; -- ---------------------------- --- Table structure for visits +-- Table structure for tool_s3_storage -- ---------------------------- -DROP TABLE IF EXISTS `visits`; -CREATE TABLE `visits` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `create_time` datetime DEFAULT NULL, - `date` varchar(255) DEFAULT NULL, - `ip_counts` bigint(20) DEFAULT NULL, - `pv_counts` bigint(20) DEFAULT NULL, - `week_day` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `UK_11aksgq87euk9bcyeesfs4vtp` (`date`) -) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `tool_s3_storage`; +CREATE TABLE `tool_s3_storage` ( + `storage_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `file_name` varchar(255) NOT NULL COMMENT '文件名称', + `file_real_name` varchar(255) NOT NULL COMMENT '真实存储的名称', + `file_size` varchar(100) NOT NULL COMMENT '文件大小', + `file_mime_type` varchar(50) NOT NULL COMMENT '文件MIME 类型', + `file_type` varchar(50) NOT NULL COMMENT '文件类型', + `file_path` tinytext NOT NULL COMMENT '文件路径', + `create_by` varchar(255) NOT NULL COMMENT '创建者', + `update_by` varchar(255) NOT NULL COMMENT '更新者', + `create_time` datetime NOT NULL COMMENT '创建日期', + `update_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`storage_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='s3 协议对象存储'; + +-- ---------------------------- +-- Records of tool_s3_storage +-- ---------------------------- +BEGIN; +INSERT INTO `tool_s3_storage` (`storage_id`, `file_name`, `file_real_name`, `file_size`, `file_mime_type`, `file_type`, `file_path`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 'tx.jpg', '2ca1de24d8fa422eae4ede30e97c46d8.jpg', '29.67KB', 'image/jpeg', 'jpg', '2025-06/2ca1de24d8fa422eae4ede30e97c46d8.jpg', 'admin', 'admin', '2025-06-25 15:48:22', '2025-06-25 15:48:22'); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/sql/quartz.sql b/sql/quartz.sql new file mode 100644 index 000000000..0c71cb596 --- /dev/null +++ b/sql/quartz.sql @@ -0,0 +1,170 @@ +drop table if exists qrtz_fired_triggers; +drop table if exists qrtz_paused_trigger_grps; +drop table if exists qrtz_scheduler_state; +drop table if exists qrtz_locks; +drop table if exists qrtz_simple_triggers; +drop table if exists qrtz_simprop_triggers; +drop table if exists qrtz_cron_triggers; +drop table if exists qrtz_blob_triggers; +drop table if exists qrtz_triggers; +drop table if exists qrtz_job_details; +drop table if exists qrtz_calendars; + +create table qrtz_job_details( +sched_name varchar(120) not null, +job_name varchar(200) not null, +job_group varchar(200) not null, +description varchar(250) null, +job_class_name varchar(250) not null, +is_durable varchar(1) not null, +is_nonconcurrent varchar(1) not null, +is_update_data varchar(1) not null, +requests_recovery varchar(1) not null, +job_data blob null, +primary key (sched_name, job_name, job_group)) +engine=innodb; + +create table qrtz_triggers ( +sched_name varchar(120) not null, +trigger_name varchar(200) not null, +trigger_group varchar(200) not null, +job_name varchar(200) not null, +job_group varchar(200) not null, +description varchar(250) null, +next_fire_time bigint(13) null, +prev_fire_time bigint(13) null, +priority integer null, +trigger_state varchar(16) not null, +trigger_type varchar(8) not null, +start_time bigint(13) not null, +end_time bigint(13) null, +calendar_name varchar(200) null, +misfire_instr smallint(2) null, +job_data blob null, +primary key (sched_name, trigger_name, trigger_group), +foreign key (sched_name, job_name, job_group) +references qrtz_job_details(sched_name, job_name, job_group)) +engine=innodb; + +create table qrtz_simple_triggers ( +sched_name varchar(120) not null, +trigger_name varchar(200) not null, +trigger_group varchar(200) not null, +repeat_count bigint(7) not null, +repeat_interval bigint(12) not null, +times_triggered bigint(10) not null, +primary key (sched_name, trigger_name, trigger_group), +foreign key (sched_name, trigger_name, trigger_group) +references qrtz_triggers(sched_name, trigger_name, trigger_group)) +engine=innodb; + +create table qrtz_cron_triggers ( +sched_name varchar(120) not null, +trigger_name varchar(200) not null, +trigger_group varchar(200) not null, +cron_expression varchar(120) not null, +time_zone_id varchar(80), +primary key (sched_name, trigger_name, trigger_group), +foreign key (sched_name, trigger_name, trigger_group) +references qrtz_triggers(sched_name, trigger_name, trigger_group)) +engine=innodb; + +create table qrtz_simprop_triggers ( +sched_name varchar(120) not null, +trigger_name varchar(200) not null, +trigger_group varchar(200) not null, +str_prop_1 varchar(512) null, +str_prop_2 varchar(512) null, +str_prop_3 varchar(512) null, +int_prop_1 int null, +int_prop_2 int null, +long_prop_1 bigint null, +long_prop_2 bigint null, +dec_prop_1 numeric(13, 4) null, +dec_prop_2 numeric(13, 4) null, +bool_prop_1 varchar(1) null, +bool_prop_2 varchar(1) null, +primary key (sched_name, trigger_name, trigger_group), +foreign key (sched_name, trigger_name, trigger_group) +references qrtz_triggers(sched_name, trigger_name, trigger_group)) +engine=innodb; + +create table qrtz_blob_triggers ( +sched_name varchar(120) not null, +trigger_name varchar(200) not null, +trigger_group varchar(200) not null, +blob_data blob null, +primary key (sched_name, trigger_name, trigger_group), +index (sched_name, trigger_name, trigger_group), +foreign key (sched_name, trigger_name, trigger_group) +references qrtz_triggers(sched_name, trigger_name, trigger_group)) +engine=innodb; + +create table qrtz_calendars ( +sched_name varchar(120) not null, +calendar_name varchar(200) not null, +calendar blob not null, +primary key (sched_name, calendar_name)) +engine=innodb; + +create table qrtz_paused_trigger_grps ( +sched_name varchar(120) not null, +trigger_group varchar(200) not null, +primary key (sched_name, trigger_group)) +engine=innodb; + +create table qrtz_fired_triggers ( +sched_name varchar(120) not null, +entry_id varchar(95) not null, +trigger_name varchar(200) not null, +trigger_group varchar(200) not null, +instance_name varchar(200) not null, +fired_time bigint(13) not null, +sched_time bigint(13) not null, +priority integer not null, +state varchar(16) not null, +job_name varchar(200) null, +job_group varchar(200) null, +is_nonconcurrent varchar(1) null, +requests_recovery varchar(1) null, +primary key (sched_name, entry_id)) +engine=innodb; + +create table qrtz_scheduler_state ( +sched_name varchar(120) not null, +instance_name varchar(200) not null, +last_checkin_time bigint(13) not null, +checkin_interval bigint(13) not null, +primary key (sched_name, instance_name)) +engine=innodb; + +create table qrtz_locks ( +sched_name varchar(120) not null, +lock_name varchar(40) not null, +primary key (sched_name, lock_name)) +engine=innodb; + +create index idx_qrtz_j_req_recovery on qrtz_job_details(sched_name, requests_recovery); +create index idx_qrtz_j_grp on qrtz_job_details(sched_name, job_group); + +create index idx_qrtz_t_j on qrtz_triggers(sched_name, job_name, job_group); +create index idx_qrtz_t_jg on qrtz_triggers(sched_name, job_group); +create index idx_qrtz_t_c on qrtz_triggers(sched_name, calendar_name); +create index idx_qrtz_t_g on qrtz_triggers(sched_name, trigger_group); +create index idx_qrtz_t_state on qrtz_triggers(sched_name, trigger_state); +create index idx_qrtz_t_n_state on qrtz_triggers(sched_name, trigger_name, trigger_group, trigger_state); +create index idx_qrtz_t_n_g_state on qrtz_triggers(sched_name, trigger_group, trigger_state); +create index idx_qrtz_t_next_fire_time on qrtz_triggers(sched_name, next_fire_time); +create index idx_qrtz_t_nft_st on qrtz_triggers(sched_name, trigger_state, next_fire_time); +create index idx_qrtz_t_nft_misfire on qrtz_triggers(sched_name, misfire_instr, next_fire_time); +create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(sched_name, misfire_instr, next_fire_time, trigger_state); +create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(sched_name, misfire_instr, next_fire_time, trigger_group, trigger_state); + +create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(sched_name, instance_name); +create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(sched_name, instance_name, requests_recovery); +create index idx_qrtz_ft_j_g on qrtz_fired_triggers(sched_name, job_name, job_group); +create index idx_qrtz_ft_jg on qrtz_fired_triggers(sched_name, job_group); +create index idx_qrtz_ft_t_g on qrtz_fired_triggers(sched_name, trigger_name, trigger_group); +create index idx_qrtz_ft_tg on qrtz_fired_triggers(sched_name, trigger_group); + +commit; \ No newline at end of file