
AI集成
package top.yxqz.springboot.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ChatClient 配置类
*
* @author 余小小
*/
@Configuration
public class ChatClientConfig {
public static final Integer MAX_MEMORY_MESSAGE_SIZE = 30;
/**
* 配置 ChatMemory - 内存存储的会话记忆
*
* @return ChatMemory 内存存储的会话记忆实例
*/
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(MAX_MEMORY_MESSAGE_SIZE) // 窗口最大消息数目,保留最近30条消息
.build();
}
/**
* 配置 ChatClient - OpenAI兼容的聊天客户端
*
* Spring AI 会自动扫描带有 @Tool 注解的方法,无需手动注册
*
* @param openAiChatModel OpenAI Chat模型(由Spring AI自动配置)
* @param chatMemory 会话记忆存储
* @return ChatClient 实例
*/
@Bean("open-ai")
public ChatClient openAIChatClient(
OpenAiChatModel openAiChatModel,
ChatMemory chatMemory) {
return ChatClient.builder(openAiChatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory()).build())
.build();
}
}定时文件清理
package top.yxqz.springboot.config;
import lombok.extern.slf4j.Slf4j;
import top.yxqz.springboot.service.FileService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
/**
* 文件清理定时任务
* @author 余小小
*/
@Slf4j
@Component
public class FileCleanupScheduler {
@Resource
private FileService sysFileInfoService;
/**
* 清理过期临时文件
* 每天凌晨3点执行
*/
@Scheduled(cron = "0 0 3 * * ?")
public void cleanupExpiredTempFiles() {
try {
log.info("开始执行定时清理过期临时文件任务");
int cleanupCount = sysFileInfoService.cleanupExpiredTempFiles();
log.info("定时清理过期临时文件任务完成,清理数量: {}", cleanupCount);
} catch (Exception e) {
log.error("定时清理过期临时文件任务执行失败", e);
}
}
/**
* 文件存储监控
* 每天上午8点执行
*/
@Scheduled(cron = "0 0 8 * * ?")
public void monitorFileStorage() {
try {
log.info("开始执行文件存储监控任务");
// TODO: 实现文件存储监控逻辑
// 1. 统计文件总数
// 2. 统计存储空间使用情况
// 3. 检查孤立文件
// 4. 生成监控报告
log.info("文件存储监控任务完成");
} catch (Exception e) {
log.error("文件存储监控任务执行失败", e);
}
}
} JWT(后期引入)
package top.yxqz.springboot.config;
import com.auth0.jwt.exceptions.JWTVerificationException;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import top.yxqz.springboot.dto.response.UserDetailResponseDTO;
import top.yxqz.springboot.enums.UserStatus;
import top.yxqz.springboot.service.UserService;
import top.yxqz.springboot.util.JwtTokenUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* JWT认证过滤器
* 1. 继承OncePerRequestFilter确保每个请求只执行一次
* 2. 集成Spring Security认证机制
* 3. 统一的token验证和用户上下文设置
* 4. 完善的异常处理和日志记录
* 5. 标准的用户认证系统,支持角色权限
*
* @author 余小小
* @date 2025-01-13
*/
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Resource
private UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String requestUri = request.getRequestURI();
String method = request.getMethod();
log.debug("JWT认证过滤器处理请求:{} {}", method, requestUri);
try {
// 1. 提取JWT token
String token = extractToken(request);
if (StringUtils.hasText(token)) {
log.debug("成功提取token,长度:{},前20字符:{}",
token.length(),
token.length() > 20 ? token.substring(0, 20) + "..." : token);
// 2. 验证token并获取用户ID
Long userId = getUserIdFromToken(token);
if (userId != null) {
// 检查token是否过期
if (JwtTokenUtils.isTokenExpired(token)) {
log.warn("JWT token已过期,用户ID:{}", userId);
SecurityContextHolder.clearContext();
filterChain.doFilter(request, response);
return;
}
// 3. 查询用户信息(验证用户是否仍然存在和有效)
UserDetailResponseDTO user = userService.getUserById(userId);
if (user != null && user.getStatus().equals(UserStatus.NORMAL.getCode())) {
// 4. 创建Spring Security认证对象
List<SimpleGrantedAuthority> authorities = Collections.singletonList(
new SimpleGrantedAuthority("ROLE_" + user.getUserType())
);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user.getUsername(),
null,
authorities
);
// 5. 设置认证信息到Spring Security上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 6. 设置用户信息到请求属性(方便Controller使用)
request.setAttribute("currentUser", user);
request.setAttribute("currentUserId", userId);
log.debug("JWT认证成功,用户ID:{},用户名:{},用户类型:{}",
userId, user.getUsername(), user.getUserType());
} else {
log.warn("JWT验证失败:用户不存在或已被禁用,用户ID:{}", userId);
// 清理认证上下文,防止安全问题
SecurityContextHolder.clearContext();
}
} else {
log.warn("JWT验证失败:无法从token中解析用户ID");
// 清理认证上下文
SecurityContextHolder.clearContext();
}
} else {
log.debug("未找到token,跳过JWT认证");
}
} catch (JWTVerificationException e) {
log.warn("JWT验证失败:{},清理认证上下文", e.getMessage());
// JWT验证失败时清理认证上下文
SecurityContextHolder.clearContext();
} catch (Exception e) {
log.error("JWT认证过程中发生异常,请求:{} {},异常:{},清理认证上下文", method, requestUri, e.getMessage(), e);
// 发生异常时清理认证上下文,确保安全
SecurityContextHolder.clearContext();
}
// 继续过滤器链
filterChain.doFilter(request, response);
}
/**
* 从请求中提取JWT token
* 只支持标准的 Authorization: Bearer <token> 方式
*
* @param request HTTP请求对象
* @return JWT token字符串,如果没有找到则返回null
*/
private String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (org.springframework.util.StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
/**
* 从token中获取用户ID
* @param token JWT token
* @return 用户ID
*/
private Long getUserIdFromToken(String token) {
try {
return JwtTokenUtils.verifyToken(token).getClaim("userId").asLong();
} catch (Exception e) {
log.error("从token获取用户ID失败", e);
return null;
}
}
} Jwt配置(后期引入)
package top.yxqz.springboot.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* JWT配置类 - 管理JWT相关的配置属性
*
* 配置项:
* - jwt.secret: JWT签名密钥
* - jwt.expiration: JWT过期时间(毫秒)
* - jwt.refresh-expiration: JWT刷新token过期时间(毫秒)
*
* 使用方式:
* 在application.properties中配置相应的属性值
*/
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
/**
* JWT签名密钥
* 默认值:应在配置文件中设置复杂的密钥
*/
private String secret = "defaultSecretKey";
/**
* JWT过期时间(毫秒)
* 默认值:24小时 = 24 * 60 * 60 * 1000 = 86400000
*/
private Long expiration = 86400000L;
/**
* JWT刷新token过期时间(毫秒)
* 默认值:7天 = 7 * 24 * 60 * 60 * 1000 = 604800000
*/
private Long refreshExpiration = 604800000L;
/**
* token头部名称
* 默认值:Authorization
*/
private String header = "Authorization";
/**
* token前缀
* 默认值:Bearer
*/
private String tokenPrefix = "Bearer ";
// Getter and Setter methods
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public Long getExpiration() {
return expiration;
}
public void setExpiration(Long expiration) {
this.expiration = expiration;
}
public Long getRefreshExpiration() {
return refreshExpiration;
}
public void setRefreshExpiration(Long refreshExpiration) {
this.refreshExpiration = refreshExpiration;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getTokenPrefix() {
return tokenPrefix;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
@Override
public String toString() {
return "JwtConfig{" +
"secret='***'" + // 不输出真实密钥
", expiration=" + expiration +
", refreshExpiration=" + refreshExpiration +
", header='" + header + '\'' +
", tokenPrefix='" + tokenPrefix + '\'' +
'}';
}
} 安全框架配置(后期引入)
package top.yxqz.springboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Spring Security 企业级配置类
*
* 🎯 核心职责:
* 1. 统一认证授权管理 - Spring Security统一处理所有安全相关功能
* 2. JWT过滤器集成 - 自定义JWT认证过滤器集成到Spring Security过滤器链
* 3. 无状态会话管理 - 适合微服务和分布式架构
* 4. 方法级安全支持 - 支持@PreAuthorize等注解
* 5. 一处配置全局生效 - 消除重复配置,统一维护
*
* 🚀 架构优化:
* - 解决循环依赖问题:通过延迟注入和职责分离
* - 提高代码可维护性:清晰的依赖关系
* - 符合Spring最佳实践:避免复杂的Bean依赖关系
*
* @author 余小小
* @date 2025-01-27
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 启用方法级安全,支持@PreAuthorize等注解
public class SecurityConfig {
/**
* 定义公开访问路径常量
* 这些路径不需要JWT验证,可以直接访问
*/
private static final String[] PUBLIC_PATHS = {
// 系统基础路径
"/",
"/health",
"/favicon.ico",
// API文档相关
"/doc.html",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-resources/**",
// 认证相关接口(必须公开)
"/api/user/auth", // 匿名用户认证(注册/登录)
"/api/user/login", // 用户登录
"/api/user/register", // 用户注册
"/api/user/forget", // 忘记密码
"/api/user/add", // 用户添加
"/api/file/**", // 临时公开
"/api/**",
// 公开信息接口
"/api/user/{id}", // 用户信息查询(公开)
// 静态资源(与实际目录结构一致)
"/static/**", // 项目静态资源统一路径
"/files/**", // 文件上传目录访问
"/*.html", // 根路径下的HTML文件
"/file-test.html" // 文件测试页面
};
/**
* 密码编码器Bean
*
* 🎯 职责分离:
* - 独立定义,避免与其他Bean形成循环依赖
* - 使用BCrypt加密算法,安全性高
* - 全局共享,其他服务可以直接注入使用
*
* @return PasswordEncoder BCrypt密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* JWT认证过滤器Bean
*
* 🎯 解决循环依赖:
* - 通过@Bean方式创建,而不是@Resource注入
* - Spring容器会自动处理依赖关系
* - 避免SecurityConfig直接依赖JwtAuthenticationFilter
*
* @return JwtAuthenticationFilter JWT认证过滤器实例
*/
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
/**
* 配置Spring Security过滤器链
*
* 核心功能:
* 1. 禁用CSRF - 适合API服务
* 2. 无状态会话 - 适合JWT认证
* 3. 路径权限配置 - 公开路径vs受保护路径
* 4. JWT过滤器集成 - 自定义认证逻辑
*
* 安全策略:
* - 默认所有请求需要认证
* - 公开路径允许匿名访问
* - JWT过滤器在用户名密码认证之前执行
*
* @param http HttpSecurity配置对象
* @return SecurityFilterChain 安全过滤器链
* @throws Exception 配置异常
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用CSRF保护(API服务通常不需要)
.csrf(csrf -> csrf.disable())
// 配置会话管理为无状态(适合JWT)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 配置请求授权规则
.authorizeHttpRequests(auth -> auth
// 公开路径,允许匿名访问
.requestMatchers(PUBLIC_PATHS).permitAll()
// 其他所有请求都需要认证
.anyRequest().authenticated()
)
// 添加JWT认证过滤器
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}K4J接口配置
package top.yxqz.kindergraten.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Knife4j API文档配置类
* 用于配置Knife4j的API文档展示及相关资源访问
* Knife4j是一个基于Swagger的API文档增强工具
*/
@Configuration
public class Knife4jConfig {
/**
* 配置OpenAPI对象
* 用于生成API文档的核心配置
* 包含API文档的基本信息、安全配置等
*
* @return OpenAPI 返回配置好的OpenAPI对象
*/
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
// 配置接口文档基本信息
.info(this.getApiInfo());
}
/**
* 获取API文档的基本信息配置
* 配置API文档的标题、描述、版本等元数据信息
* 支持以下配置项:
* - 文档标题
* - 文档描述
* - 作者信息(当前已注释)
* - 许可证信息(当前已注释)
* - 服务条款(当前已注释)
* - 版本信息
*
* @return Info 返回API文档基本信息对象
*/
private Info getApiInfo() {
return new Info()
// 配置文档标题
.title("智能幼儿园系统")
// 配置文档描述
.description("wwww.yxqz.top")
// 配置作者信息
.contact(new Contact().name("余小小").url("https://wwww.yxqz.top").email("yxqz@qq.com"))
// 配置License许可证信息
// .license(new License().name("Apache 2.0").url("https://www.baidu.cn"))
// 概述信息
.summary("智能幼儿园系统(创新班)")
.termsOfService("https://www.yxqz.top")
// 配置版本号
.version("1.0");
}
}日期格式化配置
package top.yxqz.springboot.config;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.format.DateTimeFormatter;
@Configuration
public class LocalDateTimeConfig {
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
// 序列化配置
builder.serializers(new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
// 反序列化配置
builder.deserializers(new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
};
}
} MP配置
package top.yxqz.springboot.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
@Configuration
@Slf4j
//@MapperScan("com.jx.agriculturalsys.controller")
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/**
* 自动填充处理器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MetaObjectHandler() {
@Override
public void insertFill(MetaObject metaObject) {
log.debug("开始插入填充...");
// 创建时间和更新时间(通用字段)
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// User实体的时间字段
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.debug("开始更新填充...");
// 更新时间(通用字段)
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// User实体的更新时间字段
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
}
};
}
}redis缓存配置
package top.yxqz.springboot.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置类
*
* 核心功能:
* 1. 配置RedisTemplate使用JSON序列化器
* 2. 解决默认JDK序列化器的兼容性问题
* 3. 提高缓存数据的可读性和跨语言兼容性
*
* @author 余小小
* @date 2025-01-27
*/
@Configuration
public class RedisConfig {
/**
* 配置RedisTemplate
*
* 序列化策略:
* - Key: String序列化器(简单直接)
* - Value: Jackson JSON序列化器(支持复杂对象)
* - HashKey: String序列化器
* - HashValue: Jackson JSON序列化器
*
* @param connectionFactory Redis连接工厂
* @return 配置好的RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 配置ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 创建Jackson序列化器(使用新的构造方法)
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
// 创建String序列化器
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 设置key和value的序列化器
template.setKeySerializer(stringRedisSerializer); // key使用String序列化
template.setValueSerializer(jackson2JsonRedisSerializer); // value使用JSON序列化
// 设置hash key和value的序列化器
template.setHashKeySerializer(stringRedisSerializer); // hash key使用String序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer); // hash value使用JSON序列化
// 初始化RedisTemplate
template.afterPropertiesSet();
return template;
}
} Web路由配置
package top.yxqz.springboot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类 - 企业级统一配置
*
* 🎯 统一职责:
* 1. API前缀配置
* 2. 静态资源映射配置
* 3. API文档资源配置
*
* 🚀 架构优势:
* - 统一管理所有Web相关配置
* - 避免多个配置类冲突
* - 职责清晰,易于维护
* - 符合单一配置原则
*
* @author 余小小
* @date 2025-01-27
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 配置API路径前缀
*
* 为所有带有@RestController注解的控制器类自动添加"/api"前缀
* 这样可以将API接口与其他Web资源(如静态资源、文档)区分开
*
* 工作原理:
* - UserController: /user/* → /api/user/*
* - FileController: /file/* → /api/file/*
* - EmailController: /email/* → /api/email/*
*
* 排除规则:
* - Swagger/Knife4j相关接口不添加前缀
* - 通过包名判断排除springfox、swagger、doc相关包
*
* @param configurer 路径匹配配置器
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", clazz ->
clazz.isAnnotationPresent(RestController.class) &&
!clazz.getPackage().getName().contains("springfox") &&
!clazz.getPackage().getName().contains("swagger") &&
!clazz.getPackage().getName().contains("doc")
);
}
/**
* 统一配置静态资源映射
*
* 🎯 配置目标:
* 1. 静态资源访问路径
* 2. 文件上传目录访问
* 3. API文档相关资源
* 4. 避免与API路径冲突
*
* 📁 资源映射规则:
* - /static/** → classpath:/static/ (项目静态资源)
* - /files/** → file:./files/ (文件上传目录)
* - /doc.html → Knife4j文档首页
* - /webjars/** → Maven webjars资源
* - /swagger-ui/** → Swagger UI资源
* - /v3/api-docs/** → OpenAPI文档
*
* @param registry 资源处理器注册表
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 1. 静态资源配置 - 项目自定义静态文件
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600); // 缓存1小时
// 2. 文件上传目录配置 - 用户上传文件访问
registry.addResourceHandler("/files/**")
.addResourceLocations("file:./files/")
.setCachePeriod(86400); // 缓存24小时
// 2. API文档资源配置 - Knife4j/Swagger相关
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
registry.addResourceHandler("/v3/api-docs/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}