refactor: 升级 SpringBoot 3.5.0 版本和代码结构优化

This commit is contained in:
Ray.Hao
2025-06-11 20:47:02 +08:00
parent 4c492784f3
commit af8b5c847d
27 changed files with 246 additions and 80 deletions

View File

@@ -3,7 +3,7 @@
<img alt="logo" width="100" height="100" src="https://foruda.gitee.com/images/1733417239320800627/3c5290fe_716974.png">
<h2>youlai-boot</h2>
<img alt="有来技术" src="https://img.shields.io/badge/Java -17-brightgreen.svg"/>
<img alt="有来技术" src="https://img.shields.io/badge/SpringBoot-3.3.6-green.svg"/>
<img alt="有来技术" src="https://img.shields.io/badge/SpringBoot-3.5.0-green.svg"/>
<a href="https://gitee.com/youlaiorg/youlai-boot" target="_blank">
<img alt="有来技术" src="https://gitee.com/youlaiorg/youlai-boot/badge/star.svg"/>
</a>
@@ -70,6 +70,7 @@ youlai-boot
├── sql # SQL脚本
│ ├── mysql # MySQL 脚本
├── src # 源码目录
│ ├── auth # 登录认证
│ ├── common # 公共模块
│ │ ├── annotation # 注解定义
│ │ ├── base # 基础类
@@ -86,12 +87,14 @@ youlai-boot
│ │ ├── filter # 过滤器(请求日志、限流)
│ │ ├── handler # 处理器(数据权限、数据填充)
│ │ └── security # Spring Security 安全模块
│ ├── modules # 业务模块
│ ├── module # 业务模块
│ │ ├── member # 会员模块【业务模块演示】
│ │ ├── order # 订单模块【业务模块演示】
│ │ ├── product # 商品模块【业务模块演示】
│ ├── module # 插件扩展
│ │ ├── knife4j # Knife4j 扩展
│ │ ├── mybatis # Mybatis 扩展
│ ├── shared # 共享模块
│ │ ├── auth # 认证模块
│ │ ├── file # 文件模块
│ │ ├── codegen # 代码生成模块
│ │ ├── mail # 邮件模块

13
pom.xml
View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.6</version> <!-- lookup parent from repository -->
<version>3.5.0</version> <!-- lookup parent from repository -->
<relativePath/>
</parent>
@@ -152,6 +152,17 @@
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.9</version>
</dependency>
<!-- MapStruct 对象映射 -->

View File

