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 调整
This commit is contained in:
2024-08-07 20:27:27 +08:00
parent c7ffc67cdc
commit d7621c6b26
30 changed files with 331 additions and 255 deletions

View File

@@ -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.5.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" />
@@ -211,12 +211,12 @@ 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://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.5.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 权限认证框架,让鉴权变得简单、优雅。 |

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.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.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), e.getMessage());
}
/**
* 拦截文件上传异常-超过上传大小限制
*/
@ExceptionHandler(MultipartException.class)
public R handleRequestTooBigException(MultipartException e, HttpServletRequest request) {
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);
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
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.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.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权");
}
/**
* 认证异常-角色认证
*/
@ExceptionHandler(NotRoleException.class)
public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权");
}
}

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

@@ -27,7 +27,6 @@ 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;
@@ -281,7 +280,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 +295,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("代码生成失败,请手动清理生成文件");

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;
@@ -77,6 +79,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;
@@ -129,13 +132,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);

View File

@@ -25,10 +25,10 @@
</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>
<!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) -->

View File

@@ -35,7 +35,7 @@ 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;
/**
* 启动程序
@@ -44,13 +44,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;

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

@@ -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,7 +182,7 @@ 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,
public R getSmsCaptcha(@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") String phone,
CaptchaVO captchaReq) {
// 行为验证码校验
ResponseModel verificationRes = behaviorCaptchaService.verification(captchaReq);

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,49 @@ public class CommonController {
@Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/file")
public R<FileUploadResp> upload(@NotNull(message = "文件不能为空") MultipartFile file) {
public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file) {
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;
@@ -35,7 +34,6 @@ import top.continew.admin.generator.model.resp.TableResp;
import top.continew.admin.generator.service.GeneratorService;
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;
import java.sql.SQLException;
import java.util.List;
@@ -58,8 +56,8 @@ public class GeneratorController {
@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 = "查询字段配置列表")
@@ -67,43 +65,40 @@ 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,
public List<FieldConfigDO> listFieldConfig(@PathVariable String tableName,
@RequestParam(required = false, defaultValue = "false") Boolean requireSync) {
return R.ok(baseService.listFieldConfig(tableName, 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")
@GetMapping("/config/{tableName}")
public R<GenConfigDO> getGenConfig(@PathVariable String tableName) throws SQLException {
return R.ok(baseService.getGenConfig(tableName));
public GenConfigDO getGenConfig(@PathVariable String tableName) throws SQLException {
return baseService.getGenConfig(tableName);
}
@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);
}
}

View File

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

View File

@@ -48,6 +48,20 @@ continew-starter.web:
pattern: '[$spanId][$traceId]'
mdc-enable: false
--- ### 全局响应配置
continew-starter.web:
response:
# 是否开启国际化默认false
i18n: false
# 自定义成功响应码默认0
default-success-code: 0
# 自定义成功提示默认ok
default-success-msg: ok
# 自定义失败响应码默认1
default-error-code: 1
# 自定义失败提示默认error
default-error-msg: error
--- ### 接口文档配置
springdoc:
# 设置对象型参数的展示形式(设为 true 表示将对象型参数平展开,即对象内的属性直接作为参数展示而不是嵌套在对象内,默认 false

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter</artifactId>
<version>2.4.0</version>
<version>2.5.0</version>
</parent>
<groupId>top.continew</groupId>