记得上下班打卡 | git大法好,push需谨慎

Commit 6fc379f3 authored by 姜秀龙's avatar 姜秀龙

接口分级相关 order adam 配置

parent d1fb9e76
package com.liquidnet.common.swagger.annotation;
import java.lang.annotation.*;
/**
* 接口分级注解
* 用于标识接口的重要性级别
*
* @author system
* @since 2024-10-29
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLevel {
/**
* 接口级别 - 默认为L99(未配置),强制开发者明确指定级别
*/
Level level() default Level.L99;
/**
* 描述信息
*/
String description() default "";
/**
* 接口级别枚举 - 数字越小越重要
*/
enum Level {
L1(1, "核心", "核心业务功能,必须稳定,影响系统可用性"),
L2(2, "重要", "重要业务功能,影响用户体验"),
L3(3, "普通", "常规业务功能"),
L4(4, "低级", "非核心功能,影响范围小"),
L88(88, "无需分级", "该接口明确标记为无需分级或暂不分级"),
L99(99, "未配置", "该接口尚未配置分级信息");
private final int value;
private final String desc;
private final String detail;
Level(int value, String desc, String detail) {
this.value = value;
this.desc = desc;
this.detail = detail;
}
public int getValue() { return value; }
public String getDesc() { return desc; }
public String getDetail() { return detail; }
/**
* 判断是否为未配置级别
*/
public boolean isUnconfigured() {
return this == L99;
}
/**
* 判断是否为无需分级
*/
public boolean isNoGrading() {
return this == L88;
}
/**
* 根据数字值获取级别
*/
public static Level fromValue(int value) {
for (Level level : values()) {
if (level.value == value) {
return level;
}
}
return L99; // 默认返回未配置
}
/**
* 获取所有业务级别(排除L88和L99)
*/
public static Level[] getBusinessLevels() {
return new Level[]{L1, L2, L3, L4};
}
}
}
\ No newline at end of file
package com.liquidnet.common.swagger.config;
import com.liquidnet.common.swagger.annotation.ApiLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.service.StringVendorExtension;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Swagger插件 - 将ApiLevel信息显示在Swagger文档中
*
* @author system
* @since 2024-10-29
*/
@Component
@Order(1000)
@Slf4j
public class ApiLevelSwaggerPlugin implements OperationBuilderPlugin {
@Override
public void apply(OperationContext context) {
// 获取方法上的ApiLevel注解
Optional<ApiLevel> methodApiLevel = context.findAnnotation(ApiLevel.class);
// 如果方法上没有,尝试获取类上的注解(继承逻辑)
if (!methodApiLevel.isPresent()) {
methodApiLevel = context.findControllerAnnotation(ApiLevel.class);
}
// 获取当前的notes(描述)
String currentNotes = context.operationBuilder().build().getNotes();
String levelInfo;
if (methodApiLevel.isPresent()) {
ApiLevel apiLevel = methodApiLevel.get();
// 所有配置的级别都是正常的分级,包括L0
levelInfo = String.format("【接口级别:%s - %s】",
apiLevel.level().name(),
apiLevel.level().getDesc());
if (apiLevel.description() != null && !apiLevel.description().isEmpty()) {
levelInfo += " " + apiLevel.description();
}
addExtensions(context, apiLevel.level().name(), apiLevel.level().getDesc(),
apiLevel.level().getDetail(), apiLevel.description());
} else {
// 完全没有注解的情况,使用L99级别
ApiLevel.Level unconfiguredLevel = ApiLevel.Level.L99;
levelInfo = String.format("【接口级别:%s - %s】",
unconfiguredLevel.name(),
unconfiguredLevel.getDesc());
addExtensions(context, unconfiguredLevel.name(), unconfiguredLevel.getDesc(),
unconfiguredLevel.getDetail(), null);
}
// 将级别信息添加到notes中
String newNotes = currentNotes != null ?
levelInfo + "\n\n" + currentNotes : levelInfo;
context.operationBuilder().notes(newNotes);
}
/**
* 添加扩展字段到Swagger JSON
*/
private void addExtensions(OperationContext context, String level, String levelDesc,
String levelDetail, String description) {
try {
List<VendorExtension> extensions = new ArrayList<>();
extensions.add(new StringVendorExtension("x-api-level", level));
extensions.add(new StringVendorExtension("x-api-level-desc", levelDesc));
extensions.add(new StringVendorExtension("x-api-level-detail", levelDetail));
if (description != null && !description.trim().isEmpty()) {
extensions.add(new StringVendorExtension("x-api-description", description));
}
context.operationBuilder().extensions(extensions);
} catch (Exception e) {
log.debug("扩展字段设置失败: {}", e.getMessage());
}
}
@Override
public boolean supports(DocumentationType delimiter) {
// 支持Swagger 2.0,如果有OAS 3.0也尝试支持
if (DocumentationType.SWAGGER_2.equals(delimiter)) {
return true;
}
// 尝试支持OAS 3.0(如果可用)
try {
return "OAS_30".equals(delimiter.getName());
} catch (Exception e) {
return false;
}
}
}
\ No newline at end of file
package com.liquidnet.common.swagger.controller;
import com.liquidnet.common.swagger.annotation.ApiLevel;
import com.liquidnet.common.swagger.dto.ApiLevelInfoDto;
import com.liquidnet.common.swagger.dto.ApiLevelStatisticsDto;
import com.liquidnet.common.swagger.service.ApiLevelStatisticsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 接口分级统计控制器
*
* @author system
* @since 2024-10-29
*/
@Api(tags = "系统管理-接口分级统计")
@RestController
@RequestMapping("/levelStatistics")
@Slf4j
@ApiLevel(level = ApiLevel.Level.L2, description = "接口分级统计管理")
public class ApiLevelStatisticsController {
@Autowired
private ApiLevelStatisticsService apiLevelStatisticsService;
@GetMapping("/overview")
@ApiOperation("获取接口分级统计概览(理想是获取所有服务的数据, 但是现在还做不到预留吧)")
@ApiLevel(level = ApiLevel.Level.L3, description = "查看系统所有接口的分级统计信息")
public ApiLevelStatisticsDto getApiLevelStatistics() {
try {
return apiLevelStatisticsService.getApiLevelStatistics();
} catch (Exception e) {
log.error("获取接口分级统计失败", e);
throw new RuntimeException("获取接口分级统计失败: " + e.getMessage());
}
}
@GetMapping("/levels")
@ApiOperation("获取所有接口级别定义")
@ApiLevel(level = ApiLevel.Level.L4, description = "查看接口级别定义说明")
public List<ApiLevelInfoDto> getApiLevels() {
List<ApiLevelInfoDto> levels = new ArrayList<>();
for (ApiLevel.Level level : ApiLevel.Level.values()) {
ApiLevelInfoDto levelInfo = new ApiLevelInfoDto();
levelInfo.setCode(level.name()); // L1, L2, L3, L4, L88, L99
levelInfo.setValue(level.getValue()); // 1, 2, 3, 4, 88, 99
levelInfo.setLabel(level.getDesc()); // 核心, 重要, 普通, 低级, 无需分级, 未配置
levelInfo.setDescription(level.getDetail()); // 详细描述
levelInfo.setIsUnconfigured(level.isUnconfigured()); // 是否为未配置
levelInfo.setIsNoGrading(level.isNoGrading()); // 是否为无需分级
// 判断是否为业务级别 (L1-L4)
boolean isBusinessLevel = level.getValue() >= 1 && level.getValue() <= 4;
levelInfo.setIsBusinessLevel(isBusinessLevel);
levels.add(levelInfo);
}
return levels;
}
@GetMapping("/service")
@ApiOperation("获取本服务接口分级统计信息")
@ApiLevel(level = ApiLevel.Level.L3, description = "获取当前服务的接口分级统计,包含按级别分组的接口列表")
public Map<String, Object> getServiceStatistics() {
try {
ApiLevelStatisticsDto fullStats = apiLevelStatisticsService.getApiLevelStatistics();
Map<String, Object> serviceStats = new HashMap<>();
// 基础统计信息
serviceStats.put("totalCount", fullStats.getTotalCount());
serviceStats.put("levelStatistics", fullStats.getLevelStatistics());
// 按级别分组的接口列表 - 动态初始化所有级别
Map<String, List<Map<String, Object>>> apisByLevel = new HashMap<>();
for (ApiLevel.Level level : ApiLevel.Level.values()) {
apisByLevel.put(level.name(), new ArrayList<>());
}
// 将接口按级别分组
for (ApiLevelStatisticsDto.ApiInfo api : fullStats.getApiList()) {
Map<String, Object> apiInfo = new HashMap<>();
apiInfo.put("url", api.getUrl());
apiInfo.put("method", api.getHttpMethod());
apiInfo.put("controller", api.getControllerName());
apiInfo.put("methodName", api.getMethodName());
apiInfo.put("description", api.getApiDescription());
// 现在所有接口都有级别(包括L99未配置)
String levelKey = api.getLevel().name();
apisByLevel.get(levelKey).add(apiInfo);
}
serviceStats.put("apisByLevel", apisByLevel);
return serviceStats;
} catch (Exception e) {
log.error("获取服务统计失败", e);
throw new RuntimeException("获取服务统计失败: " + e.getMessage());
}
}
}
\ No newline at end of file
package com.liquidnet.common.swagger.dto;
import lombok.Data;
/**
* 接口级别信息DTO
*
* @author system
* @since 2024-10-29
*/
@Data
public class ApiLevelInfoDto {
/**
* 级别代码 (L1, L2, L3, L4, L88, L99)
*/
private String code;
/**
* 级别数值 (1, 2, 3, 4, 88, 99)
*/
private Integer value;
/**
* 级别标签 (核心, 重要, 普通, 低级, 无需分级, 未配置)
*/
private String label;
/**
* 级别详细描述
*/
private String description;
/**
* 是否为未配置级别
*/
private Boolean isUnconfigured;
/**
* 是否为无需分级级别
*/
private Boolean isNoGrading;
/**
* 是否为业务级别 (L1-L4)
*/
private Boolean isBusinessLevel;
}
\ No newline at end of file
package com.liquidnet.common.swagger.dto;
import com.liquidnet.common.swagger.annotation.ApiLevel;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 接口分级统计数据传输对象
*
* @author system
* @since 2024-10-29
*/
@Data
public class ApiLevelStatisticsDto {
/**
* 接口总数
*/
private Integer totalCount;
/**
* 接口列表
*/
private List<ApiInfo> apiList;
/**
* 级别统计 (级别名称 -> 数量)
*/
private Map<String, Integer> levelStatistics;
/**
* 服务统计 (服务名称 -> (级别名称 -> 数量))
*/
private Map<String, Map<String, Integer>> serviceStatistics;
/**
* 接口信息
*/
@Data
public static class ApiInfo {
/**
* 服务名称
*/
private String serviceName;
/**
* 控制器名称
*/
private String controllerName;
/**
* 方法名称
*/
private String methodName;
/**
* 接口URL
*/
private String url;
/**
* HTTP方法
*/
private String httpMethod;
/**
* 接口级别
*/
private ApiLevel.Level level;
/**
* 分级描述
*/
private String description;
/**
* API描述
*/
private String apiDescription;
}
}
\ No newline at end of file
package com.liquidnet.common.swagger.service;
import com.liquidnet.common.swagger.annotation.ApiLevel;
import com.liquidnet.common.swagger.dto.ApiLevelStatisticsDto;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* 接口分级统计服务
*
* @author system
* @since 2024-10-29
*/
@Service
@Slf4j
public class ApiLevelStatisticsService {
@Autowired
private ApplicationContext applicationContext;
@Value("${liquidnet.info.context:unknown-service}")
private String rawServiceName;
/**
* 获取处理后的服务名称(去掉前后斜杠)
*/
private String getCleanServiceName() {
if (rawServiceName == null || rawServiceName.trim().isEmpty()) {
return "unknown-service";
}
String cleaned = rawServiceName.trim();
// 去掉前面的斜杠
if (cleaned.startsWith("/")) {
cleaned = cleaned.substring(1);
}
// 去掉后面的斜杠
if (cleaned.endsWith("/")) {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned.isEmpty() ? "unknown-service" : cleaned;
}
/**
* 获取所有接口的分级统计信息
*/
public ApiLevelStatisticsDto getApiLevelStatistics() {
ApiLevelStatisticsDto statistics = new ApiLevelStatisticsDto();
// 获取所有Controller
Map<String, Object> controllers = applicationContext.getBeansWithAnnotation(RestController.class);
controllers.putAll(applicationContext.getBeansWithAnnotation(Controller.class));
List<ApiLevelStatisticsDto.ApiInfo> apiInfoList = new ArrayList<>();
for (Map.Entry<String, Object> entry : controllers.entrySet()) {
Object controller = entry.getValue();
Class<?> controllerClass = controller.getClass();
// 跳过代理类
if (controllerClass.getName().contains("$$")) {
controllerClass = controllerClass.getSuperclass();
}
// 过滤掉系统自带的Controller
if (shouldSkipController(controllerClass)) {
continue;
}
// 使用配置文件中的服务名称
// 获取类级别的ApiLevel注解
ApiLevel classApiLevel = controllerClass.getAnnotation(ApiLevel.class);
// 遍历所有方法
Method[] methods = controllerClass.getDeclaredMethods();
for (Method method : methods) {
if (isApiMethod(method)) {
ApiLevelStatisticsDto.ApiInfo apiInfo = buildApiInfo(method, controllerClass, getCleanServiceName(), classApiLevel);
if (apiInfo != null) {
apiInfoList.add(apiInfo);
}
}
}
}
statistics.setApiList(apiInfoList);
statistics.setTotalCount(apiInfoList.size());
statistics.setLevelStatistics(calculateLevelStatistics(apiInfoList));
// 注意:对于单服务,serviceStatistics与levelStatistics内容相同
// 保留serviceStatistics是为了API一致性和未来扩展性
statistics.setServiceStatistics(calculateServiceStatistics(apiInfoList));
return statistics;
}
/**
* 判断是否应该跳过该Controller
*/
private boolean shouldSkipController(Class<?> controllerClass) {
String className = controllerClass.getName();
String simpleName = controllerClass.getSimpleName();
// 过滤掉系统自带的Controller
return className.contains("ApiResourceController") ||
className.contains("BasicErrorController") ||
simpleName.equals("ApiResourceController") ||
simpleName.equals("BasicErrorController") ||
// 过滤掉接口分级统计相关的Controller
simpleName.equals("ApiLevelStatisticsController") ||
className.contains("com.liquidnet.common.swagger.controller") ||
// 过滤掉Spring Boot Actuator相关的Controller
className.contains("org.springframework.boot.actuate") ||
// 过滤掉Spring Security相关的Controller
className.contains("org.springframework.security") ||
// 过滤掉其他框架的Controller
className.contains("springfox.documentation") ||
className.contains("com.github.xiaoymin.knife4j");
}
/**
* 判断是否为API方法
*/
private boolean isApiMethod(Method method) {
return method.isAnnotationPresent(RequestMapping.class) ||
method.isAnnotationPresent(GetMapping.class) ||
method.isAnnotationPresent(PostMapping.class) ||
method.isAnnotationPresent(PutMapping.class) ||
method.isAnnotationPresent(DeleteMapping.class) ||
method.isAnnotationPresent(PatchMapping.class);
}
/**
* 构建API信息
*/
private ApiLevelStatisticsDto.ApiInfo buildApiInfo(Method method, Class<?> controllerClass,
String serviceName, ApiLevel classApiLevel) {
ApiLevelStatisticsDto.ApiInfo apiInfo = new ApiLevelStatisticsDto.ApiInfo();
// 获取方法级别的ApiLevel注解
ApiLevel methodApiLevel = method.getAnnotation(ApiLevel.class);
ApiLevel effectiveApiLevel = methodApiLevel != null ? methodApiLevel : classApiLevel;
// 如果没有ApiLevel注解,使用L99表示未配置
if (effectiveApiLevel == null) {
ApiLevel.Level unconfiguredLevel = ApiLevel.Level.L99;
apiInfo.setLevel(unconfiguredLevel);
apiInfo.setDescription(unconfiguredLevel.getDesc()); // 使用枚举中的描述
} else {
// 所有配置的级别都是正常的分级
apiInfo.setLevel(effectiveApiLevel.level());
apiInfo.setDescription(effectiveApiLevel.description());
}
// 设置基本信息
apiInfo.setServiceName(serviceName);
apiInfo.setControllerName(controllerClass.getSimpleName());
apiInfo.setMethodName(method.getName());
apiInfo.setUrl(getApiUrl(method, controllerClass));
apiInfo.setHttpMethod(getHttpMethod(method));
// 获取API描述
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
if (apiOperation != null) {
apiInfo.setApiDescription(apiOperation.value());
}
return apiInfo;
}
/**
* 获取API URL
*/
private String getApiUrl(Method method, Class<?> controllerClass) {
StringBuilder url = new StringBuilder();
// 获取类级别的RequestMapping
RequestMapping classMapping = controllerClass.getAnnotation(RequestMapping.class);
if (classMapping != null && classMapping.value().length > 0) {
url.append(classMapping.value()[0]);
}
// 获取方法级别的映射
String methodPath = getMethodPath(method);
if (methodPath != null && !methodPath.isEmpty()) {
if (!url.toString().endsWith("/") && !methodPath.startsWith("/")) {
url.append("/");
}
url.append(methodPath);
}
return url.toString();
}
/**
* 获取方法路径
*/
private String getMethodPath(Method method) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "";
}
if (method.isAnnotationPresent(GetMapping.class)) {
GetMapping mapping = method.getAnnotation(GetMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "";
}
if (method.isAnnotationPresent(PostMapping.class)) {
PostMapping mapping = method.getAnnotation(PostMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "";
}
if (method.isAnnotationPresent(PutMapping.class)) {
PutMapping mapping = method.getAnnotation(PutMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "";
}
if (method.isAnnotationPresent(DeleteMapping.class)) {
DeleteMapping mapping = method.getAnnotation(DeleteMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "";
}
if (method.isAnnotationPresent(PatchMapping.class)) {
PatchMapping mapping = method.getAnnotation(PatchMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "";
}
return "";
}
/**
* 获取HTTP方法
*/
private String getHttpMethod(Method method) {
if (method.isAnnotationPresent(GetMapping.class)) return "GET";
if (method.isAnnotationPresent(PostMapping.class)) return "POST";
if (method.isAnnotationPresent(PutMapping.class)) return "PUT";
if (method.isAnnotationPresent(DeleteMapping.class)) return "DELETE";
if (method.isAnnotationPresent(PatchMapping.class)) return "PATCH";
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
if (mapping.method().length > 0) {
return mapping.method()[0].name();
}
}
return "GET";
}
/**
* 计算级别统计
*/
private Map<String, Integer> calculateLevelStatistics(List<ApiLevelStatisticsDto.ApiInfo> apiList) {
// 统计实际数据,现在所有级别都在枚举中
Map<String, Integer> actualStats = apiList.stream()
.collect(Collectors.groupingBy(
api -> api.getLevel().name(),
Collectors.collectingAndThen(Collectors.counting(), Math::toIntExact)
));
// 动态获取所有配置的级别,确保所有级别都显示
Map<String, Integer> completeStats = new LinkedHashMap<>();
// 遍历枚举中定义的所有级别(包括L99)
for (ApiLevel.Level level : ApiLevel.Level.values()) {
completeStats.put(level.name(), actualStats.getOrDefault(level.name(), 0));
}
return completeStats;
}
/**
* 计算服务统计
*/
private Map<String, Map<String, Integer>> calculateServiceStatistics(List<ApiLevelStatisticsDto.ApiInfo> apiList) {
// 先按服务分组统计,现在所有级别都在枚举中
Map<String, Map<String, Integer>> actualServiceStats = apiList.stream()
.collect(Collectors.groupingBy(
ApiLevelStatisticsDto.ApiInfo::getServiceName,
Collectors.groupingBy(
api -> api.getLevel().name(),
Collectors.collectingAndThen(Collectors.counting(), Math::toIntExact)
)
));
// 为每个服务补全所有级别(确保所有级别都显示,没有数据的显示0)
Map<String, Map<String, Integer>> completeServiceStats = new LinkedHashMap<>();
for (Map.Entry<String, Map<String, Integer>> entry : actualServiceStats.entrySet()) {
String serviceName = entry.getKey();
Map<String, Integer> serviceLevelStats = entry.getValue();
Map<String, Integer> completeLevelStats = new LinkedHashMap<>();
// 动态获取所有配置的级别(包括L99)
for (ApiLevel.Level level : ApiLevel.Level.values()) {
completeLevelStats.put(level.name(), serviceLevelStats.getOrDefault(level.name(), 0));
}
completeServiceStats.put(serviceName, completeLevelStats);
}
return completeServiceStats;
}
}
\ No newline at end of file
...@@ -115,6 +115,8 @@ spring: ...@@ -115,6 +115,8 @@ spring:
# ----------------------------------------------------------- # -----------------------------------------------------------
global-auth: global-auth:
exclude-url-pattern: # 模式I(与模式II互斥) exclude-url-pattern: # 模式I(与模式II互斥)
- ${liquidnet.info.context}/levelTest/**
- ${liquidnet.info.context}/levelStatistics/**
- ${liquidnet.info.context}/doc.html - ${liquidnet.info.context}/doc.html
- ${liquidnet.info.context}/webjars/** - ${liquidnet.info.context}/webjars/**
- ${liquidnet.info.context}/swagger-resources/** - ${liquidnet.info.context}/swagger-resources/**
......
...@@ -108,6 +108,8 @@ spring: ...@@ -108,6 +108,8 @@ spring:
# ----------------------------------------------------------- # -----------------------------------------------------------
global-auth: global-auth:
exclude-url-pattern: exclude-url-pattern:
- ${liquidnet.info.context}/levelTest/**
- ${liquidnet.info.context}/levelStatistics/**
- ${liquidnet.info.context}/doc.html - ${liquidnet.info.context}/doc.html
- ${liquidnet.info.context}/webjars/** - ${liquidnet.info.context}/webjars/**
- ${liquidnet.info.context}/swagger-resources/** - ${liquidnet.info.context}/swagger-resources/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment