[CMLR-030] 控制器签名 Map->DTO 等价替换

This commit is contained in:
2026-02-08 20:16:36 +08:00
parent 5be92d1727
commit 75badad2b2
2 changed files with 120 additions and 1 deletions

View File

@@ -0,0 +1,119 @@
package vvpkassistant.controller;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import vvpkassistant.Data.ResponseData;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
class ControllerMapToDtoContractTests {
@Test
void shouldKeepAllFormerMapEndpointsAndReturnTypes() {
Map<String, Class<?>> expected = new LinkedHashMap<>();
expected.put("user/inputUserInfo", ResponseData.class);
expected.put("user/loginWithPhoneNumber", ResponseData.class);
expected.put("user/queryMyAllPkData", ResponseData.class);
expected.put("user/handlePkInfo", ResponseData.class);
expected.put("user/pkRecordDetail", ResponseData.class);
expected.put("user/pinToTop", ResponseData.class);
expected.put("user/cancelPin", ResponseData.class);
expected.put("user/pointsDetail", ResponseData.class);
expected.put("pk/pkList", ResponseData.class);
expected.put("pk/queryMyCanUsePkData", ResponseData.class);
expected.put("pk/pkInfoDetail", ResponseData.class);
expected.put("pk/deletePkDataWithId", ResponseData.class);
expected.put("pk/fetchDetailPkDataWithId", ResponseData.class);
expected.put("pk/listUninvitedPublishedAnchorsByUserId", ResponseData.class);
expected.put("anchor/list", ResponseData.class);
expected.put("anchor/deleteMyAnchor", ResponseData.class);
expected.put("systemMessage/list", ResponseData.class);
expected.put("chat/receiveImMessage", Map.class);
Map<String, Class<?>> actual = new HashMap<>();
Class<?>[] controllers = new Class<?>[]{
UserController.class,
PkController.class,
AnchorsController.class,
SystemMessageController.class,
ChatController.class
};
for (Class<?> controllerClass : controllers) {
RequestMapping requestMapping = controllerClass.getAnnotation(RequestMapping.class);
String basePath = requestMapping == null ? "" : firstPath(requestMapping.value(), requestMapping.path());
for (Method method : controllerClass.getDeclaredMethods()) {
PostMapping postMapping = method.getAnnotation(PostMapping.class);
if (postMapping == null) {
continue;
}
String subPath = firstPath(postMapping.value(), postMapping.path());
String fullPath = joinPath(basePath, subPath);
if (expected.containsKey(fullPath)) {
actual.put(fullPath, method.getReturnType());
}
}
}
Assertions.assertEquals(18, actual.size(), "目标端点数量不匹配");
expected.forEach((path, returnType) ->
Assertions.assertEquals(returnType, actual.get(path), "端点返回类型不一致: " + path)
);
}
@Test
void shouldNotUseRequestBodyMapInTargetControllers() {
Class<?>[] controllers = new Class<?>[]{
UserController.class,
PkController.class,
AnchorsController.class,
SystemMessageController.class,
ChatController.class
};
for (Class<?> controllerClass : controllers) {
for (Method method : controllerClass.getDeclaredMethods()) {
for (Parameter parameter : method.getParameters()) {
if (!hasRequestBody(parameter.getAnnotations())) {
continue;
}
Assertions.assertFalse(Map.class.isAssignableFrom(parameter.getType()),
"存在 @RequestBody Map 参数: " + controllerClass.getSimpleName() + "." + method.getName());
}
}
}
}
private static boolean hasRequestBody(Annotation[] annotations) {
return Arrays.stream(annotations).anyMatch(annotation -> annotation.annotationType() == RequestBody.class);
}
private static String firstPath(String[] values, String[] paths) {
if (values != null && values.length > 0 && values[0] != null && !values[0].isEmpty()) {
return values[0];
}
if (paths != null && paths.length > 0 && paths[0] != null && !paths[0].isEmpty()) {
return paths[0];
}
return "";
}
private static String joinPath(String basePath, String subPath) {
String base = basePath.startsWith("/") ? basePath.substring(1) : basePath;
String sub = subPath.startsWith("/") ? subPath.substring(1) : subPath;
if (base.isEmpty()) {
return sub;
}
if (sub.isEmpty()) {
return base;
}
return base + "/" + sub;
}
}