25 Commits
3.2.x ... 3.3.x

Author SHA1 Message Date
302f21867a release: v3.3.0 2024-09-09 19:45:37 +08:00
403c72aa52 fix: 修复初始数据错误 2024-09-09 19:43:07 +08:00
c87317d199 chore: continew-starter 2.5.2 => 2.6.0
修复更新加密失效
修复特殊校验异常不打印堆栈
其他优化

Closes #84
2024-09-07 19:00:06 +08:00
098571ffb2 fix: 修复角色查询参数与前端不一致的问题
Closes #85
2024-09-06 19:38:32 +08:00
89031954c0 fix: 临时修复更新会导致原加密失效的问题(待 starter 升级) 2024-09-04 22:37:14 +08:00
6324a5af88 chore: 优化部分配置及注释 2024-09-04 20:16:11 +08:00
387fb19464 feat: 修改角色支持衔接新增角色时的父子联动 2024-09-03 00:30:57 +08:00
ca9f34d3d5 chore(generator): 优化部分代码生成代码 2024-08-31 20:34:18 +08:00
ad9a6000fc refactor: 重构权限变更逻辑,修改角色、变更用户角色不再下线用户 2024-08-31 20:32:50 +08:00
25240fa819 fix: 临时移除 MyBatis Plus saveBatch 不兼容的 rewriteBatchedStatements 配置 2024-08-31 20:28:54 +08:00
51abd7e802 chore: 优化日志配置 2024-08-30 20:12:05 +08:00
f5ee2b5beb refactor(generator): 优化代码生成列配置代码,取消后端部分默认值 2024-08-30 20:05:59 +08:00
bd7bf43134 chore: 优化部分配置 2024-08-29 20:14:43 +08:00
ecc98b1999 fix(generator): 修复不配置关联字典生成代码报错的问题 2024-08-29 19:40:28 +08:00
bb5a92e5ca chore: 优化健康监测接口响应信息 2024-08-28 22:20:24 +08:00
6c4e2522df fix: 修复 DataPermission 注解表别名配置无效的问题 2024-08-28 22:10:20 +08:00
fdd21a01c1 feat(generator): 代码生成字段配置支持选择关联字典
暂时仅支持前端部分生成

Closes #I9SWQB
Closes #55
2024-08-27 00:01:57 +08:00
e17d5db0fb chore: 优化全局异常配置,状态信息不再显示原生异常错误信息 2024-08-26 22:06:14 +08:00
9e91f563e2 fix: 修复目录、菜单的组件名称重复的错误问题 2024-08-22 20:53:01 +08:00
d56b9aa35e feat: 保存字段配置时重新指定排序
Closes #IAG7UD
2024-08-20 22:46:37 +08:00
0f1479f40d chore: continew-starter 2.5.1 => 2.5.2
接口文档显示优化
增强 SQL 注入防御
2024-08-15 00:07:59 +08:00
62d99b8a4e chore: continew-starter 2.5.0 => 2.5.1
调整 apply 中 SQL 函数的使用方式,以避免 SQL 注入
2024-08-13 00:24:18 +08:00
75cef77318 build: 优化打包配置 2024-08-10 21:41:34 +08:00
2c1eb5660f refactor: 重构日志持久层接口本地实现类 2024-08-07 20:29:59 +08:00
d7621c6b26 chore: continew-starter 2.4.0 => 2.5.0
1.continew-starter-log-httptrace-pro => continew-starter-log-interceptor
2.移除 WebMvcConfiguration 配置(已迁移到 Starter 项目)
3.Starter 全局响应(新)适配,自定义异常拦截调整到 Admin 项目
4.部分 API 调整
2024-08-07 20:27:27 +08:00
108 changed files with 1135 additions and 761 deletions

View File