@@ -1,11 +1,11 @@
package com.youlai.boot.shared.auth.controller;
package com.youlai.boot.auth.controller;
import com.youlai.boot.auth.model.CaptchaInfo;
import com.youlai.boot.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.shared.auth.service.AuthService;
import com.youlai.boot.shared.auth.model.CaptchaInfo;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.core.security.model.AuthenticationToken;
import com.youlai.boot.common.annotation.Log;
import io.swagger.v3.oas.annotations.Operation;
@@ -32,7 +32,7 @@ public class AuthController {
private final AuthService authService;
@Operation(summary = "获取登录验证码")
@Operation(summary = "获取验证码")
@GetMapping("/captcha")
public Result<CaptchaInfo> getCaptcha() {
CaptchaInfo captcha = authService.getCaptcha();
@@ -50,42 +50,6 @@ public class AuthController {
return Result.success(authenticationToken);
}
@Operation(summary = "注销登录")
@DeleteMapping("/logout")
@Log(value = "注销", module = LogModuleEnum.LOGIN)
public Result<?> logout() {
authService.logout();
return Result.success();
}
@Operation(summary = "刷新访问令牌")
@PostMapping("/refresh-token")
public Result<?> refreshToken(
@Parameter(description = "刷新令牌", example = "xxx.xxx.xxx") @RequestParam String refreshToken
) {
AuthenticationToken authenticationToken = authService.refreshToken(refreshToken);
return Result.success(authenticationToken);
}
@Operation(summary = "微信授权登录")
@PostMapping("/login/wechat")
@Log(value = "微信登录", module = LogModuleEnum.LOGIN)
public Result<AuthenticationToken> loginByWechat(
@Parameter(description = "微信授权码", example = "code") @RequestParam String code
) {
AuthenticationToken loginResult = authService.loginByWechat(code);
return Result.success(loginResult);
}
@Operation(summary = "发送登录短信验证码")
@PostMapping("/login/sms/code")
public Result<Void> sendLoginVerifyCode(
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile
) {
authService.sendSmsLoginCode(mobile);
return Result.success();
}
@Operation(summary = "短信验证码登录")
@PostMapping("/login/sms")
@Log(value = "短信验证码登录", module = LogModuleEnum.LOGIN)
@@ -97,17 +61,55 @@ public class AuthController {
return Result.success(loginResult);
}
@Operation(summary = "微信小程序Code登录")
@Operation(summary = "发送登录短信验证码")
@PostMapping("/sms/code")
public Result<Void> sendLoginVerifyCode(
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile
) {
authService.sendSmsLoginCode(mobile);
return Result.success();
}
@Operation(summary = "微信授权登录(Web)")
@PostMapping("/login/wechat")
@Log(value = "微信登录", module = LogModuleEnum.LOGIN)
public Result<AuthenticationToken> loginByWechat(
@Parameter(description = "微信授权码", example = "code") @RequestParam String code
) {
AuthenticationToken loginResult = authService.loginByWechat(code);
return Result.success(loginResult);
}
@Operation(summary = "微信小程序登录(Code)")
@PostMapping("/wx/miniapp/code-login")
public Result<AuthenticationToken> loginByWxMiniAppCode(@RequestBody @Valid WxMiniAppCodeLoginDTO loginDTO) {
AuthenticationToken token = authService.loginByWxMiniAppCode(loginDTO);
return Result.success(token);
}
@Operation(summary = "微信小程序手机号登录")
@Operation(summary = "微信小程序登录(手机号)")
@PostMapping("/wx/miniapp/phone-login")
public Result<AuthenticationToken> loginByWxMiniAppPhone(@RequestBody @Valid WxMiniAppPhoneLoginDTO loginDTO) {
AuthenticationToken token = authService.loginByWxMiniAppPhone(loginDTO);
return Result.success(token);
}
@Operation(summary = "退出登录")
@DeleteMapping("/logout")
@Log(value = "退出登录", module = LogModuleEnum.LOGIN)
public Result<?> logout() {
authService.logout();
return Result.success();
}
@Operation(summary = "刷新令牌")
@PostMapping("/refresh-token")
public Result<?> refreshToken(
@Parameter(description = "刷新令牌", example = "xxx.xxx.xxx") @RequestParam String refreshToken
) {
AuthenticationToken authenticationToken = authService.refreshToken(refreshToken);
return Result.success(authenticationToken);
}
}

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.shared.auth.enums;
package com.youlai.boot.auth.enums;
/**
* EasyCaptcha 验证码类型枚举

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.shared.auth.model;
package com.youlai.boot.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.shared.auth.model.dto;
package com.youlai.boot.auth.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.shared.auth.model.dto;
package com.youlai.boot.auth.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,9 +1,9 @@
package com.youlai.boot.shared.auth.service;
package com.youlai.boot.auth.service;
import com.youlai.boot.shared.auth.model.CaptchaInfo;
import com.youlai.boot.auth.model.CaptchaInfo;
import com.youlai.boot.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.core.security.model.AuthenticationToken;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.auth.model.dto.WxMiniAppCodeLoginDTO;
/**
* 认证服务接口

View File

@@ -1,10 +1,13 @@
package com.youlai.boot.shared.auth.service.impl;
package com.youlai.boot.auth.service.impl;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.auth.enums.CaptchaTypeEnum;
import com.youlai.boot.auth.model.CaptchaInfo;
import com.youlai.boot.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.exception.BusinessException;
@@ -12,12 +15,9 @@ import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.config.property.CaptchaProperties;
import com.youlai.boot.core.security.extension.sms.SmsAuthenticationToken;
import com.youlai.boot.core.security.util.SecurityUtils;
import com.youlai.boot.shared.auth.enums.CaptchaTypeEnum;
import com.youlai.boot.core.security.model.AuthenticationToken;
import com.youlai.boot.shared.auth.model.CaptchaInfo;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.shared.auth.service.AuthService;
import com.youlai.boot.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.core.security.token.TokenManager;
import com.youlai.boot.shared.sms.enums.SmsTypeEnum;
import com.youlai.boot.shared.sms.service.SmsService;

View File

@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.youlai.boot.core.handler.MyDataPermissionHandler;
import com.youlai.boot.core.handler.MyMetaObjectHandler;
import com.youlai.boot.pulgin.mybatis.MyDataPermissionHandler;
import com.youlai.boot.pulgin.mybatis.MyMetaObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.member.controller;
package com.youlai.boot.module.member.controller;
/**
* 会员控制层-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.member.mapper;
package com.youlai.boot.module.member.mapper;
/**
* 会员数据访问层-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.member.model;
package com.youlai.boot.module.member.model;
/**
* 会员实体-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.member.service;
package com.youlai.boot.module.member.service;
/**
* 会员管理服务类-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.order.controller;
package com.youlai.boot.module.order.controller;
/**
* 订单控制层-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.order.mapper;
package com.youlai.boot.module.order.mapper;
/**
* 订单数据访问层-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.order.model;
package com.youlai.boot.module.order.model;
/**
* 订单实体-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.order.service;
package com.youlai.boot.module.order.service;
/**
* 订单管理服务类-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.product.controller;
package com.youlai.boot.module.product.controller;
/**
* 商品控制层-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.product.mapper;
package com.youlai.boot.module.product.mapper;
/**
* 商品数据访问层-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.product.model;
package com.youlai.boot.module.product.model;
/**
* 商品实体-业务模块演示

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.modules.product.service;
package com.youlai.boot.module.product.service;
/**
* 会员管理服务类-业务模块演示

View File

@@ -0,0 +1,150 @@
package com.youlai.boot.pulgin.knife4j;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.github.xiaoymin.knife4j.core.conf.ExtensionsConstants;
import com.github.xiaoymin.knife4j.core.conf.GlobalConstants;
import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties;
import com.github.xiaoymin.knife4j.spring.configuration.Knife4jSetting;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.models.OpenAPI;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;
/**
* 增强扩展属性支持
* @since 4.1.0
* @author <a href="xiaoymin@foxmail.com">xiaoymin@foxmail.com</a>
* 2022/12/11 22:40
*/
@Primary
@Configuration
@Slf4j
public class Knife4jOpenApiCustomizer extends com.github.xiaoymin.knife4j.spring.extension.Knife4jOpenApiCustomizer implements GlobalOpenApiCustomizer {
final Knife4jProperties knife4jProperties;
final SpringDocConfigProperties properties;
public Knife4jOpenApiCustomizer(Knife4jProperties knife4jProperties, SpringDocConfigProperties properties) {
super(knife4jProperties,properties);
this.knife4jProperties = knife4jProperties;
this.properties = properties;
}
@Override
public void customise(OpenAPI openApi) {
log.debug("Knife4j OpenApiCustomizer");
if (knife4jProperties.isEnable()) {
Knife4jSetting setting = knife4jProperties.getSetting();
OpenApiExtensionResolver openApiExtensionResolver = new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments());
// 解析初始化
openApiExtensionResolver.start();
Map<String, Object> objectMap = new HashMap<>();
objectMap.put(GlobalConstants.EXTENSION_OPEN_SETTING_NAME, setting);
objectMap.put(GlobalConstants.EXTENSION_OPEN_MARKDOWN_NAME, openApiExtensionResolver.getMarkdownFiles());
openApi.addExtension(GlobalConstants.EXTENSION_OPEN_API_NAME, objectMap);
addOrderExtension(openApi);
}
}
/**
* 往OpenAPI内tags字段添加x-order属性
*
* @param openApi openApi
*/
private void addOrderExtension(OpenAPI openApi) {
if (CollectionUtils.isEmpty(properties.getGroupConfigs())) {
return;
}
// 获取包扫描路径
Set<String> packagesToScan =
properties.getGroupConfigs().stream()
.map(SpringDocConfigProperties.GroupConfig::getPackagesToScan)
.filter(toScan -> !CollectionUtils.isEmpty(toScan))
.flatMap(List::stream)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(packagesToScan)) {
return;
}
// 扫描包下被ApiSupport注解的RestController Class
Set<Class<?>> classes =
packagesToScan.stream()
.map(packageToScan -> scanPackageByAnnotation(packageToScan, RestController.class))
.flatMap(Set::stream)
.filter(clazz -> clazz.isAnnotationPresent(ApiSupport.class))
.collect(Collectors.toSet());
if (!CollectionUtils.isEmpty(classes)) {
// ApiSupport oder值存入tagSortMap<Tag.name,ApiSupport.order>
Map<String, Integer> tagOrderMap = new HashMap<>();
classes.forEach(
clazz -> {
Tag tag = getTag(clazz);
if (Objects.nonNull(tag)) {
ApiSupport apiSupport = clazz.getAnnotation(ApiSupport.class);
tagOrderMap.putIfAbsent(tag.name(), apiSupport.order());
}
});
// 往openApi tags字段添加x-order增强属性
if (openApi.getTags() != null) {
openApi
.getTags()
.forEach(
tag -> {
if (tagOrderMap.containsKey(tag.getName())) {
tag.addExtension(
ExtensionsConstants.EXTENSION_ORDER, tagOrderMap.get(tag.getName()));
}
});
}
}
}
private Tag getTag(Class<?> clazz) {
// 从类上获取
Tag tag = clazz.getAnnotation(Tag.class);
if (Objects.isNull(tag)) {
// 从接口上获取
Class<?>[] interfaces = clazz.getInterfaces();
if (ArrayUtils.isNotEmpty(interfaces)) {
for (Class<?> interfaceClazz : interfaces) {
Tag anno = interfaceClazz.getAnnotation(Tag.class);
if (Objects.nonNull(anno)) {
tag = anno;
break;
}
}
}
}
return tag;
}
private Set<Class<?>> scanPackageByAnnotation(
String packageName, final Class<? extends Annotation> annotationClass) {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass));
Set<Class<?>> classes = new HashSet<>();
for (BeanDefinition beanDefinition : scanner.findCandidateComponents(packageName)) {
try {
Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
classes.add(clazz);
} catch (ClassNotFoundException ignore) {
}
}
return classes;
}
}

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.core.handler;
package com.youlai.boot.pulgin.mybatis;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.core.handler;
package com.youlai.boot.pulgin.mybatis;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;

View File

@@ -155,8 +155,8 @@ springdoc:
- group: '系统管理'
paths-to-match: "/**"
packages-to-scan:
- com.youlai.boot.auth.controller
- com.youlai.boot.system.controller
- com.youlai.boot.shared.auth.controller
- com.youlai.boot.shared.file.controller
- com.youlai.boot.shared.codegen.controller
default-flat-param-object: true

View File

@@ -152,8 +152,8 @@ springdoc:
- group: '系统管理'
paths-to-match: "/**"
packages-to-scan:
- com.youlai.boot.auth.controller
- com.youlai.boot.system.controller
- com.youlai.boot.shared.auth.controller
- com.youlai.boot.shared.file.controller
- com.youlai.boot.shared.codegen.controller
default-flat-param-object: true