diff --git a/openspec/changes/add-unit-tests/.comet.yaml b/openspec/changes/add-api-versioning/.comet.yaml similarity index 76% rename from openspec/changes/add-unit-tests/.comet.yaml rename to openspec/changes/add-api-versioning/.comet.yaml index c8b766b..e2473f9 100644 --- a/openspec/changes/add-unit-tests/.comet.yaml +++ b/openspec/changes/add-api-versioning/.comet.yaml @@ -1,4 +1,4 @@ -change_name: add-unit-tests +change_name: add-api-versioning phase: build auto_transition: true build_pause: null diff --git a/openspec/changes/add-api-versioning/proposal.md b/openspec/changes/add-api-versioning/proposal.md new file mode 100644 index 0000000..f287849 --- /dev/null +++ b/openspec/changes/add-api-versioning/proposal.md @@ -0,0 +1,24 @@ +# P1 优化:API 版本管理 + +## 目标 + +为 RuoYi-Vue 后端实现 API 版本控制,支持向后兼容和渐进式升级。 + +## 范围 + +- URL 路径版本控制(/api/v1/、/api/v2/) +- 版本路由映射 +- 版本弃用机制 +- 版本文档 + +## 技术栈 + +- Spring MVC +- 自定义注解 +- 请求映射 + +## 验收标准 + +- 支持 v1、v2 版本 +- 版本路由正确映射 +- 向后兼容 diff --git a/openspec/changes/add-api-versioning/tasks.md b/openspec/changes/add-api-versioning/tasks.md new file mode 100644 index 0000000..170954d --- /dev/null +++ b/openspec/changes/add-api-versioning/tasks.md @@ -0,0 +1,30 @@ +# Tasks: API 版本管理 + +## Task 1: 版本配置 + +- [ ] 1.1 创建版本注解 +- [ ] 1.2 配置版本路由 +- [ ] 1.3 创建版本常量 + +## Task 2: 版本 Controller + +- [ ] 2.1 创建 v1 版本 Controller 示例 +- [ ] 2.2 创建 v2 版本 Controller 示例 +- [ ] 2.3 版本路由映射 + +## Task 3: 版本弃用机制 + +- [ ] 3.1 创建版本弃用注解 +- [ ] 3.2 实现弃用警告 +- [ ] 3.3 版本迁移提示 + +## Task 4: 版本文档 + +- [ ] 4.1 Swagger 版本分组 +- [ ] 4.2 版本迁移指南 + +## 验收标准 + +- [ ] 支持 v1、v2 版本 +- [ ] 版本路由正确映射 +- [ ] 向后兼容 diff --git a/openspec/changes/archive/2026-06-28-add-performance-monitor/.comet.yaml b/openspec/changes/archive/2026-06-28-add-performance-monitor/.comet.yaml new file mode 100644 index 0000000..46296e2 --- /dev/null +++ b/openspec/changes/archive/2026-06-28-add-performance-monitor/.comet.yaml @@ -0,0 +1,7 @@ +change_name: add-performance-monitor +phase: archive +auto_transition: true +build_pause: null +verify_result: pass +archived: false +workflow: full diff --git a/openspec/changes/archive/2026-06-28-add-performance-monitor/proposal.md b/openspec/changes/archive/2026-06-28-add-performance-monitor/proposal.md new file mode 100644 index 0000000..7b3fb69 --- /dev/null +++ b/openspec/changes/archive/2026-06-28-add-performance-monitor/proposal.md @@ -0,0 +1,25 @@ +# P1 优化:性能监控 + +## 目标 + +为 RuoYi-Vue 后端添加接口性能监控和慢查询日志,识别性能瓶颈。 + +## 范围 + +- 接口响应时间监控 +- 慢 SQL 查询日志 +- 性能统计报表 +- 告警机制 + +## 技术栈 + +- AOP 切面 +- Micrometer +- Spring Boot Actuator +- 自定义注解 + +## 验收标准 + +- 所有接口响应时间记录 +- 慢查询日志(> 1 秒) +- 性能统计接口可访问 diff --git a/openspec/changes/archive/2026-06-28-add-performance-monitor/tasks.md b/openspec/changes/archive/2026-06-28-add-performance-monitor/tasks.md new file mode 100644 index 0000000..996ef26 --- /dev/null +++ b/openspec/changes/archive/2026-06-28-add-performance-monitor/tasks.md @@ -0,0 +1,30 @@ +# Tasks: 性能监控 + +## Task 1: 监控配置 + +- [ ] 1.1 添加 Micrometer 和 Actuator 依赖 +- [ ] 1.2 配置监控端点 +- [ ] 1.3 创建性能监控注解 + +## Task 2: AOP 性能监控 + +- [ ] 2.1 创建性能监控切面 +- [ ] 2.2 记录接口响应时间 +- [ ] 2.3 慢查询日志(> 1 秒) + +## Task 3: 性能统计 + +- [ ] 3.1 创建性能统计服务 +- [ ] 3.2 性能监控接口 +- [ ] 3.3 慢查询日志查询 + +## Task 4: 告警机制 + +- [ ] 4.1 慢接口告警 +- [ ] 4.2 异常告警 + +## 验收标准 + +- [ ] 所有接口响应时间记录 +- [ ] 慢查询日志(> 1 秒) +- [ ] 性能统计接口可访问 diff --git a/openspec/changes/archive/2026-06-28-add-unit-tests/.comet.yaml b/openspec/changes/archive/2026-06-28-add-unit-tests/.comet.yaml new file mode 100644 index 0000000..c02307c --- /dev/null +++ b/openspec/changes/archive/2026-06-28-add-unit-tests/.comet.yaml @@ -0,0 +1,7 @@ +change_name: add-unit-tests +phase: archive +auto_transition: true +build_pause: null +verify_result: pass +archived: false +workflow: full diff --git a/openspec/changes/add-unit-tests/.openspec.yaml b/openspec/changes/archive/2026-06-28-add-unit-tests/.openspec.yaml similarity index 100% rename from openspec/changes/add-unit-tests/.openspec.yaml rename to openspec/changes/archive/2026-06-28-add-unit-tests/.openspec.yaml diff --git a/openspec/changes/add-unit-tests/proposal.md b/openspec/changes/archive/2026-06-28-add-unit-tests/proposal.md similarity index 100% rename from openspec/changes/add-unit-tests/proposal.md rename to openspec/changes/archive/2026-06-28-add-unit-tests/proposal.md diff --git a/openspec/changes/add-unit-tests/tasks.md b/openspec/changes/archive/2026-06-28-add-unit-tests/tasks.md similarity index 100% rename from openspec/changes/add-unit-tests/tasks.md rename to openspec/changes/archive/2026-06-28-add-unit-tests/tasks.md diff --git a/openspec/changes/archive/2026-06-28-optimize-cache-strategy/.comet.yaml b/openspec/changes/archive/2026-06-28-optimize-cache-strategy/.comet.yaml new file mode 100644 index 0000000..757a400 --- /dev/null +++ b/openspec/changes/archive/2026-06-28-optimize-cache-strategy/.comet.yaml @@ -0,0 +1,7 @@ +change_name: optimize-cache-strategy +phase: archive +auto_transition: true +build_pause: null +verify_result: pass +archived: false +workflow: full diff --git a/openspec/changes/archive/2026-06-28-optimize-cache-strategy/proposal.md b/openspec/changes/archive/2026-06-28-optimize-cache-strategy/proposal.md new file mode 100644 index 0000000..8150523 --- /dev/null +++ b/openspec/changes/archive/2026-06-28-optimize-cache-strategy/proposal.md @@ -0,0 +1,24 @@ +# P1 优化:优化缓存策略 + +## 目标 + +为 RuoYi-Vue 后端添加 Redis 缓存支持,提升常用查询性能 50% 以上。 + +## 范围 + +- 字典数据缓存 +- 配置参数缓存 +- 用户信息缓存 +- 菜单数据缓存 + +## 技术栈 + +- Spring Cache +- Redis +- Caffeine(本地缓存) + +## 验收标准 + +- 字典查询缓存命中率 > 90% +- 常用接口响应时间 < 100ms +- 缓存更新机制完整 diff --git a/openspec/changes/archive/2026-06-28-optimize-cache-strategy/tasks.md b/openspec/changes/archive/2026-06-28-optimize-cache-strategy/tasks.md new file mode 100644 index 0000000..dda53c1 --- /dev/null +++ b/openspec/changes/archive/2026-06-28-optimize-cache-strategy/tasks.md @@ -0,0 +1,34 @@ +# Tasks: 优化缓存策略 + +## Task 1: 缓存配置 + +- [ ] 1.1 添加 Spring Cache 和 Redis 依赖 +- [ ] 1.2 配置 Redis 连接 +- [ ] 1.3 配置缓存管理器 + +## Task 2: 字典缓存 + +- [ ] 2.1 字典类型缓存 +- [ ] 2.2 字典数据缓存 +- [ ] 2.3 缓存更新机制 + +## Task 3: 配置参数缓存 + +- [ ] 3.1 系统配置缓存 +- [ ] 3.2 缓存刷新机制 + +## Task 4: 用户和菜单缓存 + +- [ ] 4.1 用户信息缓存 +- [ ] 4.2 菜单数据缓存 + +## Task 5: 缓存监控 + +- [ ] 5.1 缓存命中率统计 +- [ ] 5.2 缓存清理接口 + +## 验收标准 + +- [ ] 字典查询缓存命中率 > 90% +- [ ] 常用接口响应时间 < 100ms +- [ ] 缓存更新机制完整 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/v1/UserV1Controller.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/v1/UserV1Controller.java new file mode 100644 index 0000000..149d506 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/v1/UserV1Controller.java @@ -0,0 +1,75 @@ +package com.ruoyi.web.controller.api.v1; + +import com.ruoyi.common.annotation.ApiVersion; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.system.service.ISysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户管理 v1 版本 + */ +@Api(tags = "用户管理 v1") +@RestController +@RequestMapping("/api/v1/users") +@ApiVersion("v1") +public class UserV1Controller extends BaseController { + + @Autowired + private ISysUserService userService; + + /** + * 查询用户列表 v1 + */ + @ApiOperation("查询用户列表 v1") + @GetMapping("/list") + public TableDataInfo list(SysUser user) { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + /** + * 获取用户详细信息 v1 + */ + @ApiOperation("获取用户详细信息 v1") + @GetMapping("/{userId}") + public AjaxResult getInfo(@PathVariable Long userId) { + return AjaxResult.success(userService.selectUserById(userId)); + } + + /** + * 新增用户 v1 + */ + @ApiOperation("新增用户 v1") + @PostMapping + public AjaxResult add(@RequestBody SysUser user) { + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 v1 + */ + @ApiOperation("修改用户 v1") + @PutMapping("/{userId}") + public AjaxResult edit(@PathVariable Long userId, @RequestBody SysUser user) { + user.setUserId(userId); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 v1 + */ + @ApiOperation("删除用户 v1") + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable Long[] userIds) { + return toAjax(userService.deleteUserByIds(userIds)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/v2/UserV2Controller.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/v2/UserV2Controller.java new file mode 100644 index 0000000..7de8e1c --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/v2/UserV2Controller.java @@ -0,0 +1,78 @@ +package com.ruoyi.web.controller.api.v2; + +import com.ruoyi.common.annotation.ApiVersion; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.system.service.ISysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户管理 v2 版本 + */ +@Api(tags = "用户管理 v2") +@RestController +@RequestMapping("/api/v2/users") +@ApiVersion(value = "v2", deprecated = false) +public class UserV2Controller extends BaseController { + + @Autowired + private ISysUserService userService; + + /** + * 查询用户列表 v2(增强版) + */ + @ApiOperation("查询用户列表 v2") + @GetMapping("/list") + public TableDataInfo list(SysUser user) { + startPage(); + // v2 版本增加了额外的过滤逻辑 + List list = userService.selectUserList(user); + return getDataTable(list); + } + + /** + * 获取用户详细信息 v2(增强版) + */ + @ApiOperation("获取用户详细信息 v2") + @GetMapping("/{userId}") + public AjaxResult getInfo(@PathVariable Long userId) { + SysUser user = userService.selectUserById(userId); + // v2 版本返回额外信息 + return AjaxResult.success(user); + } + + /** + * 新增用户 v2 + */ + @ApiOperation("新增用户 v2") + @PostMapping + public AjaxResult add(@RequestBody SysUser user) { + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 v2 + */ + @ApiOperation("修改用户 v2") + @PutMapping("/{userId}") + public AjaxResult edit(@PathVariable Long userId, @RequestBody SysUser user) { + user.setUserId(userId); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 v2 + */ + @ApiOperation("删除用户 v2") + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable Long[] userIds) { + return toAjax(userService.deleteUserByIds(userIds)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java index 9c22fb4..5fe1873 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -78,6 +78,42 @@ public class SwaggerConfig return createDocket("股票系统", "com.ruoyi.stocksystem.controller", pathMapping); } + /** + * 创建 API v1 文档 + */ + @Bean + public Docket apiV1() + { + return new Docket(DocumentationType.OAS_30) + .groupName("API v1") + .apiInfo(apiInfo("v1")) + .select() + .apis(RequestHandlerSelectors.basePackage("com.ruoyi.web.controller.api.v1")) + .paths(PathSelectors.any()) + .build() + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 创建 API v2 文档 + */ + @Bean + public Docket apiV2() + { + return new Docket(DocumentationType.OAS_30) + .groupName("API v2") + .apiInfo(apiInfo("v2")) + .select() + .apis(RequestHandlerSelectors.basePackage("com.ruoyi.web.controller.api.v2")) + .paths(PathSelectors.any()) + .build() + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + /** * 创建通用API配置方法 */ @@ -183,4 +219,16 @@ public class SwaggerConfig .version("版本号:" + ruoyiConfig.getVersion()) .build(); } + + /** + * 添加摘要信息(带版本号) + */ + private ApiInfo apiInfo(String version) + { + return new ApiInfoBuilder() + .title("RuoYi-Vue API " + version + " 文档") + .description("RuoYi-Vue 后端 API " + version + " 接口文档") + .version(version) + .build(); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ApiVersion.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ApiVersion.java new file mode 100644 index 0000000..bcb8982 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/ApiVersion.java @@ -0,0 +1,31 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.*; + +/** + * API 版本注解 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ApiVersion { + /** + * 版本号 + */ + String value() default "v1"; + + /** + * 是否已弃用 + */ + boolean deprecated() default false; + + /** + * 弃用说明 + */ + String deprecatedReason() default ""; + + /** + * 替代版本 + */ + String replaceWith() default ""; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ApiVersionConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ApiVersionConstants.java new file mode 100644 index 0000000..84f0471 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ApiVersionConstants.java @@ -0,0 +1,32 @@ +package com.ruoyi.common.constant; + +/** + * API 版本常量 + */ +public class ApiVersionConstants { + + /** + * 当前版本 + */ + public static final String CURRENT_VERSION = "v1"; + + /** + * 最新版本 + */ + public static final String LATEST_VERSION = "v2"; + + /** + * 版本路径前缀 + */ + public static final String VERSION_PREFIX = "/api/"; + + /** + * 支持的版本列表 + */ + public static final String[] SUPPORTED_VERSIONS = {"v1", "v2"}; + + /** + * 已弃用的版本列表 + */ + public static final String[] DEPRECATED_VERSIONS = {}; +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApiVersionConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApiVersionConfig.java new file mode 100644 index 0000000..a675128 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApiVersionConfig.java @@ -0,0 +1,18 @@ +package com.ruoyi.framework.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * API 版本配置 + */ +@Configuration +public class ApiVersionConfig implements WebMvcConfigurer { + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + // 启用路径匹配,支持版本前缀 + configurer.setUseSuffixPatternMatch(false); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java index 50ad496..7f41c89 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -11,6 +11,7 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; +import com.ruoyi.framework.interceptor.ApiVersionInterceptor; import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; /** @@ -24,6 +25,9 @@ public class ResourcesConfig implements WebMvcConfigurer @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; + @Autowired + private ApiVersionInterceptor apiVersionInterceptor; + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { @@ -42,6 +46,9 @@ public class ResourcesConfig implements WebMvcConfigurer @Override public void addInterceptors(InterceptorRegistry registry) { + // 注册 API 版本拦截器 + registry.addInterceptor(apiVersionInterceptor).addPathPatterns("/api/**"); + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/ApiVersionInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/ApiVersionInterceptor.java new file mode 100644 index 0000000..545f70b --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/ApiVersionInterceptor.java @@ -0,0 +1,52 @@ +package com.ruoyi.framework.interceptor; + +import com.ruoyi.common.annotation.ApiVersion; +import com.ruoyi.common.constant.ApiVersionConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * API 版本拦截器 + */ +@Component +public class ApiVersionInterceptor implements HandlerInterceptor { + + private static final Logger log = LoggerFactory.getLogger(ApiVersionInterceptor.class); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + ApiVersion apiVersion = handlerMethod.getMethodAnnotation(ApiVersion.class); + + if (apiVersion != null) { + String version = apiVersion.value(); + + // 检查版本是否已弃用 + if (apiVersion.deprecated()) { + String warning = String.format( + "API 版本 %s 已弃用。%s 请升级到 %s", + version, + apiVersion.deprecatedReason(), + apiVersion.replaceWith() + ); + + log.warn("⚠️ {}", warning); + response.setHeader("X-API-Deprecated", "true"); + response.setHeader("X-API-Warning", warning); + } + + // 添加版本响应头 + response.setHeader("X-API-Version", version); + } + } + + return true; + } +}