@@ -1,3 +1,40 @@
## [v3.3.0](https://github.com/continew-org/continew-admin/compare/v3.2.0...v3.3.0) (2024-09-09)
### ✨ 新特性
* 重构全局响应处理及异常拦截,自定义异常拦截从 Starter 调整到 Admin 项目 ([d7621c6](https://github.com/continew-org/continew-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44))
* 重构 Controller 接口方法返回值写法,接口文档也已适配处理 ([d7621c6](https://github.com/continew-org/continew-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44)) ([0f1479f](https://github.com/continew-org/continew-admin/commit/0f1479f40deef83a5f5d64cbc24a7691b274b112))
* 代码生成字段配置时支持指定排序 ([d56b9aa](https://github.com/continew-org/continew-admin/commit/d56b9aa35ee2502804f487487d7bac02f4edc9b0))
* 代码生成字段配置时支持选择关联字典 ([fdd21a0](https://github.com/continew-org/continew-admin/commit/fdd21a01c106e12321d0d1886ff643c72a09943b)) ([ecc98b1](https://github.com/continew-org/continew-admin/commit/ecc98b1999d90c1a7a29af94dc8705283f34dada))
* 修改角色功能权限、数据权限支持衔接新增角色时的父子联动选项 ([387fb19](https://github.com/continew-org/continew-admin/commit/387fb194640d4a288f053c3eba1bf5b314d64da7))
### 💎 功能优化
- 移除 WebMvcConfiguration 配置(已迁移到 Starter 项目)([d7621c6](https://github.com/continew-org/continew-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44))
- 重构日志持久层接口本地实现类 ([2c1eb56](https://github.com/continew-org/continew-admin/commit/2c1eb5660f69a9ab702d503944a11e47edac1142))
- 优化打包配置,模板等配置文件提取到 jar 包外部 ([75cef77](https://github.com/continew-org/continew-admin/commit/75cef773187e5b5060a10a12e7c9912002376d7a))
- 优化健康监测接口响应信息 ([bb5a92e](https://github.com/continew-org/continew-admin/commit/bb5a92e5ca238ed677d9ac3589fdf8009d2ac232))
- 优化代码生成列配置代码,取消后端部分默认值 ([f5ee2b5](https://github.com/continew-org/continew-admin/commit/f5ee2b5beb9572d3fcd5b7c2f6db0627dedb31aa)) ([ca9f34d](https://github.com/continew-org/continew-admin/commit/ca9f34d3d5a3f96c6df537036a5fd876cae2e89a))
- 重构权限变更逻辑,修改角色、变更用户角色不再下线用户 ([ad9a600](https://github.com/continew-org/continew-admin/commit/ad9a6000fcb5d64b04cf230caa3cbacc8c3ac8d7))
### 🐛 问题修复
- 修复打包部署后,下载用户导入模板异常问题 (Gitee#25) ([c7ffc67](https://github.com/continew-org/continew-admin/commit/c7ffc67cdc9139a4398c7dc819ca453880bd100a))
- 修复日志记录仅支持获取 JSON 结构响应体的问题 ([d7621c6](https://github.com/continew-org/continew-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44))
- 修复并增强 SQL 注入防御 ([0f1479f](https://github.com/continew-org/continew-admin/commit/0f1479f40deef83a5f5d64cbc24a7691b274b112))
- 修复目录、菜单的组件名称重复的错误问题 ([9e91f56](https://github.com/continew-org/continew-admin/commit/9e91f563e2a263ce302dc3bf17c89e37c2b56285))
- 修复 DataPermission 注解表别名配置无效的问题 ([6c4e252](https://github.com/continew-org/continew-admin/commit/6c4e2522df3f44ba0f5a21228e805b8ac98f8e6b))
- 临时移除 MyBatis Plus saveBatch 不兼容的 rewriteBatchedStatements 配置 ([25240fa](https://github.com/continew-org/continew-admin/commit/25240fa81957a1677deda294ce8f2b0af5413315))
- 修复更新会导致原加密失效的问题 ([8903195](https://github.com/continew-org/continew-admin/commit/89031954c0b7daee1c08e1a10fd50139301cd6ab)) ([c87317d](https://github.com/continew-org/continew-admin/commit/c87317d19946989e86dfbc5f24b155b2ea5abdc9))
- 修复角色查询参数与前端不一致的问题 ([098571f](https://github.com/continew-org/continew-admin/commit/098571ffb2febc6163d2b9e5b18c4796ea80cbfa))
- 修复特殊校验异常不打印堆栈 ([c87317d](https://github.com/continew-org/continew-admin/commit/c87317d19946989e86dfbc5f24b155b2ea5abdc9))
- 修复日志全局 includes 配置会被局部修改的问题 ([c87317d](https://github.com/continew-org/continew-admin/commit/c87317d19946989e86dfbc5f24b155b2ea5abdc9))
- 修复初始数据错误 ([403c72a](https://github.com/continew-org/continew-admin/commit/403c72aa52a0f0852208f15fa1c7117ee26414f0))
### 📦 依赖升级
- ContiNew Starter 2.4.0 => 2.6.0 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/continew-org/continew-starter/blob/dev/CHANGELOG.md))
## [v3.2.0](https://github.com/continew-org/continew-admin/compare/v3.1.0...v3.2.0) (2024-08-05)
### ✨ 新特性

View File

@@ -4,7 +4,7 @@
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License" />
</a>
<a href="https://github.com/continew-org/continew-admin" target="_blank">
<img src="https://img.shields.io/badge/RELEASE-v3.2.0-%23ff3f59.svg" alt="Release" />
<img src="https://img.shields.io/badge/RELEASE-v3.3.0-%23ff3f59.svg" alt="Release" />
</a>
<a href="https://app.codacy.com/gh/continew-org/continew-admin/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" target="_blank">
<img src="https://app.codacy.com/project/badge/Grade/19e3e2395d554efe902c3822e65db30e" alt="Codacy Badge" />
@@ -13,7 +13,7 @@
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-admin&metric=alert_status" alt="Sonar Status" />
</a>
<a href="https://github.com/continew-org/continew-starter" target="_blank">
<img src="https://img.shields.io/badge/ContiNew Starter-2.4.0-%236CB52D.svg" alt="ContiNew Starter" />
<img src="https://img.shields.io/badge/ContiNew Starter-2.6.0-%236CB52D.svg" alt="ContiNew Starter" />
</a>
<a href="https://spring.io/projects/spring-boot" target="_blank">
<img src="https://img.shields.io/badge/Spring Boot-3.2.7-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
@@ -213,13 +213,13 @@ public class DeptController extends BaseController<DeptService, DeptResp, DeptDe
| 名称 | 版本 | 简介 |
| :----------------------------------------------------------- | :----------- | :----------------------------------------------------------- |
| <a href="https://cn.vuejs.org/" target="_blank">Vue</a> | 3.4.21 | 渐进式 JavaScript 框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 |
| <a href="https://arco.design/vue/docs/start" target="_blank">Arco Design</a> | 2.55.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 |
| <a href="https://arco.design/vue/docs/start" target="_blank">Arco Design</a> | 2.56.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 |
| <a href="https://www.typescriptlang.org/zh/" target="_blank">TypeScript</a> | 5.0.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 |
| <a href="https://cn.vitejs.dev/" target="_blank">Vite</a> | 5.1.5 | 下一代的前端工具链,为开发提供极速响应。 |
| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.4.0 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken可轻松集成到应用中为开发人员减少手动引入依赖及配置的麻烦为 Spring Boot Web 项目的灵活快速构建提供支持。 |
| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.6.0 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken可轻松集成到应用中为开发人员减少手动引入依赖及配置的麻烦为 Spring Boot Web 项目的灵活快速构建提供支持。 |
| <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a> | 3.2.7 | 简化 Spring 应用的初始搭建和开发过程基于“约定优于配置”的理念使开发人员不再需要定义样板化的配置。Spring Boot 3.0 开始,要求 Java 17 作为最低版本) |
| <a href="https://undertow.io/" target="_blank">Undertow</a> | 2.3.13.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 |
| <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token + JWT</a> | 1.38.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
| <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token + JWT</a> | 1.39.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
| <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a> | 3.5.7 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 |
| <a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a> | 4.3.1 | 基于 Spring Boot 的快速集成多数据源的启动器。 |
| Hikari | 5.0.1 | JDBC 连接池,号称 “史上最快连接池”SpringBoot 在 2.0 之后,采用的默认数据库连接池就是 Hikari。 |
@@ -228,18 +228,20 @@ public class DeptController extends BaseController<DeptService, DeptResp, DeptDe
| <a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a> | 3.9.1 | SQL 性能分析组件。 |
| <a href="https://github.com/liquibase/liquibase" target="_blank">Liquibase</a> | 4.24.0 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 |
| [JetCache](https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md) | 2.7.6 | 一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新还提供了 Cache 接口用于手工缓存操作。 |
| <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a> | 3.32.0 | 不仅仅是一个 Redis Java 客户端Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 |
| <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a> | 3.35.0 | 不仅仅是一个 Redis Java 客户端Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 |
| <a href="https://redis.io/" target="_blank">Redis</a> | 7.2.3 | 高性能的 key-value 数据库。 |
| [Snail Job](https://snailjob.opensnail.com/) | 1.1.0 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 |
| [X File Storage](https://x-file-storage.xuyanwu.cn/#/) | 2.2.0 | 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台。 |
| <a href="https://sms4j.com/" target="_blank">SMS4J</a> | 3.2.1 | 短信聚合框架,轻松集成多家短信服务,解决接入多个短信 SDK 的繁琐流程。 |
| [Snail Job](https://snailjob.opensnail.com/) | 1.1.2 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 |
| [X File Storage](https://x-file-storage.xuyanwu.cn/#/) | 2.2.1 | 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台。 |
| <a href="https://sms4j.com/" target="_blank">SMS4J</a> | 3.3.2 | 短信聚合框架,轻松集成多家短信服务,解决接入多个短信 SDK 的繁琐流程。 |
| <a href="https://justauth.cn/" target="_blank">Just Auth</a> | 1.16.6 | 开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK让登录变得 So easy |
| <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a> | 4.0.1 | 一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 |
| [AJ-Captcha](https://ajcaptcha.beliefteam.cn/captcha-doc/) | 1.3.0 | Java 行为验证码包含滑动拼图、文字点选两种方式UI支持弹出和嵌入两种方式。 |
| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型可用于 Java Web、JavaSE 等项目。 |
| [Crane4j](https://createsequence.gitee.io/crane4j-doc/#/) | 2.9.0 | 一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B再把 B 的属性映射到 A” 这类需求的字段填充框架。 |
| [CosID](https://cosid.ahoo.me/guide/getting-started.html) | 2.9.6 | 旨在提供通用、灵活、高性能的分布式 ID 生成器。 |
| [Graceful Response](https://doc.feiniaojin.com/graceful-response/home.html) | 5.0.0-boot3 | 一个Spring Boot技术栈下的优雅响应处理组件可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程提高开发效率提高代码质量。 |
| <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a> | 4.5.0 | 前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案。 |
| <a href="https://www.hutool.cn/" target="_blank">Hutool</a> | 5.8.29 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
| <a href="https://www.hutool.cn/" target="_blank">Hutool</a> | 5.8.32 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
| <a href="https://projectlombok.org/" target="_blank">Lombok</a> | 1.18.32 | 在 Java 开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码,让编写的类更加简洁。 |
## 快速开始

View File

@@ -67,7 +67,7 @@
<!-- ContiNew Starter 数据访问模块 - MyBatis Plus -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mybatis-plus</artifactId>
<artifactId>continew-starter-data-mp</artifactId>
</dependency>
<!-- ContiNew Starter 缓存模块 - JetCache -->

View File

@@ -1,62 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import java.util.Objects;
/**
* Web MVC 配置
*
* @author Charles7c
* @since 2022/12/11 19:40
*/
@EnableWebMvc
@Configuration
@RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer {
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
/**
* 解决 Jackson2ObjectMapperBuilderCustomizer 配置不生效的问题
* <p>
* MappingJackson2HttpMessageConverter 对象在程序启动时创建了多个,移除多余的,保证只有一个
* </p>
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
if (Objects.isNull(mappingJackson2HttpMessageConverter)) {
converters.add(0, new MappingJackson2HttpMessageConverter());
} else {
converters.add(0, mappingJackson2HttpMessageConverter);
}
// 自定义 converters 时,需要手动在最前面添加 ByteArrayHttpMessageConverter
// 否则 Spring Doc OpenAPI 的 /*/api-docs/**(例如:/v3/api-docs/default接口响应内容会变为 Base64 编码后的内容,最终导致接口文档解析失败
// 详情请参阅https://github.com/springdoc/springdoc-openapi/issues/2143
converters.add(0, new ByteArrayHttpMessageConverter());
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.exception;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.web.model.R;
/**
* 全局异常处理器
*
* @author Charles7c
* @since 2024/8/7 20:21
*/
@Slf4j
@Order(99)
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 拦截业务异常
*/
@ExceptionHandler(BusinessException.class)
public R handleBusinessException(BusinessException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage());
}
/**
* 拦截自定义验证异常-错误请求
*/
@ExceptionHandler(BadRequestException.class)
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), e.getMessage());
}
/**
* 拦截文件上传异常-超过上传大小限制
*/
@ExceptionHandler(MultipartException.class)
public R handleRequestTooBigException(MultipartException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
String msg = e.getMessage();
R defaultFail = R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), msg);
if (CharSequenceUtil.isBlank(msg)) {
return defaultFail;
}
String sizeLimit;
Throwable cause = e.getCause();
if (null != cause) {
msg = msg.concat(cause.getMessage().toLowerCase());
}
if (msg.contains("size") && msg.contains("exceed")) {
sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for");
} else if (msg.contains("larger than")) {
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
} else {
return defaultFail;
}
String errorMsg = "请上传小于 %sKB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024);
return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMsg);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.exception;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import top.continew.starter.web.model.R;
/**
* 全局 SaToken 异常处理器
*
* @author Charles7c
* @since 2024/8/7 20:21
*/
@Slf4j
@Order(99)
@RestControllerAdvice
public class GlobalSaTokenExceptionHandler {
/**
* 认证异常-登录认证
*/
@ExceptionHandler(NotLoginException.class)
public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
String errorMsg = switch (e.getType()) {
case NotLoginException.KICK_OUT -> "您已被踢下线";
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线";
default -> "您的登录状态已过期,请重新登录";
};
return R.fail(String.valueOf(HttpStatus.UNAUTHORIZED.value()), errorMsg);
}
/**
* 认证异常-权限认证
*/
@ExceptionHandler(NotPermissionException.class)
public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权");
}
/**
* 认证异常-角色认证
*/
@ExceptionHandler(NotRoleException.class)
public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权");
}
}

View File

@@ -19,9 +19,9 @@ package top.continew.admin.common.config.mybatis;
import cn.hutool.core.convert.Convert;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionCurrentUser;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
import top.continew.starter.data.mybatis.plus.datapermission.DataScope;
import top.continew.starter.data.mp.datapermission.DataPermissionCurrentUser;
import top.continew.starter.data.mp.datapermission.DataPermissionFilter;
import top.continew.starter.data.mp.datapermission.DataScope;
import java.util.stream.Collectors;

View File

@@ -20,8 +20,8 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermission;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.data.mp.datapermission.DataPermission;
import java.util.List;

View File

@@ -20,7 +20,7 @@ import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
import top.continew.starter.data.mp.datapermission.DataPermissionFilter;
/**
* MyBatis Plus 配置

View File

@@ -25,6 +25,7 @@ import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 登录用户信息
@@ -109,16 +110,17 @@ public class LoginUser implements Serializable {
*/
private Integer passwordExpirationDays;
public LoginUser(Set<String> permissions,
Set<String> roleCodes,
Set<RoleDTO> roles,
Integer passwordExpirationDays) {
public LoginUser(Set<String> permissions, Set<RoleDTO> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.roleCodes = roleCodes;
this.roles = roles;
this.setRoles(roles);
this.passwordExpirationDays = passwordExpirationDays;
}
public void setRoles(Set<RoleDTO> roles) {
this.roles = roles;
this.roleCodes = roles.stream().map(RoleDTO::getCode).collect(Collectors.toSet());
}
/**
* 是否为管理员
*

View File

@@ -69,6 +69,18 @@ public class LoginHelper {
return tokenValue;
}
/**
* 更新登录用户信息
*
* @param loginUser
* 登录用户信息
* @param token 令牌
*/
public static void updateLoginUser(LoginUser loginUser, String token) {
SaHolder.getStorage().delete(CacheConstants.LOGIN_USER_KEY);
StpUtil.getTokenSessionByToken(token).set(CacheConstants.LOGIN_USER_KEY, loginUser);
}
/**
* 获取登录用户信息
*

View File

@@ -14,7 +14,7 @@
<properties>
<!-- SnailJob 服务端 -->
<snail-job.version>1.1.0</snail-job.version>
<snail-job.version>1.1.2</snail-job.version>
</properties>
<dependencies>

View File

@@ -19,7 +19,7 @@ package top.continew.admin.generator.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.generator.model.entity.FieldConfigDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -17,7 +17,7 @@
package top.continew.admin.generator.mapper;
import top.continew.admin.generator.model.entity.GenConfigDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 生成配置 Mapper

View File

@@ -141,6 +141,12 @@ public class FieldConfigDO implements Serializable {
@Schema(description = "查询方式", example = "1")
private QueryTypeEnum queryType;
/**
* 字典编码
*/
@Schema(description = "字典编码", example = "notice_type")
private String dictCode;
/**
* 创建时间
*/
@@ -151,19 +157,22 @@ public class FieldConfigDO implements Serializable {
public FieldConfigDO(@NonNull Column column) {
this.setTableName(column.getTableName());
this.setColumnName(column.getName());
this.setColumnType(StrUtil.splitToArray(column.getTypeName(), StringConstants.SPACE)[0].toLowerCase());
this.setColumnType(column.getTypeName());
this.setColumnSize(Convert.toStr(column.getSize()));
this.setComment(column.getComment());
this.setIsRequired(!column.isPk() && !column.isNullable());
this.setShowInList(true);
this.setShowInForm(this.getIsRequired());
this.setShowInQuery(this.getIsRequired());
this.setFormType(FormTypeEnum.INPUT);
this.setQueryType("String".equals(this.getFieldType()) ? QueryTypeEnum.LIKE : QueryTypeEnum.EQ);
}
public void setColumnName(String columnName) {
this.columnName = columnName;
this.fieldName = StrUtil.toCamelCase(this.columnName);
}
public void setColumnType(String columnType) {
String[] arr = StrUtil.splitToArray(columnType, StringConstants.SPACE);
this.columnType = arr.length > 1 ? arr[0].toLowerCase() : columnType.toLowerCase();
}
}

View File

@@ -18,18 +18,15 @@ package top.continew.admin.generator.model.entity;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.starter.core.util.StrUtils;
import top.continew.starter.core.constant.StringConstants;
import java.io.Serial;
import java.io.Serializable;
@@ -118,21 +115,16 @@ public class GenConfigDO implements Serializable {
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
/**
* 类名前缀
*/
@Setter(AccessLevel.NONE)
@JsonIgnore
@TableField(exist = false)
private String classNamePrefix;
public GenConfigDO(String tableName) {
this.tableName = tableName;
this.setTableName(tableName);
}
public String getClassNamePrefix() {
String rawClassName = StrUtils.blankToDefault(this.tablePrefix, this.tableName, prefix -> StrUtil
.removePrefix(this.tableName, prefix));
return StrUtil.upperFirst(StrUtil.toCamelCase(rawClassName));
public void setTableName(String tableName) {
this.tableName = tableName;
// 默认表前缀sys_user -> sys_
int underLineIndex = StrUtil.indexOf(tableName, StringConstants.C_UNDERLINE);
if (-1 != underLineIndex) {
this.tablePrefix = StrUtil.subPre(tableName, underLineIndex + 1);
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.generator.model.entity;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.StrUtils;
import java.io.Serial;
import java.util.List;
import java.util.Set;
/**
* 内部生成配置信息
*
* @author zhangqcc
* @since 2024/8/30 19:35
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InnerGenConfigDO extends GenConfigDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字段配置信息
*/
private List<FieldConfigDO> fieldConfigs;
/**
* 生成时间
*/
private String datetime;
/**
* API 模块名称
*/
private String apiModuleName;
/**
* API 名称
*/
private String apiName;
/**
* 类名
*/
private String className;
/**
* 类名前缀
*/
private String classNamePrefix;
/**
* 子包名称
*/
private String subPackageName;
/**
* 字典编码列表
*/
private Set<String> dictCodes;
/**
* 是否包含必填字段
*/
private boolean hasRequiredField;
/**
* 是否包含字典字段
*/
private boolean hasDictField;
/**
* 是否包含 BigDecimal 字段
*/
private boolean hasBigDecimalField;
/**
* 是否包含 List 字段
*/
private boolean hasListField;
/**
* 是否包含 Time 包字段
*/
private boolean hasTimeField;
public InnerGenConfigDO() {
}
public InnerGenConfigDO(GenConfigDO genConfig) {
BeanUtil.copyProperties(genConfig, this);
this.setDatetime(DateUtil.date().toString("yyyy/MM/dd HH:mm"));
this.setApiName(StrUtil.lowerFirst(this.getClassNamePrefix()));
}
@Override
public void setPackageName(String packageName) {
super.setPackageName(packageName);
String realPackageName = this.getPackageName();
this.setApiModuleName(StrUtil.subSuf(realPackageName, StrUtil
.lastIndexOfIgnoreCase(realPackageName, StringConstants.DOT) + 1));
}
public String getClassNamePrefix() {
String tableName = super.getTableName();
String rawClassName = StrUtils.blankToDefault(super.getTablePrefix(), tableName, prefix -> StrUtil
.removePrefix(tableName, prefix));
return StrUtil.upperFirst(StrUtil.toCamelCase(rawClassName));
}
}

View File

@@ -16,7 +16,6 @@
package top.continew.admin.generator.service;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import top.continew.admin.generator.model.entity.FieldConfigDO;
import top.continew.admin.generator.model.entity.GenConfigDO;
@@ -86,8 +85,7 @@ public interface GeneratorService {
* 生成代码
*
* @param tableNames 表明层
* @param request 请求对象
* @param response 响应对象
*/
void generate(List<String> tableNames, HttpServletRequest request, HttpServletResponse response);
void generate(List<String> tableNames, HttpServletResponse response);
}

View File

@@ -19,26 +19,27 @@ package top.continew.admin.generator.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.db.meta.Column;
import cn.hutool.system.SystemUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.generator.config.properties.GeneratorProperties;
import top.continew.admin.generator.enums.FormTypeEnum;
import top.continew.admin.generator.enums.QueryTypeEnum;
import top.continew.admin.generator.mapper.FieldConfigMapper;
import top.continew.admin.generator.mapper.GenConfigMapper;
import top.continew.admin.generator.model.entity.FieldConfigDO;
import top.continew.admin.generator.model.entity.GenConfigDO;
import top.continew.admin.generator.model.entity.InnerGenConfigDO;
import top.continew.admin.generator.model.query.TableQuery;
import top.continew.admin.generator.model.req.GenConfigReq;
import top.continew.admin.generator.model.resp.GeneratePreviewResp;
@@ -79,6 +80,7 @@ public class GeneratorServiceImpl implements GeneratorService {
private final ProjectProperties projectProperties;
private final FieldConfigMapper fieldConfigMapper;
private final GenConfigMapper genConfigMapper;
private static final List<String> TIME_PACKAGE_CLASS = Arrays.asList("LocalDate", "LocalTime", "LocalDateTime");
@Override
public PageResp<TableResp> pageTable(TableQuery query, PageQuery pageQuery) throws SQLException {
@@ -123,11 +125,6 @@ public class GeneratorServiceImpl implements GeneratorService {
if (null != lastGenConfig) {
genConfig.setAuthor(lastGenConfig.getAuthor());
}
// 默认表前缀sys_user -> sys_
int underLineIndex = StrUtil.indexOf(tableName, StringConstants.C_UNDERLINE);
if (-1 != underLineIndex) {
genConfig.setTablePrefix(StrUtil.subPre(tableName, underLineIndex + 1));
}
}
return genConfig;
}
@@ -155,8 +152,7 @@ public class GeneratorServiceImpl implements GeneratorService {
.orElseGet(() -> new FieldConfigDO(column));
// 更新已有字段配置
if (null != fieldConfig.getCreateTime()) {
String columnType = StrUtil.splitToArray(column.getTypeName(), StringConstants.SPACE)[0].toLowerCase();
fieldConfig.setColumnType(columnType);
fieldConfig.setColumnType(column.getTypeName());
fieldConfig.setColumnSize(Convert.toStr(column.getSize()));
}
String fieldType = typeMappingEntrySet.stream()
@@ -174,19 +170,22 @@ public class GeneratorServiceImpl implements GeneratorService {
@Override
@Transactional(rollbackFor = Exception.class)
public void saveConfig(GenConfigReq req, String tableName) {
// 保存字段配置
// 保存字段配置(先删除再保存)
fieldConfigMapper.delete(Wrappers.lambdaQuery(FieldConfigDO.class).eq(FieldConfigDO::getTableName, tableName));
List<FieldConfigDO> fieldConfigList = req.getFieldConfigs();
for (FieldConfigDO fieldConfig : fieldConfigList) {
for (int i = 0; i < fieldConfigList.size(); i++) {
FieldConfigDO fieldConfig = fieldConfigList.get(i);
// 重新设置排序
fieldConfig.setFieldSort(i + 1);
if (Boolean.TRUE.equals(fieldConfig.getShowInForm())) {
CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
fieldConfig.setFormType(ObjectUtil.defaultIfNull(fieldConfig.getFormType(), FormTypeEnum.INPUT));
} else {
// 在表单中不显示,不需要设置必填
fieldConfig.setIsRequired(false);
}
if (Boolean.TRUE.equals(fieldConfig.getShowInQuery())) {
CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
CheckUtils.throwIfNull(fieldConfig.getQueryType(), "字段 [{}] 的查询方式不能为空", fieldConfig.getFieldName());
fieldConfig.setFormType(ObjectUtil.defaultIfNull(fieldConfig.getFormType(), FormTypeEnum.INPUT));
fieldConfig.setQueryType(ObjectUtil.defaultIfNull(fieldConfig.getQueryType(), QueryTypeEnum.EQ));
} else {
// 在查询中不显示,不需要设置查询方式
fieldConfig.setQueryType(null);
@@ -218,51 +217,50 @@ public class GeneratorServiceImpl implements GeneratorService {
CheckUtils.throwIfNull(genConfig, "请先进行数据表 [{}] 生成配置", tableName);
List<FieldConfigDO> fieldConfigList = fieldConfigMapper.selectListByTableName(tableName);
CheckUtils.throwIfEmpty(fieldConfigList, "请先进行数据表 [{}] 字段配置", tableName);
Map<String, Object> genConfigMap = BeanUtil.beanToMap(genConfig);
genConfigMap.put("date", DateUtil.date().toString("yyyy/MM/dd HH:mm"));
String packageName = genConfig.getPackageName();
String apiModuleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
genConfigMap.put("apiModuleName", apiModuleName);
genConfigMap.put("apiName", StrUtil.lowerFirst(genConfig.getClassNamePrefix()));
InnerGenConfigDO innerGenConfig = new InnerGenConfigDO(genConfig);
// 渲染代码
String classNamePrefix = genConfig.getClassNamePrefix();
String classNamePrefix = innerGenConfig.getClassNamePrefix();
Map<String, GeneratorProperties.TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
for (Map.Entry<String, GeneratorProperties.TemplateConfig> templateConfigEntry : templateConfigMap.entrySet()) {
this.pretreatment(genConfigMap, fieldConfigList, templateConfigEntry);
String className = classNamePrefix + StrUtil.nullToEmpty(templateConfigEntry.getKey());
genConfigMap.put("className", className);
GeneratorProperties.TemplateConfig templateConfig = templateConfigEntry.getValue();
// 移除需要忽略的字段
innerGenConfig.setFieldConfigs(fieldConfigList.stream()
.filter(fieldConfig -> !StrUtil.equalsAny(fieldConfig.getFieldName(), templateConfig
.getExcludeFields()))
.toList());
// 预处理配置
this.pretreatment(innerGenConfig);
// 处理其他配置
innerGenConfig.setSubPackageName(templateConfig.getPackageName());
String classNameSuffix = templateConfigEntry.getKey();
String className = classNamePrefix + classNameSuffix;
innerGenConfig.setClassName(className);
boolean isBackend = templateConfig.isBackend();
String extension = templateConfig.getExtension();
GeneratePreviewResp generatePreview = new GeneratePreviewResp();
generatePreview.setBackend(isBackend);
generatePreviewList.add(generatePreview);
if (isBackend) {
generatePreview.setFileName(className + extension);
generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap));
} else {
generatePreview.setFileName(".vue".equals(extension) && "index".equals(templateConfigEntry.getKey())
String fileName = className + extension;
if (!isBackend) {
fileName = ".vue".equals(extension) && "index".equals(classNameSuffix)
? "index.vue"
: this.getFrontendFileName(classNamePrefix, className, extension));
genConfigMap.put("fieldConfigs", fieldConfigList);
generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap));
: this.getFrontendFileName(classNamePrefix, className, extension);
}
setPreviewPath(generatePreview, genConfig, templateConfig);
generatePreview.setFileName(fileName);
generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), BeanUtil
.beanToMap(innerGenConfig)));
this.setPreviewPath(generatePreview, innerGenConfig, templateConfig);
}
return generatePreviewList;
}
private void setPreviewPath(GeneratePreviewResp generatePreview,
GenConfigDO genConfig,
InnerGenConfigDO genConfig,
GeneratorProperties.TemplateConfig templateConfig) {
// 获取前后端基础路径
String backendBasicPackagePath = this.buildBackendBasicPackagePath(genConfig);
String frontendBasicPackagePath = String.join(File.separator, projectProperties.getAppName(), projectProperties
.getAppName() + "-ui");
String packageName = genConfig.getPackageName();
String moduleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
String packagePath;
if (generatePreview.isBackend()) {
// 例如continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl
@@ -271,7 +269,7 @@ public class GeneratorServiceImpl implements GeneratorService {
} else {
// 例如continew-admin/continew-admin-ui/src/views/system
packagePath = String.join(File.separator, frontendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.SLASH, File.separator), moduleName);
.replace(StringConstants.SLASH, File.separator), genConfig.getApiModuleName());
// 例如continew-admin/continew-admin-ui/src/views/system/user
packagePath = ".vue".equals(templateConfig.getExtension())
? packagePath + File.separator + StrUtil.lowerFirst(genConfig.getClassNamePrefix())
@@ -281,7 +279,7 @@ public class GeneratorServiceImpl implements GeneratorService {
}
@Override
public void generate(List<String> tableNames, HttpServletRequest request, HttpServletResponse response) {
public void generate(List<String> tableNames, HttpServletResponse response) {
try {
String tempDir = SystemUtil.getUserInfo().getTempDir();
// 删除旧代码
@@ -296,7 +294,7 @@ public class GeneratorServiceImpl implements GeneratorService {
File tempDirFile = new File(tempDir, projectProperties.getAppName());
String zipFilePath = tempDirFile.getPath() + jodd.io.ZipUtil.ZIP_EXT;
ZipUtil.zip(tempDirFile.getPath(), zipFilePath);
FileUploadUtils.download(request, response, new File(zipFilePath), true);
FileUploadUtils.download(response, new File(zipFilePath));
} catch (Exception e) {
log.error("Generate code of table '{}' occurred an error. {}", tableNames, e.getMessage(), e);
throw new BusinessException("代码生成失败,请手动清理生成文件");
@@ -350,42 +348,36 @@ public class GeneratorServiceImpl implements GeneratorService {
/**
* 预处理生成配置
*
* @param genConfigMap 生成配置
* @param originFieldConfigList 原始字段配置列表
* @param templateConfigEntry 模板配置
* @param genConfig 生成配置
*/
private void pretreatment(Map<String, Object> genConfigMap,
List<FieldConfigDO> originFieldConfigList,
Map.Entry<String, GeneratorProperties.TemplateConfig> templateConfigEntry) {
GeneratorProperties.TemplateConfig templateConfig = templateConfigEntry.getValue();
// 移除需要忽略的字段
List<FieldConfigDO> fieldConfigList = originFieldConfigList.stream()
.filter(fieldConfig -> !StrUtil.equalsAny(fieldConfig.getFieldName(), templateConfig.getExcludeFields()))
.toList();
genConfigMap.put("fieldConfigs", fieldConfigList);
private void pretreatment(InnerGenConfigDO genConfig) {
List<FieldConfigDO> fieldConfigList = genConfig.getFieldConfigs();
// 统计部分特殊字段特征
genConfigMap.put("hasLocalDateTime", false);
genConfigMap.put("hasBigDecimal", false);
genConfigMap.put("hasRequiredField", false);
genConfigMap.put("hasListQueryField", false);
Set<String> dictCodeSet = new HashSet<>();
for (FieldConfigDO fieldConfig : fieldConfigList) {
String fieldType = fieldConfig.getFieldType();
if ("LocalDateTime".equals(fieldType)) {
genConfigMap.put("hasLocalDateTime", true);
}
if ("BigDecimal".equals(fieldType)) {
genConfigMap.put("hasBigDecimal", true);
}
// 必填项
if (Boolean.TRUE.equals(fieldConfig.getIsRequired())) {
genConfigMap.put("hasRequiredField", true);
genConfig.setHasRequiredField(true);
}
// 数据类型
if ("BigDecimal".equals(fieldType)) {
genConfig.setHasBigDecimalField(true);
}
if (TIME_PACKAGE_CLASS.contains(fieldType)) {
genConfig.setHasTimeField(true);
}
QueryTypeEnum queryType = fieldConfig.getQueryType();
if (null != queryType && StrUtil.equalsAny(queryType.name(), QueryTypeEnum.IN.name(), QueryTypeEnum.NOT_IN
.name(), QueryTypeEnum.BETWEEN.name())) {
genConfigMap.put("hasListQueryField", true);
genConfig.setHasListField(true);
}
// 字典码
if (StrUtil.isNotBlank(fieldConfig.getDictCode())) {
genConfig.setHasDictField(true);
dictCodeSet.add(fieldConfig.getDictCode());
}
}
String subPackageName = templateConfig.getPackageName();
genConfigMap.put("subPackageName", subPackageName);
genConfig.setDictCodes(dictCodeSet);
}
}

View File

@@ -18,7 +18,7 @@ import ${packageName}.service.${classNamePrefix}Service;
* ${businessName}管理 API
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Tag(name = "${businessName}管理 API")
@RestController

View File

@@ -1,10 +1,10 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
<#if hasTimeField>
import java.time.*;
</#if>
<#if hasBigDecimal>
<#if hasBigDecimalField>
import java.math.BigDecimal;
</#if>
@@ -21,7 +21,7 @@ import top.continew.starter.extension.crud.model.resp.BaseDetailResp;
* ${businessName}详情信息
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Data
@ExcelIgnoreUnannotated

View File

@@ -1,10 +1,10 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
<#if hasTimeField>
import java.time.*;
</#if>
<#if hasBigDecimal>
<#if hasBigDecimalField>
import java.math.BigDecimal;
</#if>
@@ -18,7 +18,7 @@ import top.continew.starter.extension.crud.model.entity.BaseDO;
* ${businessName}实体
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Data
@TableName("${tableName}")

View File

@@ -1,12 +1,12 @@
package ${packageName}.${subPackageName};
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import ${packageName}.model.entity.${classNamePrefix}DO;
/**
* ${businessName} Mapper
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
public interface ${className} extends BaseMapper<${classNamePrefix}DO> {}

View File

@@ -2,13 +2,13 @@ package ${packageName}.${subPackageName};
import java.io.Serial;
import java.io.Serializable;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
<#if hasTimeField>
import java.time.*;
</#if>
<#if hasBigDecimal>
<#if hasBigDecimalField>
import java.math.BigDecimal;
</#if>
<#if hasListQueryField>
<#if hasListField>
import java.util.List;
</#if>
@@ -23,7 +23,7 @@ import top.continew.starter.data.core.enums.QueryType;
* ${businessName}查询条件
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Data
@Schema(description = "${businessName}查询条件")

View File

@@ -1,10 +1,10 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
<#if hasTimeField>
import java.time.*;
</#if>
<#if hasBigDecimal>
<#if hasBigDecimalField>
import java.math.BigDecimal;
</#if>
@@ -24,7 +24,7 @@ import top.continew.starter.extension.crud.model.req.BaseReq;
* 创建或修改${businessName}信息
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Data
@Schema(description = "创建或修改${businessName}信息")

View File

@@ -1,10 +1,10 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
<#if hasTimeField>
import java.time.*;
</#if>
<#if hasBigDecimal>
<#if hasBigDecimalField>
import java.math.BigDecimal;
</#if>
@@ -18,7 +18,7 @@ import top.continew.starter.extension.crud.model.resp.BaseResp;
* ${businessName}信息
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Data
@Schema(description = "${businessName}信息")

View File

@@ -10,6 +10,6 @@ import ${packageName}.model.resp.${classNamePrefix}Resp;
* ${businessName}业务接口
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
public interface ${className} extends BaseService<${classNamePrefix}Resp, ${classNamePrefix}DetailResp, ${classNamePrefix}Query, ${classNamePrefix}Req> {}

View File

@@ -17,7 +17,7 @@ import ${packageName}.service.${classNamePrefix}Service;
* ${businessName}业务实现
*
* @author ${author}
* @since ${date}
* @since ${datetime}
*/
@Service
@RequiredArgsConstructor

View File

@@ -29,15 +29,9 @@ const isUpdate = computed(() => !!dataId.value)
const title = computed(() => (isUpdate.value ? '修改${businessName}' : '新增${businessName}'))
const formRef = ref<InstanceType<typeof GiForm>>()
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm>
<#-- SELECT/RADIO/CHECK_BOX/TREE_SELECT控件从服务器端获取数据 -->
<#if fieldConfig.formType = 'SELECT' || fieldConfig.formType = 'RADIO'
|| fieldConfig.formType = 'CHECK_BOX' || fieldConfig.formType = 'TREE_SELECT'>
const { ${fieldConfig.columnName}_enum } = useDict('${fieldConfig.columnName}_enum')
<#if hasDictField>
const { <#list dictCodes as dictCode>${dictCode}<#if dictCode_has_next>,</#if></#list> } = useDict(<#list dictCodes as dictCode>'${dictCode}'<#if dictCode_has_next>,</#if></#list>)
</#if>
</#if>
</#list>
const options: Options = {
form: {},
@@ -67,16 +61,15 @@ const columns: Columns = reactive([
type: 'switch',
<#elseif fieldConfig.formType = 'CHECK_BOX'>
type: 'check-group',
options: ${fieldConfig.columnName}_enum,
<#elseif fieldConfig.formType = 'TREE_SELECT'>
type: 'tree-select',
data: '${fieldConfig.columnName}_enum',
<#elseif fieldConfig.formType = 'SELECT'>
type: 'select',
options: ${fieldConfig.columnName}_enum,
<#elseif fieldConfig.formType = 'RADIO'>
type: 'radio-group',
options: ${fieldConfig.columnName}_enum,
type: 'radio-group'
</#if>
<#if fieldConfig.dictCode?? && fieldConfig.dictCode != ''>
options: ${fieldConfig.dictCode},
</#if>
<#if fieldConfig.isRequired>
rules: [{ required: true, message: '请输入${fieldConfig.comment}' }]

View File

@@ -12,62 +12,51 @@
:disabled-column-keys="['name']"
@refresh="search"
>
<#-- 查询字段配置 -->
<template #custom-left>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
<#if fieldConfig.formType == "SELECT"><#-- 下拉框 -->
<a-select
v-model="queryForm.${fieldConfig.fieldName}"
:options="${fieldConfig.columnName}_enum"
placeholder="请选择${fieldConfig.comment}"
allow-clear
style="width: 150px"
@change="search"
/>
<#elseif fieldConfig.formType == "RADIO"><#-- 单选框 -->
<a-radio-group v-model="queryForm.${fieldConfig.fieldName}" :options="${fieldConfig.columnName}_enum" @change="search"/>
<#elseif fieldConfig.formType == "DATE"><#-- 日期框 -->
<#if fieldConfig.queryType == "BETWEEN">
<a-range-picker
v-model="queryForm.${fieldConfig.fieldName}"
:placeholder="['请选择开始${fieldConfig.comment}','请选择结束${fieldConfig.comment}']"
format="YYYY-MM-DD"
style="width: 100%"
/>
<#else>
<a-date-picker
v-model="queryForm.${fieldConfig.fieldName}"
placeholder="请选择${fieldConfig.comment}"
format="YYYY-MM-DD"
style="width: 100%"
/>
</#if>
<#elseif fieldConfig.formType == "DATE_TIME"><#-- 日期时间框 -->
<#if fieldConfig.queryType == "BETWEEN">
<a-range-picker
v-model="queryForm.${fieldConfig.fieldName}"
:placeholder="['请选择开始${fieldConfig.comment}','请选择结束${fieldConfig.comment}']"
show-time
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<#else>
<a-date-picker
v-model="queryForm.${fieldConfig.fieldName}"
placeholder="请选择${fieldConfig.comment}"
show-time
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</#if>
<#else>
<a-input v-model="queryForm.${fieldConfig.fieldName}" placeholder="请输入${fieldConfig.comment}" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
</#if>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
<#if fieldConfig.formType == "SELECT"><#-- 下拉框 -->
<a-select
v-model="queryForm.${fieldConfig.fieldName}"
:options="${fieldConfig.columnName}_enum"
placeholder="请选择${fieldConfig.comment}"
allow-clear
style="width: 150px"
@change="search"
/>
<#elseif fieldConfig.formType == "RADIO"><#-- 单选框 -->
<a-radio-group v-model="queryForm.${fieldConfig.fieldName}" :options="${fieldConfig.dictCode}" @change="search"/>
<#elseif fieldConfig.formType == "DATE"><#-- 日期框 -->
<#if fieldConfig.queryType == "BETWEEN">
<DateRangePicker v-model="queryForm.${fieldConfig.fieldName}" format="YYYY-MM-DD" @change="search" />
<#else>
<a-date-picker
v-model="queryForm.${fieldConfig.fieldName}"
placeholder="请选择${fieldConfig.comment}"
format="YYYY-MM-DD"
style="height: 32px"
/>
</#if>
</#list>
<#elseif fieldConfig.formType == "DATE_TIME"><#-- 日期时间框 -->
<#if fieldConfig.queryType == "BETWEEN">
<DateRangePicker v-model="queryForm.${fieldConfig.fieldName}" @change="search" />
<#else>
<a-date-picker
v-model="queryForm.${fieldConfig.fieldName}"
placeholder="请选择${fieldConfig.comment}"
show-time
format="YYYY-MM-DD HH:mm:ss"
style="height: 32px"
/>
</#if>
<#else>
<a-input v-model="queryForm.${fieldConfig.fieldName}" placeholder="请输入${fieldConfig.comment}" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
</#if>
</#if>
</#list>
<a-button @click="reset">重置</a-button>
</template>
<template #custom-right>
@@ -83,9 +72,19 @@
</a-button>
</a-tooltip>
</template>
<#-- 列字段配置 -->
<template #name="{ record }">
<a-link @click="onDetail(record)">{{ record.name }}</a-link>
</template>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInList>
<#if fieldConfig.dictCode?? && fieldConfig.dictCode != "">
<template #${fieldConfig.fieldName}="{ record }">
<GiCellTag :value="record.${fieldConfig.fieldName}" :dict="${fieldConfig.dictCode}" />
</template>
</#if>
</#if>
</#list>
<template #action="{ record }">
<a-space>
<a-link v-permission="['${apiModuleName}:${apiName}:update']" @click="onUpdate(record)">修改</a-link>
@@ -109,7 +108,7 @@
<script setup lang="ts">
import ${classNamePrefix}AddModal from './${classNamePrefix}AddModal.vue'
import ${classNamePrefix}DetailDrawer from './${classNamePrefix}DetailDrawer.vue'
import { type ${classNamePrefix}Resp, type ${classNamePrefix}Query, delete${classNamePrefix}, export${classNamePrefix}, list${classNamePrefix} } from '@/apis/${apiModuleName}/${apiName}'
import { type ${classNamePrefix}Resp, type ${classNamePrefix}Query, delete${classNamePrefix}, export${classNamePrefix}, list${classNamePrefix} } from '@/apis/${apiModuleName}'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import { useDownload, useTable } from '@/hooks'
import { isMobile } from '@/utils'
@@ -118,6 +117,10 @@ import { useDict } from '@/hooks/app'
defineOptions({ name: '${classNamePrefix}' })
<#if hasDictField>
const { <#list dictCodes as dictCode>${dictCode}<#if dictCode_has_next>,</#if></#list> } = useDict(<#list dictCodes as dictCode>'${dictCode}'<#if dictCode_has_next>,</#if></#list>)
</#if>
const queryForm = reactive<${classNamePrefix}Query>({
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
@@ -127,14 +130,6 @@ const queryForm = reactive<${classNamePrefix}Query>({
sort: ['createTime,desc']
})
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
<#if fieldConfig.formType == "SELECT" || fieldConfig.formType == "RADIO">
const { ${fieldConfig.columnName}_enum } = useDict('${fieldConfig.columnName}_enum')
</#if>
</#if>
</#list>
const {
tableData: dataList,
loading,

View File

@@ -51,4 +51,16 @@ public class OnlineUserQuery implements Serializable {
@Schema(description = "登录时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private List<Date> loginTime;
/**
* 用户 ID
*/
@Schema(hidden = true)
private Long userId;
/**
* 角色 ID
*/
@Schema(hidden = true)
private Long roleId;
}

View File

@@ -59,16 +59,9 @@ public interface OnlineUserService {
LocalDateTime getLastActiveTime(String token);
/**
* 根据角色 ID 清除
*
* @param roleId 角色 ID
*/
void cleanByRoleId(Long roleId);
/**
* 根据用户 ID 清除登录
* 踢出用户
*
* @param userId 用户 ID
*/
void cleanByUserId(Long userId);
void kickOut(Long userId);
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.auth.service;
import java.util.Set;
/**
* 权限业务接口
*
* @author Charles7c
* @since 2023/3/2 20:40
*/
public interface PermissionService {
/**
* 根据用户 ID 查询权限码
*
* @param userId 用户 ID
* @return 权限码集合
*/
Set<String> listPermissionByUserId(Long userId);
/**
* 根据用户 ID 查询角色编码
*
* @param userId 用户 ID
* @return 角色编码集合
*/
Set<String> listRoleCodeByUserId(Long userId);
}

View File

@@ -36,18 +36,17 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.auth.model.resp.RouteResp;
import top.continew.admin.auth.service.LoginService;
import top.continew.admin.auth.service.PermissionService;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.enums.GenderEnum;
import top.continew.admin.system.enums.MenuTypeEnum;
import top.continew.admin.system.enums.MessageTypeEnum;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.model.dto.RoleDTO;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.enums.MenuTypeEnum;
import top.continew.admin.system.enums.MessageTemplateEnum;
import top.continew.admin.system.enums.MessageTypeEnum;
import top.continew.admin.system.enums.PasswordPolicyEnum;
import top.continew.admin.system.model.entity.DeptDO;
import top.continew.admin.system.model.entity.RoleDO;
@@ -81,17 +80,16 @@ import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRA
public class LoginServiceImpl implements LoginService {
private final ProjectProperties projectProperties;
private final PasswordEncoder passwordEncoder;
private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
private final UserService userService;
private final DeptService deptService;
private final RoleService roleService;
private final MenuService menuService;
private final PermissionService permissionService;
private final UserRoleService userRoleService;
private final UserSocialService userSocialService;
private final MessageService messageService;
private final PasswordEncoder passwordEncoder;
private final OptionService optionService;
private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
private final MessageService messageService;
@Override
public String accountLogin(String username, String password, HttpServletRequest request) {
@@ -163,7 +161,7 @@ public class LoginServiceImpl implements LoginService {
@Override
public List<RouteResp> buildRouteTree(Long userId) {
Set<String> roleCodeSet = permissionService.listRoleCodeByUserId(userId);
Set<String> roleCodeSet = roleService.listCodeByUserId(userId);
if (CollUtil.isEmpty(roleCodeSet)) {
return new ArrayList<>(0);
}
@@ -205,17 +203,15 @@ public class LoginServiceImpl implements LoginService {
*/
private String login(UserDO user) {
Long userId = user.getId();
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> permissionService
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService
.listPermissionByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Set<String>> roleCodeFuture = CompletableFuture.supplyAsync(() -> permissionService
.listRoleCodeByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Set<RoleDTO>> roleFuture = CompletableFuture.supplyAsync(() -> roleService
.listByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
CompletableFuture.allOf(permissionFuture, roleCodeFuture, roleFuture);
LoginUser loginUser = new LoginUser(permissionFuture.join(), roleCodeFuture.join(), roleFuture
.join(), passwordExpirationDaysFuture.join());
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);
LoginUser loginUser = new LoginUser(permissionFuture.join(), roleFuture.join(), passwordExpirationDaysFuture
.join());
BeanUtil.copyProperties(user, loginUser);
return LoginHelper.login(loginUser);
}

View File

@@ -18,12 +18,12 @@ package top.continew.admin.auth.service.impl;
import cn.crane4j.annotation.AutoOperate;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.auth.model.query.OnlineUserQuery;
import top.continew.admin.auth.model.resp.OnlineUserResp;
@@ -44,10 +44,10 @@ import java.util.List;
* 在线用户业务实现
*
* @author Charles7c
* @author Lion Li<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>
* @since 2023/3/25 22:49
*/
@Service
@RequiredArgsConstructor
public class OnlineUserServiceImpl implements OnlineUserService {
@Override
@@ -63,18 +63,18 @@ public class OnlineUserServiceImpl implements OnlineUserService {
List<LoginUser> loginUserList = new ArrayList<>();
// 查询所有登录用户
List<String> tokenKeyList = StpUtil.searchTokenValue(StringConstants.EMPTY, 0, -1, false);
for (String tokenKey : tokenKeyList) {
tokenKeyList.parallelStream().forEach(tokenKey -> {
String token = StrUtil.subAfter(tokenKey, StringConstants.COLON, true);
// 忽略已过期或失效 Token
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < SaTokenDao.NEVER_EXPIRE) {
continue;
return;
}
// 检查是否符合查询条件
LoginUser loginUser = LoginHelper.getLoginUser(token);
if (this.isMatchQuery(query, loginUser)) {
loginUserList.add(loginUser);
}
}
});
// 设置排序
CollUtil.sort(loginUserList, Comparator.comparing(LoginUser::getLoginTime).reversed());
return loginUserList;
@@ -87,20 +87,7 @@ public class OnlineUserServiceImpl implements OnlineUserService {
}
@Override
public void cleanByRoleId(Long roleId) {
List<LoginUser> loginUserList = this.list(new OnlineUserQuery());
loginUserList.parallelStream().forEach(u -> {
if (u.getRoles().stream().anyMatch(r -> r.getId().equals(roleId))) {
try {
StpUtil.logoutByTokenValue(u.getToken());
} catch (NotLoginException ignored) {
}
}
});
}
@Override
public void cleanByUserId(Long userId) {
public void kickOut(Long userId) {
if (!StpUtil.isLogin(userId)) {
return;
}
@@ -121,13 +108,22 @@ public class OnlineUserServiceImpl implements OnlineUserService {
flag1 = StrUtil.contains(loginUser.getUsername(), nickname) || StrUtil.contains(LoginHelper
.getNickname(loginUser.getId()), nickname);
}
boolean flag2 = true;
List<Date> loginTime = query.getLoginTime();
if (CollUtil.isNotEmpty(loginTime)) {
flag2 = DateUtil.isIn(DateUtil.date(loginUser.getLoginTime()).toJdkDate(), loginTime.get(0), loginTime
.get(1));
}
return flag1 && flag2;
boolean flag3 = true;
Long userId = query.getUserId();
if (null != userId) {
flag3 = userId.equals(loginUser.getId());
}
boolean flag4 = true;
Long roleId = query.getRoleId();
if (null != roleId) {
flag4 = loginUser.getRoles().stream().anyMatch(r -> r.getId().equals(roleId));
}
return flag1 && flag2 && flag3 && flag4;
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.auth.service.impl;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.auth.service.PermissionService;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.system.service.MenuService;
import top.continew.admin.system.service.RoleService;
import java.util.Set;
/**
* 权限业务实现
*
* @author Charles7c
* @since 2023/3/2 20:40
*/
@Service
@RequiredArgsConstructor
public class PermissionServiceImpl implements PermissionService {
private final MenuService menuService;
private final RoleService roleService;
@Override
public Set<String> listPermissionByUserId(Long userId) {
Set<String> roleCodeSet = this.listRoleCodeByUserId(userId);
// 超级管理员赋予全部权限
if (roleCodeSet.contains(SysConstants.ADMIN_ROLE_CODE)) {
return CollUtil.newHashSet(SysConstants.ALL_PERMISSION);
}
return menuService.listPermissionByUserId(userId);
}
@Override
public Set<String> listRoleCodeByUserId(Long userId) {
return roleService.listCodeByUserId(userId);
}
}

View File

@@ -17,7 +17,7 @@
package top.continew.admin.system.mapper;
import top.continew.admin.system.model.entity.DeptDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 部门 Mapper

View File

@@ -18,7 +18,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import top.continew.admin.system.model.entity.DictItemDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import java.util.List;

View File

@@ -17,7 +17,7 @@
package top.continew.admin.system.mapper;
import top.continew.admin.system.model.entity.DictDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 字典 Mapper

View File

@@ -19,7 +19,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.resp.FileStatisticsResp;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -25,7 +25,7 @@ import top.continew.admin.system.model.resp.DashboardAccessTrendResp;
import top.continew.admin.system.model.resp.DashboardPopularModuleResp;
import top.continew.admin.system.model.resp.DashboardTotalResp;
import top.continew.admin.system.model.resp.log.LogResp;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;
import java.util.Map;

View File

@@ -18,7 +18,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import top.continew.admin.system.model.entity.MenuDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;
import java.util.Set;

View File

@@ -22,7 +22,7 @@ import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import top.continew.admin.system.model.entity.MessageDO;
import top.continew.admin.system.model.resp.MessageResp;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 消息 Mapper

View File

@@ -18,7 +18,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import top.continew.admin.system.model.entity.MessageUserDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 消息和用户 Mapper

View File

@@ -18,7 +18,7 @@ package top.continew.admin.system.mapper;
import top.continew.admin.system.model.entity.NoticeDO;
import top.continew.admin.system.model.resp.DashboardNoticeResp;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -19,7 +19,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.system.model.entity.OptionDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -19,7 +19,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.system.model.entity.RoleDeptDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -17,7 +17,7 @@
package top.continew.admin.system.mapper;
import top.continew.admin.system.model.entity.RoleDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 角色 Mapper

View File

@@ -17,7 +17,7 @@
package top.continew.admin.system.mapper;
import top.continew.admin.system.model.entity.RoleMenuDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -17,7 +17,7 @@
package top.continew.admin.system.mapper;
import top.continew.admin.system.model.entity.StorageDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 存储 Mapper

View File

@@ -24,7 +24,7 @@ import org.apache.ibatis.annotations.Select;
import top.continew.admin.common.config.mybatis.DataPermissionMapper;
import top.continew.admin.system.model.entity.UserDO;
import top.continew.admin.system.model.resp.UserDetailResp;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermission;
import top.continew.starter.data.mp.datapermission.DataPermission;
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import java.util.List;
@@ -44,7 +44,7 @@ public interface UserMapper extends DataPermissionMapper<UserDO> {
* @param queryWrapper 查询条件
* @return 分页列表信息
*/
@DataPermission("t1")
@DataPermission(tableAlias = "t1")
IPage<UserDetailResp> selectUserPage(@Param("page") IPage<UserDO> page,
@Param(Constants.WRAPPER) QueryWrapper<UserDO> queryWrapper);
@@ -54,7 +54,7 @@ public interface UserMapper extends DataPermissionMapper<UserDO> {
* @param queryWrapper 查询条件
* @return 列表信息
*/
@DataPermission("t1")
@DataPermission(tableAlias = "t1")
List<UserDetailResp> selectUserList(@Param(Constants.WRAPPER) QueryWrapper<UserDO> queryWrapper);
/**

View File

@@ -17,7 +17,7 @@
package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.admin.system.model.entity.UserPasswordHistoryDO;
/**

View File

@@ -19,7 +19,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.system.model.entity.UserRoleDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import java.util.List;

View File

@@ -18,7 +18,7 @@ package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Param;
import top.continew.admin.system.model.entity.UserSocialDO;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 用户社会化关联 Mapper

View File

@@ -18,6 +18,7 @@ package top.continew.admin.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import top.continew.starter.extension.crud.annotation.DictField;
import top.continew.starter.extension.crud.model.entity.BaseDO;
import java.io.Serial;
@@ -29,6 +30,7 @@ import java.io.Serial;
* @since 2023/9/11 21:29
*/
@Data
@DictField(valueKey = "code")
@TableName("sys_dict")
public class DictDO extends BaseDO {

View File

@@ -67,4 +67,14 @@ public class RoleDO extends BaseDO {
* 是否为系统内置数据
*/
private Boolean isSystem;
/**
* 菜单选择是否父子节点关联
*/
private Boolean menuCheckStrictly;
/**
* 部门选择是否父子节点关联
*/
private Boolean deptCheckStrictly;
}

View File

@@ -42,5 +42,5 @@ public class RoleQuery implements Serializable {
*/
@Schema(description = "关键词", example = "测试人员")
@Query(columns = {"name", "code", "description"}, type = QueryType.LIKE)
private String name;
private String description;
}

View File

@@ -90,4 +90,16 @@ public class RoleReq extends BaseReq {
*/
@Schema(description = "权限范围:部门 ID 列表", example = "5")
private List<Long> deptIds = new ArrayList<>();
/**
* 菜单选择是否父子节点关联
*/
@Schema(description = "菜单选择是否父子节点关联", example = "false")
private Boolean menuCheckStrictly;
/**
* 部门选择是否父子节点关联
*/
@Schema(description = "部门选择是否父子节点关联", example = "false")
private Boolean deptCheckStrictly;
}

View File

@@ -81,6 +81,18 @@ public class RoleDetailResp extends BaseDetailResp {
@ExcelProperty(value = "系统内置")
private Boolean isSystem;
/**
* 菜单选择是否父子节点关联
*/
@Schema(description = "菜单选择是否父子节点关联", example = "false")
private Boolean menuCheckStrictly;
/**
* 部门选择是否父子节点关联
*/
@Schema(description = "部门选择是否父子节点关联", example = "false")
private Boolean deptCheckStrictly;
/**
* 描述
*/

View File

@@ -20,7 +20,7 @@ import top.continew.admin.system.model.entity.DeptDO;
import top.continew.admin.system.model.query.DeptQuery;
import top.continew.admin.system.model.req.DeptReq;
import top.continew.admin.system.model.resp.DeptResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
import java.util.List;

View File

@@ -20,7 +20,7 @@ import top.continew.admin.system.model.entity.DictItemDO;
import top.continew.admin.system.model.query.DictItemQuery;
import top.continew.admin.system.model.req.DictItemReq;
import top.continew.admin.system.model.resp.DictItemResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseService;
@@ -48,4 +48,11 @@ public interface DictItemService extends BaseService<DictItemResp, DictItemResp,
* @param dictIds 字典 ID 列表
*/
void deleteByDictIds(List<Long> dictIds);
/**
* 查询枚举字典名称列表
*
* @return 枚举字典名称列表
*/
List<String> listEnumDictNames();
}

View File

@@ -20,9 +20,12 @@ import top.continew.admin.system.model.entity.DictDO;
import top.continew.admin.system.model.query.DictQuery;
import top.continew.admin.system.model.req.DictReq;
import top.continew.admin.system.model.resp.DictResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseService;
import java.util.List;
/**
* 字典业务接口
*
@@ -30,4 +33,11 @@ import top.continew.starter.extension.crud.service.BaseService;
* @since 2023/9/11 21:29
*/
public interface DictService extends BaseService<DictResp, DictResp, DictQuery, DictReq>, IService<DictDO> {
/**
* 查询枚举字典
*
* @return 枚举字典列表
*/
List<LabelValueResp> listEnumDict();
}

View File

@@ -23,7 +23,7 @@ import top.continew.admin.system.model.query.FileQuery;
import top.continew.admin.system.model.req.FileReq;
import top.continew.admin.system.model.resp.FileResp;
import top.continew.admin.system.model.resp.FileStatisticsResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
import java.util.List;

View File

@@ -21,7 +21,7 @@ import top.continew.admin.system.model.query.MenuQuery;
import top.continew.admin.system.model.req.MenuReq;
import top.continew.admin.system.model.resp.MenuResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import java.util.List;
import java.util.Set;

View File

@@ -23,7 +23,7 @@ import top.continew.admin.system.model.resp.NoticeDetailResp;
import top.continew.admin.system.model.resp.NoticeResp;
import top.continew.admin.system.model.resp.DashboardNoticeResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import java.util.List;

View File

@@ -22,7 +22,7 @@ import top.continew.admin.system.model.query.RoleQuery;
import top.continew.admin.system.model.req.RoleReq;
import top.continew.admin.system.model.resp.RoleDetailResp;
import top.continew.admin.system.model.resp.RoleResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
import java.util.List;
@@ -36,6 +36,14 @@ import java.util.Set;
*/
public interface RoleService extends BaseService<RoleResp, RoleDetailResp, RoleQuery, RoleReq>, IService<RoleDO> {
/**
* 根据用户 ID 查询权限码
*
* @param userId 用户 ID
* @return 权限码集合
*/
Set<String> listPermissionByUserId(Long userId);
/**
* 根据 ID 列表查询
*

View File

@@ -20,7 +20,7 @@ import top.continew.admin.system.model.entity.StorageDO;
import top.continew.admin.system.model.query.StorageQuery;
import top.continew.admin.system.model.req.StorageReq;
import top.continew.admin.system.model.resp.StorageResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
/**

View File

@@ -25,7 +25,7 @@ import top.continew.admin.system.model.resp.UserDetailResp;
import top.continew.admin.system.model.resp.UserImportParseResp;
import top.continew.admin.system.model.resp.UserImportResp;
import top.continew.admin.system.model.resp.UserResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
import java.io.IOException;

View File

@@ -32,8 +32,8 @@ import top.continew.admin.system.service.DictItemService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.core.enums.BaseEnum;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
@@ -81,6 +81,11 @@ public class DictItemServiceImpl extends BaseServiceImpl<DictItemMapper, DictIte
RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + StringConstants.ASTERISK);
}
@Override
public List<String> listEnumDictNames() {
return ENUM_DICT_CACHE.keySet().stream().toList();
}
/**
* 字典值是否存在
*

View File

@@ -26,6 +26,7 @@ import top.continew.admin.system.model.resp.DictResp;
import top.continew.admin.system.service.DictItemService;
import top.continew.admin.system.service.DictService;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
import java.util.List;
@@ -71,6 +72,12 @@ public class DictServiceImpl extends BaseServiceImpl<DictMapper, DictDO, DictRes
dictItemService.deleteByDictIds(ids);
}
@Override
public List<LabelValueResp> listEnumDict() {
List<String> enumDictNameList = dictItemService.listEnumDictNames();
return enumDictNameList.stream().map(name -> new LabelValueResp(name, name)).toList();
}
/**
* 名称是否存在
*

View File

@@ -19,11 +19,11 @@ package top.continew.admin.system.service.impl;
import cn.crane4j.annotation.AutoOperate;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -67,7 +67,8 @@ public class LogServiceImpl implements LogService {
@Override
public PageResp<LogResp> page(LogQuery query, PageQuery pageQuery) {
QueryWrapper<LogDO> queryWrapper = this.buildQueryWrapper(query);
IPage<LogResp> page = baseMapper.selectLogPage(pageQuery.toPage(), queryWrapper);
IPage<LogResp> page = baseMapper.selectLogPage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}
@@ -132,12 +133,12 @@ public class LogServiceImpl implements LogService {
* @param sortQuery 排序查询条件
*/
private void sort(QueryWrapper<LogDO> queryWrapper, SortQuery sortQuery) {
Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort();
for (Sort.Order order : sort) {
if (null != order) {
String property = order.getProperty();
queryWrapper.orderBy(true, order.isAscending(), CharSequenceUtil.toUnderlineCase(property));
}
if (sortQuery == null || sortQuery.getSort().isUnsorted()) {
return;
}
for (Sort.Order order : sortQuery.getSort()) {
String property = order.getProperty();
queryWrapper.orderBy(true, order.isAscending(), CharSequenceUtil.toUnderlineCase(property));
}
}

View File

@@ -52,7 +52,12 @@ public class MenuServiceImpl extends BaseServiceImpl<MenuMapper, MenuDO, MenuRes
@Override
public Long add(MenuReq req) {
String title = req.getTitle();
CheckUtils.throwIf(this.isNameExists(title, req.getParentId(), null), "新增失败,[{}] 已存在", title);
CheckUtils.throwIf(this.isTitleExists(title, req.getParentId(), null), "新增失败,标题 [{}] 已存在", title);
// 目录和菜单的组件名称不能重复
if (!MenuTypeEnum.BUTTON.equals(req.getType())) {
String name = req.getName();
CheckUtils.throwIf(this.isNameExists(name, null), "新增失败,组件名称 [{}] 已存在", name);
}
// 目录类型菜单,默认为 Layout
if (MenuTypeEnum.DIR.equals(req.getType())) {
req.setComponent(StrUtil.blankToDefault(req.getComponent(), "Layout"));
@@ -64,7 +69,12 @@ public class MenuServiceImpl extends BaseServiceImpl<MenuMapper, MenuDO, MenuRes
@Override
public void update(MenuReq req, Long id) {
String title = req.getTitle();
CheckUtils.throwIf(this.isNameExists(title, req.getParentId(), id), "修改失败,[{}] 已存在", title);
CheckUtils.throwIf(this.isTitleExists(title, req.getParentId(), id), "修改失败,标题 [{}] 已存在", title);
// 目录和菜单的组件名称不能重复
if (!MenuTypeEnum.BUTTON.equals(req.getType())) {
String name = req.getName();
CheckUtils.throwIf(this.isNameExists(name, id), "修改失败,组件名称 [{}] 已存在", name);
}
MenuDO oldMenu = super.getById(id);
CheckUtils.throwIfNotEqual(req.getType(), oldMenu.getType(), "不允许修改菜单类型");
super.update(req, id);
@@ -100,18 +110,33 @@ public class MenuServiceImpl extends BaseServiceImpl<MenuMapper, MenuDO, MenuRes
}
/**
* 名称是否存在
* 标题是否存在
*
* @param name 名称
* @param title 标题
* @param parentId 上级 ID
* @param id ID
* @return 是否存在
* @return true存在false存在
*/
private boolean isNameExists(String name, Long parentId, Long id) {
private boolean isTitleExists(String title, Long parentId, Long id) {
return baseMapper.lambdaQuery()
.eq(MenuDO::getTitle, name)
.eq(MenuDO::getTitle, title)
.eq(MenuDO::getParentId, parentId)
.ne(null != id, MenuDO::getId, id)
.exists();
}
/**
* 名称是否存在
*
* @param name 标题
* @param id ID
* @return true存在false不存在
*/
private boolean isNameExists(String name, Long id) {
return baseMapper.lambdaQuery()
.eq(MenuDO::getName, name)
.ne(MenuDO::getType, MenuTypeEnum.BUTTON)
.ne(null != id, MenuDO::getId, id)
.exists();
}
}

View File

@@ -21,6 +21,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,7 +33,7 @@ import top.continew.admin.system.model.resp.MessageResp;
import top.continew.admin.system.service.MessageService;
import top.continew.admin.system.service.MessageUserService;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.data.mybatis.plus.query.QueryWrapperHelper;
import top.continew.starter.data.mp.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
@@ -54,10 +55,11 @@ public class MessageServiceImpl implements MessageService {
@Override
@AutoOperate(type = MessageResp.class, on = "list")
public PageResp<MessageResp> page(MessageQuery query, PageQuery pageQuery) {
QueryWrapper<MessageDO> queryWrapper = QueryWrapperHelper.build(query);
QueryWrapper<MessageDO> queryWrapper = QueryWrapperHelper.build(query, pageQuery.getSort());
queryWrapper.apply(null != query.getUserId(), "t2.user_id={0}", query.getUserId())
.apply(null != query.getIsRead(), "t2.is_read={0}", query.getIsRead());
IPage<MessageResp> page = baseMapper.selectPageByUserId(pageQuery.toPage(), queryWrapper);
IPage<MessageResp> page = baseMapper.selectPageByUserId(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}

View File

@@ -38,7 +38,7 @@ import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mybatis.plus.query.QueryWrapperHelper;
import top.continew.starter.data.mp.util.QueryWrapperHelper;
import java.util.List;
import java.util.Map;

View File

@@ -26,12 +26,15 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.auth.model.query.OnlineUserQuery;
import top.continew.admin.auth.service.OnlineUserService;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.ContainerConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DataScopeEnum;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.model.dto.RoleDTO;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.mapper.RoleMapper;
import top.continew.admin.system.model.entity.RoleDO;
import top.continew.admin.system.model.query.RoleQuery;
@@ -57,10 +60,10 @@ import java.util.stream.Collectors;
public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleResp, RoleDetailResp, RoleQuery, RoleReq> implements RoleService {
private final MenuService menuService;
private final OnlineUserService onlineUserService;
private final RoleMenuService roleMenuService;
private final RoleDeptService roleDeptService;
private final UserRoleService userRoleService;
private final OnlineUserService onlineUserService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -92,16 +95,23 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes
}
// 更新信息
super.update(req, id);
// 更新关联信息
if (!SysConstants.ADMIN_ROLE_CODE.equals(oldRole.getCode())) {
// 保存角色和菜单关联
boolean isSaveMenuSuccess = roleMenuService.add(req.getMenuIds(), id);
// 保存角色和部门关联
boolean isSaveDeptSuccess = roleDeptService.add(req.getDeptIds(), id);
// 如果功能权限或数据权限有变更,则清除关联的在线用户(重新登录以获取最新角色权限)
if (ObjectUtil.notEqual(req.getDataScope(), oldDataScope) || isSaveMenuSuccess || isSaveDeptSuccess) {
onlineUserService.cleanByRoleId(id);
}
if (SysConstants.ADMIN_ROLE_CODE.equals(req.getCode())) {
return;
}
// 保存角色和菜单关联
boolean isSaveMenuSuccess = roleMenuService.add(req.getMenuIds(), id);
// 保存角色和部门关联
boolean isSaveDeptSuccess = roleDeptService.add(req.getDeptIds(), id);
// 如果功能权限或数据权限有变更,则更新在线用户权限信息
if (ObjectUtil.notEqual(req.getDataScope(), oldDataScope) || isSaveMenuSuccess || isSaveDeptSuccess) {
OnlineUserQuery query = new OnlineUserQuery();
query.setRoleId(id);
List<LoginUser> loginUserList = onlineUserService.list(query);
loginUserList.parallelStream().forEach(loginUser -> {
loginUser.setRoles(this.listByUserId(loginUser.getId()));
loginUser.setPermissions(this.listPermissionByUserId(loginUser.getId()));
LoginHelper.updateLoginUser(loginUser, loginUser.getToken());
});
}
}
@@ -136,6 +146,16 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes
}
}
@Override
public Set<String> listPermissionByUserId(Long userId) {
Set<String> roleCodeSet = this.listCodeByUserId(userId);
// 超级管理员赋予全部权限
if (roleCodeSet.contains(SysConstants.ADMIN_ROLE_CODE)) {
return CollUtil.newHashSet(SysConstants.ALL_PERMISSION);
}
return menuService.listPermissionByUserId(userId);
}
@Override
@ContainerMethod(namespace = ContainerConstants.USER_ROLE_NAME_LIST, type = MappingType.ORDER_OF_KEYS)
public List<String> listNameByIds(List<Long> ids) {

View File

@@ -20,12 +20,14 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
@@ -38,6 +40,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
@@ -49,11 +52,13 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import top.continew.admin.auth.model.query.OnlineUserQuery;
import top.continew.admin.auth.service.OnlineUserService;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.enums.GenderEnum;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.mapper.UserMapper;
@@ -77,6 +82,7 @@ import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.CommonUserService;
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
import top.continew.starter.web.util.FileUploadUtils;
import java.io.IOException;
import java.time.Duration;
@@ -98,11 +104,11 @@ import static top.continew.admin.system.enums.PasswordPolicyEnum.*;
@RequiredArgsConstructor
public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserResp, UserDetailResp, UserQuery, UserReq> implements UserService, CommonUserService {
private final OnlineUserService onlineUserService;
private final UserRoleService userRoleService;
private final PasswordEncoder passwordEncoder;
private final OptionService optionService;
private final UserPasswordHistoryService userPasswordHistoryService;
private final OnlineUserService onlineUserService;
private final OptionService optionService;
private final UserRoleService userRoleService;
private final RoleService roleService;
@Resource
@@ -113,7 +119,9 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
@Override
public PageResp<UserResp> page(UserQuery query, PageQuery pageQuery) {
QueryWrapper<UserDO> queryWrapper = this.buildQueryWrapper(query);
IPage<UserDetailResp> page = baseMapper.selectUserPage(pageQuery.toPage(), queryWrapper);
super.sort(queryWrapper, pageQuery);
IPage<UserDetailResp> page = baseMapper.selectUserPage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
PageResp<UserResp> pageResp = PageResp.build(page, super.getListClass());
pageResp.getList().forEach(this::fill);
return pageResp;
@@ -129,13 +137,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
@Override
public void downloadImportUserTemplate(HttpServletResponse response) throws IOException {
try {
byte[] bytes = ResourceUtil.readBytes("templates/import/userImportTemplate.xlsx");
response.setHeader("Content-Disposition", "attachment;filename=" + URLUtil.encode("用户导入模板.xlsx"));
response.addHeader("Content-Length", String.valueOf(bytes.length));
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setContentType("application/octet-stream;charset=UTF-8");
IoUtil.write(response.getOutputStream(), true, bytes);
FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/userImportTemplate.xlsx"), "用户导入模板.xlsx");
} catch (Exception e) {
log.error("下载用户导入模板失败:", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
@@ -323,9 +326,21 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
baseMapper.updateById(newUser);
// 保存用户和角色关联
boolean isSaveUserRoleSuccess = userRoleService.add(req.getRoleIds(), id);
// 如果功能权限或数据权限有变更,则清除关联的在线用户(重新登录以获取最新角色权限)
if (DisEnableStatusEnum.DISABLE.equals(newStatus) || isSaveUserRoleSuccess) {
onlineUserService.cleanByUserId(id);
// 如果禁用用户,则踢出在线用户
if (DisEnableStatusEnum.DISABLE.equals(newStatus)) {
onlineUserService.kickOut(id);
return;
}
// 如果角色有变更,则更新在线用户权限信息
if (isSaveUserRoleSuccess) {
OnlineUserQuery query = new OnlineUserQuery();
query.setUserId(id);
List<LoginUser> loginUserList = onlineUserService.list(query);
loginUserList.parallelStream().forEach(loginUser -> {
loginUser.setRoles(roleService.listByUserId(loginUser.getId()));
loginUser.setPermissions(roleService.listPermissionByUserId(loginUser.getId()));
LoginHelper.updateLoginUser(loginUser, loginUser.getToken());
});
}
}
@@ -351,10 +366,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
@Override
public void resetPassword(UserPasswordResetReq req, Long id) {
UserDO user = super.getById(id);
user.setPassword(req.getNewPassword());
user.setPwdResetTime(LocalDateTime.now());
baseMapper.updateById(user);
super.getById(id);
baseMapper.lambdaUpdate()
.set(UserDO::getPassword, req.getNewPassword())
.set(UserDO::getPwdResetTime, LocalDateTime.now())
.eq(UserDO::getId, id)
.update();
}
@Override
@@ -399,9 +416,11 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
// 校验密码合法性
int passwordRepetitionTimes = this.checkPassword(newPassword, user);
// 更新密码和密码重置时间
user.setPassword(newPassword);
user.setPwdResetTime(LocalDateTime.now());
baseMapper.updateById(user);
baseMapper.lambdaUpdate()
.set(UserDO::getPassword, newPassword)
.set(UserDO::getPwdResetTime, LocalDateTime.now())
.eq(UserDO::getId, id)
.update();
// 保存历史密码
userPasswordHistoryService.add(id, password, passwordRepetitionTimes);
// 修改后登出

View File

@@ -17,18 +17,18 @@
<!-- 启动类 -->
<main-class>top.continew.admin.ContiNewAdminApplication</main-class>
<!-- 程序 jar 输出目录 -->
<bin-path>bin</bin-path>
<bin-path>bin/</bin-path>
<!-- 配置文件输出目录 -->
<config-path>config</config-path>
<config-path>config/</config-path>
<!-- 依赖 jar 输出目录 -->
<lib-path>lib</lib-path>
<lib-path>lib/</lib-path>
</properties>
<dependencies>
<!-- ContiNew Starter 日志模块 - HttpTraceProSpring Boot Actuator HttpTrace 定制增强版) -->
<!-- ContiNew Starter 日志模块 - 拦截器版Spring Boot Actuator HttpTrace 增强版) -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-httptrace-pro</artifactId>
<artifactId>continew-starter-log-interceptor</artifactId>
</dependency>
<!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) -->
@@ -75,19 +75,22 @@
<!-- 排除配置文件 -->
<excludes>
<exclude>${config-path}/</exclude>
<exclude>db/</exclude>
<exclude>templates/</exclude>
<exclude>logback-spring.xml</exclude>
</excludes>
<archive>
<manifest>
<mainClass>${main-class}</mainClass>
<!-- 为 MANIFEST.MF 中的 Class-Path 加入依赖 jar 目录前缀 -->
<classpathPrefix>../${lib-path}/</classpathPrefix>
<classpathPrefix>../${lib-path}</classpathPrefix>
<addClasspath>true</addClasspath>
<!-- jar 包不包含唯一版本标识 -->
<useUniqueVersions>false</useUniqueVersions>
</manifest>
<manifestEntries>
<!--为 MANIFEST.MF 中的 Class-Path 加入配置文件目录前缀 -->
<Class-Path>../${config-path}/</Class-Path>
<Class-Path>../${config-path}</Class-Path>
</manifestEntries>
</archive>
<outputDirectory>${project.build.directory}/app/${bin-path}</outputDirectory>
@@ -123,14 +126,19 @@
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources/${config-path}</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>${config-path}/</include>
<include>db/</include>
<include>templates/</include>
<include>logback-spring.xml</include>
</includes>
</resource>
</resources>
<outputDirectory>${project.build.directory}/app</outputDirectory>
<outputDirectory>${project.build.directory}/app/${config-path}</outputDirectory>
</configuration>
</execution>
</executions>

View File

@@ -35,7 +35,8 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.extension.crud.annotation.EnableCrudRestController;
import top.continew.starter.web.annotation.EnableGlobalExceptionHandler;
import top.continew.starter.web.annotation.EnableGlobalResponse;
import top.continew.starter.web.model.R;
/**
* 启动程序
@@ -44,13 +45,13 @@ import top.continew.starter.web.annotation.EnableGlobalExceptionHandler;
* @since 2022/12/8 23:15
*/
@Slf4j
@RestController
@EnableFileStorage
@EnableMethodCache(basePackages = "top.continew.admin")
@EnableGlobalResponse
@EnableCrudRestController
@RestController
@SpringBootApplication
@RequiredArgsConstructor
@EnableCrudRestController
@EnableGlobalExceptionHandler
@EnableMethodCache(basePackages = "top.continew.admin")
public class ContiNewAdminApplication implements ApplicationRunner {
private final ProjectProperties projectProperties;
@@ -60,16 +61,11 @@ public class ContiNewAdminApplication implements ApplicationRunner {
SpringApplication.run(ContiNewAdminApplication.class, args);
}
/**
* 访问首页提示
*
* @return /
*/
@Hidden
@SaIgnore
@GetMapping("/")
public String index() {
return "%s service started successfully.".formatted(projectProperties.getName());
public R index() {
return R.ok("%s service started successfully.".formatted(projectProperties.getName()), null);
}
@Override

View File

@@ -21,7 +21,7 @@ import org.springframework.context.annotation.Configuration;
import top.continew.admin.system.mapper.LogMapper;
import top.continew.admin.system.service.UserService;
import top.continew.starter.log.core.dao.LogDao;
import top.continew.starter.log.httptracepro.autoconfigure.ConditionalOnEnabledLog;
import top.continew.starter.log.interceptor.autoconfigure.ConditionalOnEnabledLog;
import top.continew.starter.web.autoconfigure.trace.TraceProperties;
/**

View File

@@ -22,6 +22,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
@@ -43,7 +44,6 @@ import top.continew.starter.log.core.model.LogResponse;
import top.continew.starter.web.autoconfigure.trace.TraceProperties;
import top.continew.starter.web.model.R;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
@@ -66,63 +66,101 @@ public class LogDaoLocalImpl implements LogDao {
@Override
public void add(LogRecord logRecord) {
LogDO logDO = new LogDO();
logDO.setDescription(logRecord.getDescription());
String module = logRecord.getModule();
logDO.setModule(StrUtils.blankToDefault(module, null, m -> m.replace("API", StringConstants.EMPTY).trim()));
logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault()));
logDO.setTimeTaken(logRecord.getTimeTaken().toMillis());
// 请求信息
// 设置请求信息
LogRequest logRequest = logRecord.getRequest();
this.setRequest(logDO, logRequest);
// 设置响应信息
LogResponse logResponse = logRecord.getResponse();
this.setResponse(logDO, logResponse);
// 设置基本信息
logDO.setDescription(logRecord.getDescription());
logDO.setModule(StrUtils.blankToDefault(logRecord.getModule(), null, m -> m
.replace("API", StringConstants.EMPTY)
.trim()));
logDO.setTimeTaken(logRecord.getTimeTaken().toMillis());
logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault()));
// 设置操作人
this.setCreateUser(logDO, logRequest, logResponse);
logMapper.insert(logDO);
}
/**
* 设置请求信息
*
* @param logDO 日志信息
* @param logRequest 请求信息
*/
private void setRequest(LogDO logDO, LogRequest logRequest) {
logDO.setRequestMethod(logRequest.getMethod());
URI requestUrl = logRequest.getUrl();
String requestUri = requestUrl.getPath();
logDO.setRequestUrl(requestUrl.toString());
Map<String, String> requestHeaderMap = logRequest.getHeaders();
logDO.setRequestHeaders(JSONUtil.toJsonStr(requestHeaderMap));
String requestBody = logRequest.getBody();
logDO.setRequestBody(requestBody);
logDO.setRequestUrl(logRequest.getUrl().toString());
logDO.setRequestHeaders(JSONUtil.toJsonStr(logRequest.getHeaders()));
logDO.setRequestBody(logRequest.getBody());
logDO.setIp(logRequest.getIp());
logDO.setAddress(logRequest.getAddress());
logDO.setBrowser(logRequest.getBrowser());
logDO.setOs(StrUtil.subBefore(logRequest.getOs(), " or", false));
// 响应信息
LogResponse logResponse = logRecord.getResponse();
Integer statusCode = logResponse.getStatus();
logDO.setStatusCode(statusCode);
}
/**
* 设置响应信息
*
* @param logDO 日志信息
* @param logResponse 响应信息
*/
private void setResponse(LogDO logDO, LogResponse logResponse) {
Map<String, String> responseHeaders = logResponse.getHeaders();
logDO.setResponseHeaders(JSONUtil.toJsonStr(responseHeaders));
logDO.setTraceId(responseHeaders.get(traceProperties.getHeaderName()));
logDO.setTraceId(responseHeaders.get(traceProperties.getTraceIdName()));
String responseBody = logResponse.getBody();
logDO.setResponseBody(responseBody);
// 状态
Integer statusCode = logResponse.getStatus();
logDO.setStatusCode(statusCode);
logDO.setStatus(statusCode >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : LogStatusEnum.SUCCESS);
if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) {
if (StrUtil.isNotBlank(responseBody)) {
R result = JSONUtil.toBean(responseBody, R.class);
if (!result.isSuccess()) {
logDO.setStatus(LogStatusEnum.FAILURE);
logDO.setErrorMsg(result.getMsg());
}
// 操作人
if (requestUri.startsWith(SysConstants.LOGOUT_URI)) {
Long loginId = Convert.toLong(result.getData(), -1L);
logDO.setCreateUser(-1 != loginId ? loginId : null);
} else if (result.isSuccess() && requestUri.startsWith(SysConstants.LOGIN_URI)) {
AccountLoginReq loginReq = JSONUtil.toBean(requestBody, AccountLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(loginReq.getUsername())
.getId()));
}
}
// 操作人
}
/**
* 设置操作人
*
* @param logDO 日志信息
* @param logRequest 请求信息
* @param logResponse 响应信息
*/
private void setCreateUser(LogDO logDO, LogRequest logRequest, LogResponse logResponse) {
String requestUri = URLUtil.getPath(logDO.getRequestUrl());
// 解析退出接口信息
String responseBody = logResponse.getBody();
if (requestUri.startsWith(SysConstants.LOGOUT_URI) && StrUtil.isNotBlank(responseBody)) {
R result = JSONUtil.toBean(responseBody, R.class);
logDO.setCreateUser(Convert.toLong(result.getData(), null));
return;
}
// 解析登录接口信息
if (requestUri.startsWith(SysConstants.LOGIN_URI) && LogStatusEnum.SUCCESS.equals(logDO.getStatus())) {
String requestBody = logRequest.getBody();
AccountLoginReq loginReq = JSONUtil.toBean(requestBody, AccountLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(loginReq.getUsername())
.getId()));
return;
}
// 解析 Token 信息
Map<String, String> requestHeaders = logRequest.getHeaders();
String headerName = HttpHeaders.AUTHORIZATION;
boolean isContains = CollUtil.containsAny(requestHeaderMap.keySet(), Set.of(headerName, headerName
boolean isContainsAuthHeader = CollUtil.containsAny(requestHeaders.keySet(), Set.of(headerName, headerName
.toLowerCase()));
if (!requestUri.startsWith(SysConstants.LOGOUT_URI) && MapUtil.isNotEmpty(requestHeaderMap) && isContains) {
String authorization = requestHeaderMap.getOrDefault(headerName, requestHeaderMap.get(headerName
if (MapUtil.isNotEmpty(requestHeaders) && isContainsAuthHeader) {
String authorization = requestHeaders.getOrDefault(headerName, requestHeaders.get(headerName
.toLowerCase()));
String token = authorization.replace(SaManager.getConfig()
.getTokenPrefix() + StringConstants.SPACE, StringConstants.EMPTY);
logDO.setCreateUser(Convert.toLong(StpUtil.getLoginIdByToken(token)));
}
logMapper.insert(logDO);
}
}

View File

@@ -43,7 +43,6 @@ import top.continew.admin.system.service.UserService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.web.model.R;
import top.continew.starter.log.core.annotation.Log;
import java.util.List;
@@ -69,7 +68,7 @@ public class AuthController {
@SaIgnore
@Operation(summary = "账号登录", description = "根据账号和密码进行登录认证")
@PostMapping("/account")
public R<LoginResp> accountLogin(@Validated @RequestBody AccountLoginReq loginReq, HttpServletRequest request) {
public LoginResp accountLogin(@Validated @RequestBody AccountLoginReq loginReq, HttpServletRequest request) {
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + loginReq.getUuid();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
@@ -79,13 +78,13 @@ public class AuthController {
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(loginReq.getPassword()));
ValidationUtils.throwIfBlank(rawPassword, "密码解密失败");
String token = loginService.accountLogin(loginReq.getUsername(), rawPassword, request);
return R.ok(LoginResp.builder().token(token).build());
return LoginResp.builder().token(token).build();
}
@SaIgnore
@Operation(summary = "手机号登录", description = "根据手机号和验证码进行登录认证")
@PostMapping("/phone")
public R<LoginResp> phoneLogin(@Validated @RequestBody PhoneLoginReq loginReq) {
public LoginResp phoneLogin(@Validated @RequestBody PhoneLoginReq loginReq) {
String phone = loginReq.getPhone();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone;
String captcha = RedisUtils.get(captchaKey);
@@ -93,13 +92,13 @@ public class AuthController {
ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR);
RedisUtils.delete(captchaKey);
String token = loginService.phoneLogin(phone);
return R.ok(LoginResp.builder().token(token).build());
return LoginResp.builder().token(token).build();
}
@SaIgnore
@Operation(summary = "邮箱登录", description = "根据邮箱和验证码进行登录认证")
@PostMapping("/email")
public R<LoginResp> emailLogin(@Validated @RequestBody EmailLoginReq loginReq) {
public LoginResp emailLogin(@Validated @RequestBody EmailLoginReq loginReq) {
String email = loginReq.getEmail();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email;
String captcha = RedisUtils.get(captchaKey);
@@ -107,35 +106,35 @@ public class AuthController {
ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR);
RedisUtils.delete(captchaKey);
String token = loginService.emailLogin(email);
return R.ok(LoginResp.builder().token(token).build());
return LoginResp.builder().token(token).build();
}
@Operation(summary = "用户退出", description = "注销用户的当前登录")
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx", in = ParameterIn.HEADER)
@PostMapping("/logout")
public R<Object> logout() {
public Object logout() {
Object loginId = StpUtil.getLoginId(-1L);
StpUtil.logout();
return R.ok(loginId);
return loginId;
}
@Log(ignore = true)
@Operation(summary = "获取用户信息", description = "获取登录用户信息")
@GetMapping("/user/info")
public R<UserInfoResp> getUserInfo() {
public UserInfoResp getUserInfo() {
LoginUser loginUser = LoginHelper.getLoginUser();
UserDetailResp userDetailResp = userService.get(loginUser.getId());
UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class);
userInfoResp.setPermissions(loginUser.getPermissions());
userInfoResp.setRoles(loginUser.getRoleCodes());
userInfoResp.setPwdExpired(loginUser.isPasswordExpired());
return R.ok(userInfoResp);
return userInfoResp;
}
@Log(ignore = true)
@Operation(summary = "获取路由信息", description = "获取登录用户的路由信息")
@GetMapping("/route")
public R<List<RouteResp>> listRoute() {
return R.ok(loginService.buildRouteTree(LoginHelper.getUserId()));
public List<RouteResp> listRoute() {
return loginService.buildRouteTree(LoginHelper.getUserId());
}
}

View File

@@ -36,7 +36,6 @@ import top.continew.admin.auth.service.LoginService;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
/**
* 三方账号认证 API
@@ -58,17 +57,17 @@ public class SocialAuthController {
@Operation(summary = "三方账号登录授权", description = "三方账号登录授权")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@GetMapping("/{source}")
public R<SocialAuthAuthorizeResp> authorize(@PathVariable String source) {
public SocialAuthAuthorizeResp authorize(@PathVariable String source) {
AuthRequest authRequest = this.getAuthRequest(source);
return R.ok(SocialAuthAuthorizeResp.builder()
return SocialAuthAuthorizeResp.builder()
.authorizeUrl(authRequest.authorize(AuthStateUtils.createState()))
.build());
.build();
}
@Operation(summary = "三方账号登录", description = "三方账号登录")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@PostMapping("/{source}")
public R<LoginResp> login(@PathVariable String source, @RequestBody AuthCallback callback) {
public LoginResp login(@PathVariable String source, @RequestBody AuthCallback callback) {
if (StpUtil.isLogin()) {
StpUtil.logout();
}
@@ -77,7 +76,7 @@ public class SocialAuthController {
ValidationUtils.throwIf(!response.ok(), response.getMsg());
AuthUser authUser = response.getData();
String token = loginService.socialLogin(authUser);
return R.ok(LoginResp.builder().token(token).build());
return LoginResp.builder().token(token).build();
}
private AuthRequest getAuthRequest(String source) {

View File

@@ -90,33 +90,33 @@ public class CaptchaController {
@Log(ignore = true)
@Operation(summary = "获取行为验证码", description = "获取行为验证码Base64编码")
@GetMapping("/behavior")
public R<Object> getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) {
public Object getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) {
captchaReq.setBrowserInfo(JakartaServletUtil.getClientIP(request) + request.getHeader(HttpHeaders.USER_AGENT));
ResponseModel responseModel = behaviorCaptchaService.get(captchaReq);
CheckUtils.throwIf(() -> !StrUtil.equals(RepCodeEnum.SUCCESS.getCode(), responseModel
.getRepCode()), responseModel.getRepMsg());
return R.ok(responseModel.getRepData());
return responseModel.getRepData();
}
@Log(ignore = true)
@Operation(summary = "校验行为验证码", description = "校验行为验证码")
@PostMapping("/behavior")
public R<Object> checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq, HttpServletRequest request) {
public Object checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq, HttpServletRequest request) {
captchaReq.setBrowserInfo(JakartaServletUtil.getClientIP(request) + request.getHeader(HttpHeaders.USER_AGENT));
return R.ok(behaviorCaptchaService.check(captchaReq));
return behaviorCaptchaService.check(captchaReq);
}
@Log(ignore = true)
@Operation(summary = "获取图片验证码", description = "获取图片验证码Base64编码带图片格式data:image/gif;base64")
@GetMapping("/image")
public R<CaptchaResp> getImageCaptcha() {
public CaptchaResp getImageCaptcha() {
String uuid = IdUtil.fastUUID();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + uuid;
Captcha captcha = graphicCaptchaService.getCaptcha();
long expireTime = LocalDateTimeUtil.toEpochMilli(LocalDateTime.now()
.plusMinutes(captchaProperties.getExpirationInMinutes()));
RedisUtils.set(captchaKey, captcha.text(), Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
return R.ok(CaptchaResp.builder().uuid(uuid).img(captcha.toBase64()).expireTime(expireTime).build());
return CaptchaResp.builder().uuid(uuid).img(captcha.toBase64()).expireTime(expireTime).build();
}
/**
@@ -140,7 +140,7 @@ public class CaptchaController {
@RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "DAY'", key = "#email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath')", rate = 20, interval = 24, unit = RateIntervalUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"),
@RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#email", rate = 100, interval = 24, unit = RateIntervalUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"),
@RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#email", rate = 30, interval = 1, unit = RateIntervalUnit.MINUTES, type = LimitType.IP, message = "获取验证码操作太频繁,请稍后再试")})
public R<Void> getMailCaptcha(@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email) throws MessagingException {
public R getMailCaptcha(@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email) throws MessagingException {
// 生成验证码
CaptchaProperties.CaptchaMail captchaMail = captchaProperties.getMail();
String captcha = RandomUtil.randomNumbers(captchaMail.getLength());
@@ -182,8 +182,8 @@ public class CaptchaController {
@RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "DAY'", key = "#phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId')", rate = 20, interval = 24, unit = RateIntervalUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"),
@RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#phone", rate = 100, interval = 24, unit = RateIntervalUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"),
@RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#phone", rate = 30, interval = 1, unit = RateIntervalUnit.MINUTES, type = LimitType.IP, message = "获取验证码操作太频繁,请稍后再试")})
public R<Void> getSmsCaptcha(@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") String phone,
CaptchaVO captchaReq) {
public R getSmsCaptcha(@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") String phone,
CaptchaVO captchaReq) {
// 行为验证码校验
ResponseModel verificationRes = behaviorCaptchaService.verification(captchaReq);
ValidationUtils.throwIfNotEqual(verificationRes.getRepCode(), RepCodeEnum.SUCCESS.getCode(), verificationRes

View File

@@ -43,7 +43,6 @@ import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
import java.util.List;
@@ -71,49 +70,50 @@ public class CommonController {
@Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/file")
public R<FileUploadResp> upload(@NotNull(message = "文件不能为空") MultipartFile file) {
public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file) {
// TODO 实际开发时请删除本条校验
ValidationUtils.throwIf(projectProperties.isProduction(), "演示环境不支持上传文件");
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
FileInfo fileInfo = fileService.upload(file);
return R.ok(FileUploadResp.builder().url(fileInfo.getUrl()).build());
return FileUploadResp.builder().url(fileInfo.getUrl()).build();
}
@Operation(summary = "查询部门树", description = "查询树结构的部门列表")
@GetMapping("/tree/dept")
public R<List<Tree<Long>>> listDeptTree(DeptQuery query, SortQuery sortQuery) {
return R.ok(deptService.tree(query, sortQuery, true));
public List<Tree<Long>> listDeptTree(DeptQuery query, SortQuery sortQuery) {
return deptService.tree(query, sortQuery, true);
}
@Operation(summary = "查询菜单树", description = "查询树结构的菜单列表")
@GetMapping("/tree/menu")
public R<List<Tree<Long>>> listMenuTree(MenuQuery query, SortQuery sortQuery) {
return R.ok(menuService.tree(query, sortQuery, true));
public List<Tree<Long>> listMenuTree(MenuQuery query, SortQuery sortQuery) {
return menuService.tree(query, sortQuery, true);
}
@Operation(summary = "查询角色字典", description = "查询角色字典列表")
@GetMapping("/dict/role")
public R<List<LabelValueResp>> listRoleDict(RoleQuery query, SortQuery sortQuery) {
return R.ok(roleService.listDict(query, sortQuery));
public List<LabelValueResp> listRoleDict(RoleQuery query, SortQuery sortQuery) {
return roleService.listDict(query, sortQuery);
}
@Operation(summary = "查询字典", description = "查询字典列表")
@Parameter(name = "code", description = "字典编码", example = "notice_type", in = ParameterIn.PATH)
@GetMapping("/dict/{code}")
public R<List<LabelValueResp>> listDict(@PathVariable String code) {
return R.ok(dictItemService.listByDictCode(code));
public List<LabelValueResp> listDict(@PathVariable String code) {
return dictItemService.listByDictCode(code);
}
@SaIgnore
@Operation(summary = "查询参数字典", description = "查询参数字典")
@GetMapping("/dict/option")
@Cached(key = "#category", name = CacheConstants.OPTION_KEY_PREFIX)
public R<List<LabelValueResp<String>>> listOptionDict(@NotBlank(message = "类别不能为空") @RequestParam String category) {
public List<LabelValueResp<String>> listOptionDict(@NotBlank(message = "类别不能为空") String category) {
OptionQuery optionQuery = new OptionQuery();
optionQuery.setCategory(category);
return R.ok(optionService.list(optionQuery)
return optionService.list(optionQuery)
.stream()
.map(option -> new LabelValueResp<>(option.getCode(), StrUtil.nullToDefault(option.getValue(), option
.getDefaultValue())))
.toList());
.toList();
}
}

View File

@@ -38,7 +38,6 @@ import top.continew.admin.system.model.resp.DashboardTotalResp;
import top.continew.admin.system.service.DashboardService;
import top.continew.admin.system.model.resp.DashboardNoticeResp;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.web.model.R;
import top.continew.starter.log.core.annotation.Log;
import java.util.List;
@@ -61,8 +60,8 @@ public class DashboardController {
@Operation(summary = "查询总计信息", description = "查询总计信息")
@GetMapping("/total")
public R<DashboardTotalResp> getTotal() {
return R.ok(dashboardService.getTotal());
public DashboardTotalResp getTotal() {
return dashboardService.getTotal();
}
@Operation(summary = "查询访问趋势信息", description = "查询访问趋势信息")
@@ -71,26 +70,26 @@ public class DashboardController {
@CachePenetrationProtect
@CacheRefresh(refresh = 7200)
@Cached(key = "#days", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true)
public R<List<DashboardAccessTrendResp>> listAccessTrend(@PathVariable Integer days) {
public List<DashboardAccessTrendResp> listAccessTrend(@PathVariable Integer days) {
ValidationUtils.throwIf(7 != days && 30 != days, "仅支持查询近 7/30 天访问趋势信息");
return R.ok(dashboardService.listAccessTrend(days));
return dashboardService.listAccessTrend(days);
}
@Operation(summary = "查询热门模块列表", description = "查询热门模块列表")
@GetMapping("/popular/module")
public R<List<DashboardPopularModuleResp>> listPopularModule() {
return R.ok(dashboardService.listPopularModule());
public List<DashboardPopularModuleResp> listPopularModule() {
return dashboardService.listPopularModule();
}
@Operation(summary = "查询访客地域分布信息", description = "查询访客地域分布信息")
@GetMapping("/geo/distribution")
public R<DashboardGeoDistributionResp> getGeoDistribution() {
return R.ok(dashboardService.getGeoDistribution());
public DashboardGeoDistributionResp getGeoDistribution() {
return dashboardService.getGeoDistribution();
}
@Operation(summary = "查询公告列表", description = "查询公告列表")
@GetMapping("/notice")
public R<List<DashboardNoticeResp>> listNotice() {
return R.ok(dashboardService.listNotice());
public List<DashboardNoticeResp> listNotice() {
return dashboardService.listNotice();
}
}

View File

@@ -35,7 +35,6 @@ import top.continew.admin.auth.service.OnlineUserService;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
/**
* 在线用户 API
@@ -54,18 +53,17 @@ public class OnlineUserController {
@Operation(summary = "分页查询列表", description = "分页查询列表")
@SaCheckPermission("monitor:online:list")
@GetMapping
public R<PageResp<OnlineUserResp>> page(OnlineUserQuery query, @Validated PageQuery pageQuery) {
return R.ok(baseService.page(query, pageQuery));
public PageResp<OnlineUserResp> page(OnlineUserQuery query, @Validated PageQuery pageQuery) {
return baseService.page(query, pageQuery);
}
@Operation(summary = "强退在线用户", description = "强退在线用户")
@Parameter(name = "token", description = "令牌", example = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiTUd6djdyOVFoeHEwdVFqdFAzV3M5YjVJRzh4YjZPSEUifQ.7q7U3ouoN7WPhH2kUEM7vPe5KF3G_qavSG-vRgIxKvE", in = ParameterIn.PATH)
@SaCheckPermission("monitor:online:kickout")
@DeleteMapping("/{token}")
public R<Void> kickout(@PathVariable String token) {
public void kickout(@PathVariable String token) {
String currentToken = StpUtil.getTokenValue();
CheckUtils.throwIfEqual(token, currentToken, "不能强退自己");
StpUtil.kickoutByTokenValue(token);
return R.ok("强退成功");
}
}

View File

@@ -32,7 +32,6 @@ import top.continew.admin.job.service.JobService;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
import java.util.List;
@@ -55,54 +54,53 @@ public class JobController {
@Operation(summary = "分页查询任务列表", description = "分页查询任务列表")
@SaCheckPermission("schedule:job:list")
@GetMapping
public R<PageResp<JobResp>> page(JobQuery query) {
return R.ok(baseService.page(query));
public PageResp<JobResp> page(JobQuery query) {
return baseService.page(query);
}
@Operation(summary = "新增任务", description = "新增任务")
@SaCheckPermission("schedule:job:add")
@PostMapping
public R<Void> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody JobReq req) {
return baseService.add(req) ? R.ok() : R.fail();
public void add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody JobReq req) {
baseService.add(req);
}
@Operation(summary = "修改任务", description = "修改任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:job:update")
@PutMapping("/{id}")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody JobReq req, @PathVariable Long id) {
return baseService.update(req, id) ? R.ok() : R.fail();
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody JobReq req, @PathVariable Long id) {
baseService.update(req, id);
}
@Operation(summary = "修改任务状态", description = "修改任务状态")
@SaCheckPermission("schedule:job:update")
@PatchMapping("/{id}/status")
public R<Void> updateStatus(@Validated @RequestBody JobStatusReq req, @PathVariable Long id) {
return baseService.updateStatus(req, id) ? R.ok() : R.fail();
public void updateStatus(@Validated @RequestBody JobStatusReq req, @PathVariable Long id) {
baseService.updateStatus(req, id);
}
@Operation(summary = "删除任务", description = "删除任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:job:delete")
@DeleteMapping("/{id}")
public R<Void> delete(@PathVariable Long id) {
return baseService.delete(id) ? R.ok() : R.fail();
public void delete(@PathVariable Long id) {
baseService.delete(id);
}
@Operation(summary = "执行任务", description = "执行任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:job:trigger")
@PostMapping("/trigger/{id}")
public R<Void> trigger(@PathVariable Long id) {
return baseService.trigger(id) ? R.ok() : R.fail();
public void trigger(@PathVariable Long id) {
baseService.trigger(id);
}
@Log(ignore = true)
@Operation(summary = "查询任务分组列表", description = "查询任务分组列表")
@SaCheckPermission("schedule:job:list")
@GetMapping("/group")
public R<List<String>> listGroup() {
List<String> groupList = baseService.listGroup();
return R.ok(groupList);
public List<String> listGroup() {
return baseService.listGroup();
}
}

View File

@@ -32,7 +32,6 @@ import top.continew.admin.job.model.resp.JobLogResp;
import top.continew.admin.job.model.resp.JobInstanceResp;
import top.continew.admin.job.service.JobLogService;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
import java.util.List;
@@ -55,37 +54,37 @@ public class JobLogController {
@Operation(summary = "分页查询任务日志列表", description = "分页查询任务日志列表")
@SaCheckPermission("schedule:log:list")
@GetMapping
public R<PageResp<JobLogResp>> page(JobLogQuery query) {
return R.ok(baseService.page(query));
public PageResp<JobLogResp> page(JobLogQuery query) {
return baseService.page(query);
}
@Operation(summary = "停止任务", description = "停止任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:log:stop")
@PostMapping("/stop/{id}")
public R<Void> stop(@PathVariable Long id) {
return baseService.stop(id) ? R.ok() : R.fail();
public void stop(@PathVariable Long id) {
baseService.stop(id);
}
@Operation(summary = "重试任务", description = "重试任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:log:retry")
@PostMapping("/retry/{id}")
public R<Void> retry(@PathVariable Long id) {
return baseService.retry(id) ? R.ok() : R.fail();
public void retry(@PathVariable Long id) {
baseService.retry(id);
}
@Operation(summary = "查询任务实例列表", description = "查询任务实例列表")
@SaCheckPermission("schedule:log:list")
@GetMapping("/instance")
public R<List<JobInstanceResp>> listInstance(JobInstanceQuery query) {
return R.ok(baseService.listInstance(query));
public List<JobInstanceResp> listInstance(JobInstanceQuery query) {
return baseService.listInstance(query);
}
@Operation(summary = "分页查询任务实例日志列表", description = "分页查询任务实例日志列表")
@SaCheckPermission("schedule:log:list")
@GetMapping("/instance/log")
public R<JobInstanceLogPageResult> pageInstanceLog(JobInstanceLogQuery query) {
return R.ok(baseService.pageInstanceLog(query));
public JobInstanceLogPageResult pageInstanceLog(JobInstanceLogQuery query) {
return baseService.pageInstanceLog(query);
}
}

View File

@@ -30,7 +30,6 @@ import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
/**
* 文件管理 API
@@ -48,7 +47,7 @@ public class FileController extends BaseController<FileService, FileResp, FileRe
@Operation(summary = "查询文件资源统计", description = "查询文件资源统计")
@SaCheckPermission("system:file:list")
@GetMapping("/statistics")
public R<FileStatisticsResp> statistics() {
return R.ok(baseService.statistics());
public FileStatisticsResp statistics() {
return baseService.statistics();
}
}

View File

@@ -17,6 +17,7 @@
package top.continew.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
@@ -35,7 +36,6 @@ import top.continew.admin.system.service.LogService;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
/**
* 系统日志 API
@@ -54,18 +54,19 @@ public class LogController {
@Operation(summary = "分页查询列表", description = "分页查询列表")
@SaCheckPermission("monitor:log:list")
@GetMapping
public R<PageResp<LogResp>> page(LogQuery query, @Validated PageQuery pageQuery) {
return R.ok(baseService.page(query, pageQuery));
public PageResp<LogResp> page(LogQuery query, @Validated PageQuery pageQuery) {
return baseService.page(query, pageQuery);
}
@Operation(summary = "查询详情", description = "查询详情")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("monitor:log:list")
@GetMapping("/{id}")
public R<LogDetailResp> get(@PathVariable Long id) {
return R.ok(baseService.get(id));
public LogDetailResp get(@PathVariable Long id) {
return baseService.get(id);
}
@ExcludeFromGracefulResponse
@Operation(summary = "导出登录日志", description = "导出登录日志")
@SaCheckPermission("monitor:log:export")
@GetMapping("/export/login")
@@ -73,6 +74,7 @@ public class LogController {
baseService.exportLoginLog(query, sortQuery, response);
}
@ExcludeFromGracefulResponse
@Operation(summary = "导出操作日志", description = "导出操作日志")
@SaCheckPermission("monitor:log:export")
@GetMapping("/export/operation")

View File

@@ -33,8 +33,8 @@ import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.web.model.R;
/**
* 菜单管理 API
@@ -48,15 +48,15 @@ import top.continew.starter.web.model.R;
public class MenuController extends BaseController<MenuService, MenuResp, MenuResp, MenuQuery, MenuReq> {
@Override
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody MenuReq req) {
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody MenuReq req) {
this.checkPath(req);
return super.add(req);
}
@Override
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody MenuReq req, @PathVariable Long id) {
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody MenuReq req, @PathVariable Long id) {
this.checkPath(req);
return super.update(req, id);
super.update(req, id);
}
/**

View File

@@ -32,7 +32,6 @@ import top.continew.admin.system.service.MessageUserService;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
import java.util.List;
@@ -53,32 +52,30 @@ public class MessageController {
@Operation(summary = "分页查询列表", description = "分页查询列表")
@GetMapping
public R<PageResp<MessageResp>> page(MessageQuery query, @Validated PageQuery pageQuery) {
public PageResp<MessageResp> page(MessageQuery query, @Validated PageQuery pageQuery) {
query.setUserId(LoginHelper.getUserId());
return R.ok(baseService.page(query, pageQuery));
return baseService.page(query, pageQuery);
}
@Operation(summary = "删除数据", description = "删除数据")
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
@DeleteMapping("/{ids}")
public R<Void> delete(@PathVariable List<Long> ids) {
public void delete(@PathVariable List<Long> ids) {
baseService.delete(ids);
return R.ok("删除成功");
}
@Operation(summary = "标记已读", description = "将消息标记为已读状态")
@Parameter(name = "ids", description = "消息ID列表", example = "1,2", in = ParameterIn.QUERY)
@PatchMapping("/read")
public R<Void> readMessage(@RequestParam(required = false) List<Long> ids) {
public void readMessage(@RequestParam(required = false) List<Long> ids) {
messageUserService.readMessage(ids);
return R.ok();
}
@Log(ignore = true)
@Operation(summary = "查询未读消息数量", description = "查询当前用户的未读消息数量")
@Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY)
@GetMapping("/unread")
public R<MessageUnreadResp> countUnreadMessage(@RequestParam(required = false) Boolean detail) {
return R.ok(messageUserService.countUnreadMessageByUserId(LoginHelper.getUserId(), detail));
public MessageUnreadResp countUnreadMessage(@RequestParam(required = false) Boolean detail) {
return messageUserService.countUnreadMessageByUserId(LoginHelper.getUserId(), detail);
}
}

View File

@@ -30,8 +30,8 @@ import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.web.model.R;
import java.time.LocalDateTime;
@@ -47,16 +47,15 @@ import java.time.LocalDateTime;
public class NoticeController extends BaseController<NoticeService, NoticeResp, NoticeDetailResp, NoticeQuery, NoticeReq> {
@Override
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody NoticeReq req) {
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody NoticeReq req) {
this.checkTime(req);
return super.add(req);
}
@Override
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody NoticeReq req,
@PathVariable Long id) {
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody NoticeReq req, @PathVariable Long id) {
this.checkTime(req);
return super.update(req, id);
super.update(req, id);
}
/**

View File

@@ -28,7 +28,6 @@ import top.continew.admin.system.model.req.OptionReq;
import top.continew.admin.system.model.req.OptionResetValueReq;
import top.continew.admin.system.model.resp.OptionResp;
import top.continew.admin.system.service.OptionService;
import top.continew.starter.web.model.R;
import java.util.List;
@@ -50,23 +49,21 @@ public class OptionController {
@Operation(summary = "查询参数列表", description = "查询参数列表")
@SaCheckPermission("system:config:list")
@GetMapping
public R<List<OptionResp>> list(@Validated OptionQuery query) {
return R.ok(baseService.list(query));
public List<OptionResp> list(@Validated OptionQuery query) {
return baseService.list(query);
}
@Operation(summary = "修改参数", description = "修改参数")
@SaCheckPermission("system:config:update")
@PutMapping
public R<Void> update(@Valid @RequestBody List<OptionReq> options) {
public void update(@Valid @RequestBody List<OptionReq> options) {
baseService.update(options);
return R.ok();
}
@Operation(summary = "重置参数", description = "重置参数")
@SaCheckPermission("system:config:reset")
@PatchMapping("/value")
public R<Void> resetValue(@Validated @RequestBody OptionResetValueReq req) {
public void resetValue(@Validated @RequestBody OptionResetValueReq req) {
baseService.resetValue(req);
return R.ok();
}
}

View File

@@ -31,9 +31,9 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.system.enums.SocialSourceEnum;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.enums.SocialSourceEnum;
import top.continew.admin.system.model.entity.UserSocialDO;
import top.continew.admin.system.model.req.UserBasicInfoUpdateReq;
import top.continew.admin.system.model.req.UserEmailUpdateRequest;
@@ -46,7 +46,6 @@ import top.continew.admin.system.service.UserSocialService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.web.model.R;
import java.io.IOException;
import java.util.List;
@@ -72,22 +71,21 @@ public class UserCenterController {
@Operation(summary = "修改头像", description = "用户修改个人头像")
@PostMapping("/avatar")
public R<AvatarResp> updateAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) throws IOException {
public AvatarResp updateAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) throws IOException {
ValidationUtils.throwIf(avatarFile::isEmpty, "头像不能为空");
String newAvatar = userService.updateAvatar(avatarFile, LoginHelper.getUserId());
return R.ok("修改成功", AvatarResp.builder().avatar(newAvatar).build());
return AvatarResp.builder().avatar(newAvatar).build();
}
@Operation(summary = "修改基础信息", description = "修改用户基础信息")
@PatchMapping("/basic/info")
public R<Void> updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) {
public void updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) {
userService.updateBasicInfo(req, LoginHelper.getUserId());
return R.ok("修改成功");
}
@Operation(summary = "修改密码", description = "修改用户登录密码")
@PatchMapping("/password")
public R<Void> updatePassword(@Validated @RequestBody UserPasswordUpdateReq updateReq) {
public void updatePassword(@Validated @RequestBody UserPasswordUpdateReq updateReq) {
String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getOldPassword()));
ValidationUtils.throwIfNull(rawOldPassword, DECRYPT_FAILED);
@@ -95,12 +93,11 @@ public class UserCenterController {
.getNewPassword()));
ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败");
userService.updatePassword(rawOldPassword, rawNewPassword, LoginHelper.getUserId());
return R.ok("修改成功,请牢记你的新密码");
}
@Operation(summary = "修改手机号", description = "修改手机号")
@PatchMapping("/phone")
public R<Void> updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) {
public void updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) {
String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getOldPassword()));
ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED);
@@ -110,12 +107,11 @@ public class UserCenterController {
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey);
userService.updatePhone(updateReq.getPhone(), rawOldPassword, LoginHelper.getUserId());
return R.ok("修改成功");
}
@Operation(summary = "修改邮箱", description = "修改用户邮箱")
@PatchMapping("/email")
public R<Void> updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) {
public void updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) {
String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getOldPassword()));
ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED);
@@ -125,40 +121,36 @@ public class UserCenterController {
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey);
userService.updateEmail(updateReq.getEmail(), rawOldPassword, LoginHelper.getUserId());
return R.ok("修改成功");
}
@Operation(summary = "查询绑定的三方账号", description = "查询绑定的三方账号")
@GetMapping("/social")
public R<List<UserSocialBindResp>> listSocialBind() {
public List<UserSocialBindResp> listSocialBind() {
List<UserSocialDO> userSocialList = userSocialService.listByUserId(LoginHelper.getUserId());
List<UserSocialBindResp> userSocialBindList = userSocialList.stream().map(userSocial -> {
return userSocialList.stream().map(userSocial -> {
String source = userSocial.getSource();
UserSocialBindResp userSocialBind = new UserSocialBindResp();
userSocialBind.setSource(source);
userSocialBind.setDescription(SocialSourceEnum.valueOf(source).getDescription());
return userSocialBind;
}).toList();
return R.ok(userSocialBindList);
}
@Operation(summary = "绑定三方账号", description = "绑定三方账号")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@PostMapping("/social/{source}")
public R<Void> bindSocial(@PathVariable String source, @RequestBody AuthCallback callback) {
public void bindSocial(@PathVariable String source, @RequestBody AuthCallback callback) {
AuthRequest authRequest = authRequestFactory.get(source);
AuthResponse<AuthUser> response = authRequest.login(callback);
ValidationUtils.throwIf(!response.ok(), response.getMsg());
AuthUser authUser = response.getData();
userSocialService.bind(authUser, LoginHelper.getUserId());
return R.ok("绑定成功");
}
@Operation(summary = "解绑三方账号", description = "解绑三方账号")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@DeleteMapping("/social/{source}")
public R<Void> unbindSocial(@PathVariable String source) {
public void unbindSocial(@PathVariable String source) {
userSocialService.deleteBySourceAndUserId(source, LoginHelper.getUserId());
return R.ok("解绑成功");
}
}

View File

@@ -43,8 +43,8 @@ import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.web.model.R;
import java.io.IOException;
@@ -64,7 +64,7 @@ public class UserController extends BaseController<UserService, UserResp, UserDe
private final UserService userService;
@Override
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody UserReq req) {
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody UserReq req) {
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
ValidationUtils.throwIf(!ReUtil
@@ -83,38 +83,36 @@ public class UserController extends BaseController<UserService, UserResp, UserDe
@Operation(summary = "解析用户导入数据", description = "解析用户导入数据")
@SaCheckPermission("system:user:import")
@PostMapping(value = "parseImportUser")
public R<UserImportParseResp> parseImportUser(@NotNull(message = "文件不能为空") MultipartFile file) {
public UserImportParseResp parseImportUser(@NotNull(message = "文件不能为空") MultipartFile file) {
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
return R.ok(userService.parseImportUser(file));
return userService.parseImportUser(file);
}
@Operation(summary = "导入用户", description = "导入用户")
@SaCheckPermission("system:user:import")
@PostMapping(value = "import")
public R<UserImportResp> importUser(@Validated @RequestBody UserImportReq req) {
return R.ok(userService.importUser(req));
public UserImportResp importUser(@Validated @RequestBody UserImportReq req) {
return userService.importUser(req);
}
@Operation(summary = "重置密码", description = "重置用户登录密码")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:user:resetPwd")
@PatchMapping("/{id}/password")
public R<Void> resetPassword(@Validated @RequestBody UserPasswordResetReq req, @PathVariable Long id) {
public void resetPassword(@Validated @RequestBody UserPasswordResetReq req, @PathVariable Long id) {
String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getNewPassword()));
ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawNewPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
req.setNewPassword(rawNewPassword);
baseService.resetPassword(req, id);
return R.ok("重置密码成功");
}
@Operation(summary = "分配角色", description = "为用户新增或移除角色")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:user:updateRole")
@PatchMapping("/{id}/role")
public R<Void> updateRole(@Validated @RequestBody UserRoleUpdateReq updateReq, @PathVariable Long id) {
public void updateRole(@Validated @RequestBody UserRoleUpdateReq updateReq, @PathVariable Long id) {
baseService.updateRole(updateReq, id);
return R.ok("分配成功");
}
}

View File

@@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
@@ -33,9 +32,10 @@ import top.continew.admin.generator.model.req.GenConfigReq;
import top.continew.admin.generator.model.resp.GeneratePreviewResp;
import top.continew.admin.generator.model.resp.TableResp;
import top.continew.admin.generator.service.GeneratorService;
import top.continew.admin.system.service.DictService;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
import java.sql.SQLException;
import java.util.List;
@@ -54,12 +54,21 @@ import java.util.List;
public class GeneratorController {
private final GeneratorService baseService;
private final DictService dictService;
@Operation(summary = "分页查询数据表", description = "分页查询数据表")
@SaCheckPermission("tool:generator:list")
@GetMapping("/table")
public R<PageResp<TableResp>> pageTable(TableQuery query, @Validated PageQuery pageQuery) throws SQLException {
return R.ok(baseService.pageTable(query, pageQuery));
public PageResp<TableResp> pageTable(TableQuery query, @Validated PageQuery pageQuery) throws SQLException {
return baseService.pageTable(query, pageQuery);
}
@Operation(summary = "查询生成配置信息", description = "查询生成配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@GetMapping("/config/{tableName}")
public GenConfigDO getGenConfig(@PathVariable String tableName) throws SQLException {
return baseService.getGenConfig(tableName);
}
@Operation(summary = "查询字段配置列表", description = "查询字段配置列表")
@@ -67,43 +76,41 @@ public class GeneratorController {
@Parameter(name = "requireSync", description = "是否需要同步", example = "false", in = ParameterIn.QUERY)
@SaCheckPermission("tool:generator:list")
@GetMapping("/field/{tableName}")
public R<List<FieldConfigDO>> listFieldConfig(@PathVariable String tableName,
@RequestParam(required = false, defaultValue = "false") Boolean requireSync) {
return R.ok(baseService.listFieldConfig(tableName, requireSync));
}
@Operation(summary = "查询生成配置信息", description = "查询生成配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@GetMapping("/config/{tableName}")
public R<GenConfigDO> getGenConfig(@PathVariable String tableName) throws SQLException {
return R.ok(baseService.getGenConfig(tableName));
public List<FieldConfigDO> listFieldConfig(@PathVariable String tableName,
@RequestParam(required = false, defaultValue = "false") Boolean requireSync) {
return baseService.listFieldConfig(tableName, requireSync);
}
@Operation(summary = "保存配置信息", description = "保存配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@PostMapping("/config/{tableName}")
public R<Void> saveConfig(@Validated @RequestBody GenConfigReq req, @PathVariable String tableName) {
public void saveConfig(@Validated @RequestBody GenConfigReq req, @PathVariable String tableName) {
baseService.saveConfig(req, tableName);
return R.ok("保存成功");
}
@Operation(summary = "生成预览", description = "预览生成代码")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@GetMapping("/preview/{tableName}")
public R<List<GeneratePreviewResp>> preview(@PathVariable String tableName) {
return R.ok(baseService.preview(tableName));
public List<GeneratePreviewResp> preview(@PathVariable String tableName) {
return baseService.preview(tableName);
}
@Operation(summary = "生成代码", description = "生成代码")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@PostMapping("/{tableNames}")
public void generate(@PathVariable List<String> tableNames,
HttpServletRequest request,
HttpServletResponse response) {
baseService.generate(tableNames, request, response);
public void generate(@PathVariable List<String> tableNames, HttpServletResponse response) {
baseService.generate(tableNames, response);
}
@Operation(summary = "查询字典", description = "查询字典列表")
@SaCheckPermission("tool:generator:list")
@GetMapping("/dict")
public List<LabelValueResp> listDict() {
List<LabelValueResp> dictList = dictService.listDict(null, null);
dictList.addAll(dictService.listEnumDict());
return dictList;
}
}

View File

@@ -5,5 +5,5 @@
\____|\___/ |_| |_| \__||_||_| \_| \___| \_/\_/ /_/ \_\\__,_||_| |_| |_||_||_| |_|
:: ${project.name} :: v${project.version}
:: ContiNew Starter :: v2.4.0
:: ContiNew Starter :: v2.6.0
:: Spring Boot :: v${spring-boot.version}

View File

@@ -22,7 +22,7 @@ spring.datasource:
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
@@ -161,7 +161,8 @@ continew-starter.log:
## 项目日志配置(配置重叠部分,优先级高于 logback-spring.xml 中的配置)
logging:
level:
top.continew: DEBUG
top.continew.admin: DEBUG
top.continew.starter: DEBUG
file:
path: ./logs

View File

@@ -9,12 +9,6 @@ generator:
## 类型映射
typeMappings:
MYSQL:
Integer:
- int
- tinyint
- smallint
- mediumint
- integer
String:
- varchar
- char
@@ -23,21 +17,27 @@ generator:
- longtext
- tinytext
- json
LocalDate:
- date
LocalDateTime:
- datetime
- timestamp
Integer:
- int
- tinyint
- smallint
- mediumint
- integer
Long:
- bigint
Float:
- float
Double:
- double
BigDecimal:
- decimal
Boolean:
- bit
BigDecimal:
- decimal
LocalDate:
- date
LocalDateTime:
- datetime
- timestamp
## 模板配置
templateConfigs:
DO:

Some files were not shown because too many files have changed in this diff Show More