feat:获取算例版本树/webapi接口自动返回creatorName和updaterName

This commit is contained in:
2025-11-21 15:45:01 +08:00
parent f45966260c
commit 03c483207c
17 changed files with 554 additions and 131 deletions

View File

@@ -0,0 +1,237 @@
package com.sdm.common.common;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sdm.common.entity.req.system.UserQueryReq;
import com.sdm.common.entity.resp.system.CIDUserResp;
import com.sdm.common.service.UserNameCacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
@RestControllerAdvice
@Slf4j
public class UserNameResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private UserNameCacheService userNameCacheService;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 只处理RestController的方法
return returnType.getContainingClass().isAnnotationPresent(RestController.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
long startTime = System.currentTimeMillis();
try {
if (body == null) {
return null;
}
processBody(body);
long cost = System.currentTimeMillis() - startTime;
log.debug("用户名转换完成,耗时: {}ms", cost);
} catch (Exception e) {
log.error("用户名转换处理失败", e);
}
return body;
}
private void processBody(Object body) {
if (body == null) return;
// 如果是SdmResponse类型处理其data字段
if (isSdmResponse(body)) {
processSdmResponse(body);
} else if (body instanceof Collection) {
((Collection<?>) body).forEach(this::processBody);
} else if (body instanceof Page) {
((Page<?>) body).getRecords().forEach(this::processBody);
} else {
processSingleObjectWithChildren(body);
}
}
/**
* 处理SdmResponse对象
*/
private void processSdmResponse(Object sdmResponse) {
try {
Field dataField = sdmResponse.getClass().getDeclaredField("data");
dataField.setAccessible(true);
Object data = dataField.get(sdmResponse);
if (data != null) {
// 递归处理data字段
processBody(data);
}
} catch (Exception e) {
log.warn("解析SdmResponse的data字段失败: {}", e.getMessage());
}
}
/**
* 判断是否是SdmResponse类型
*/
private boolean isSdmResponse(Object obj) {
if (obj == null) return false;
// 通过类名判断
String className = obj.getClass().getSimpleName();
if ("SdmResponse".equals(className)) {
return true;
}
// 检查特定字段
return hasSdmResponseFields(obj);
}
private boolean hasSdmResponseFields(Object obj) {
try {
Class<?> clazz = obj.getClass();
Field codeField = clazz.getDeclaredField("code");
Field messageField = clazz.getDeclaredField("message");
Field dataField = clazz.getDeclaredField("data");
return codeField != null && messageField != null && dataField != null;
} catch (NoSuchFieldException e) {
return false;
}
}
private void processSingleObjectWithChildren(Object obj) {
if (obj == null) return;
try {
// 收集不重复的userId
Set<Long> userIds = collectUserIds(obj);
if (!userIds.isEmpty()) {
// 获取用户名称
Map<Long, String> userNames = userNameCacheService.batchGetUserNames(userIds);
setUserNamesToObject(obj, userNames);
}
// 递归处理children字段
processChildrenField(obj);
} catch (Exception e) {
log.warn("处理对象 {} 的用户名失败: {}", obj.getClass().getSimpleName(), e.getMessage());
}
}
/**
* 递归处理children字段
*/
private void processChildrenField(Object obj) throws Exception {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if ("children".equals(field.getName())) {
field.setAccessible(true);
Object children = field.get(obj);
if (children != null) {
// 递归处理children
processBody(children);
}
// 找到children字段后就可以退出循环了
break;
}
}
}
private Set<Long> collectUserIds(Object obj) throws Exception {
Set<Long> userIds = new HashSet<>();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (isUserIdField(field)) {
field.setAccessible(true);
Long userId = (Long) field.get(obj);
userIds.add(userId);
}
}
return userIds;
}
private boolean isUserIdField(Field field) {
String fieldName = field.getName();
return field.getType() == Long.class &&
("creator".equals(fieldName) || "updater".equals(fieldName)
|| "userId".equals(fieldName));
}
private void setUserNamesToObject(Object obj, Map<Long, String> userNames) throws Exception {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (isUserIdField(field)) {
field.setAccessible(true);
Long userId = (Long) field.get(obj);
String userName = userNames.get(userId);
if (userName != null) {
// 设置对应的name字段
String nameFieldName = field.getName() + "Name";
try {
Field nameField = obj.getClass().getDeclaredField(nameFieldName);
nameField.setAccessible(true);
nameField.set(obj, userName);
} catch (NoSuchFieldException e) {
// 如果没有对应的name字段忽略
log.debug("对象 {} 没有字段 {}", obj.getClass().getSimpleName(), nameFieldName);
}
}
}
}
}
// /**
// * 批量获取用户名,减少接口调用次数
// */
// @Cacheable(value = "userNames", key = "T(com.sdm.common.common.UserNameResponseAdvice).generateKey(#userIds)")
// protected Map<Long, String> batchGetUserNames(Set<Long> userIds) {
// log.info("【缓存未命中】批量查询用户名,用户数量: {}", userIds.size());
//
// // 批量调用用户服务
// SdmResponse<List<CIDUserResp>> response = sysUserFeignClient.listUserByIds(
// UserQueryReq.builder().userIds(new ArrayList<>(userIds)).build()
// );
//
// Map<Long, String> userMap = response.getData().stream()
// .collect(Collectors.toMap(
// CIDUserResp::getUserId,
// CIDUserResp::getNickname
// ));
//
// return userMap;
// }
//
// /**
// * 生成缓存key的静态方法
// */
// public static String generateKey(Set<Long> userIds) {
// if (userIds == null || userIds.isEmpty()) {
// return "empty";
// }
// // 排序后拼接成字符串
// List<Long> sortedList = new ArrayList<>(userIds);
// Collections.sort(sortedList);
// return sortedList.toString(); // 例如: "[1, 2, 3]"
// }
}

View File

@@ -0,0 +1,51 @@
package com.sdm.common.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
/**
* 用户名缓存配置
*/
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
// 用户名缓存
new ConcurrentMapCache("userNames")
// 可以添加其他缓存配置
));
return cacheManager;
}
@Bean
public CacheErrorHandler cacheErrorHandler() {
return new SimpleCacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.warn("缓存获取失败, key: {}, 错误: {}", key, exception.getMessage());
// 不抛出异常,继续执行
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.warn("缓存存入失败, key: {}, 错误: {}", key, exception.getMessage());
// 不抛出异常,继续执行
}
};
}
}

View File

@@ -0,0 +1,55 @@
package com.sdm.common.service;
import com.sdm.common.common.SdmResponse;
import com.sdm.common.entity.req.system.UserQueryReq;
import com.sdm.common.entity.resp.system.CIDUserResp;
import com.sdm.common.feign.inter.system.ISysUserFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class UserNameCacheService {
@Autowired
private ISysUserFeignClient sysUserFeignClient;
/**
* 批量获取用户名 - 带缓存 TODO 后续加入Redis的时候改成Redis
*/
@Cacheable(value = "userNames", key = "#userIds.toString()")
public Map<Long, String> batchGetUserNames(Set<Long> userIds) {
log.info("【缓存未命中】批量查询用户名,用户数量: {}", userIds.size());
// 批量调用用户服务
SdmResponse<List<CIDUserResp>> response = sysUserFeignClient.listUserByIds(
UserQueryReq.builder().userIds(new ArrayList<>(userIds)).build()
);
Map<Long, String> userMap = response.getData().stream()
.collect(Collectors.toMap(
CIDUserResp::getUserId,
CIDUserResp::getNickname
));
return userMap;
}
/**
* 生成缓存key的静态方法
*/
public static String generateKey(Set<Long> userIds) {
if (userIds == null || userIds.isEmpty()) {
return "empty";
}
// 排序后拼接成字符串
List<Long> sortedList = new ArrayList<>(userIds);
Collections.sort(sortedList);
return sortedList.toString();
}
}