feat: 完成二阶段的优化处理
All checks were successful
缓存序列化结构检查 / cache-schema-check (push) Has been skipped

This commit is contained in:
2026-07-14 15:49:40 +08:00
parent 875a5edbd1
commit cab3fea666
17 changed files with 623 additions and 118 deletions

View File

@@ -0,0 +1,101 @@
package com.codechecker.cache.detector;
import com.codechecker.cache.TestSupport;
import com.codechecker.cache.schema.SourceIndex;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RedisWritePointDetectorTest {
@Test
void detectsW04DirectObjectWrite() {
String source = ""
+ "package demo;\n"
+ "import demo.model.DemoVo;\n"
+ "public class DemoService {\n"
+ " private RedisTemplate<String, DemoVo> redisTemplate;\n"
+ " public void save(String key, DemoVo vo) {\n"
+ " redisTemplate.opsForValue().set(key, vo, 60, TimeUnit.SECONDS);\n"
+ " }\n"
+ "}\n";
String vo = ""
+ "package demo.model;\n"
+ "public class DemoVo { private String name; }\n";
SourceIndex index = new SourceIndex();
index.addSource(vo);
index.addSource(source);
Set<String> patterns = new HashSet<>(Arrays.asList("W04"));
List<WritePoint> wps = new RedisWritePointDetector(index, patterns)
.detect("DemoService.java", source);
assertEquals(1, wps.size());
WritePoint wp = wps.get(0);
assertEquals("W04", wp.getPattern());
assertEquals("demo.model.DemoVo", wp.getResolvedValueType());
}
@Test
void detectsW05HashPut() {
String source = ""
+ "package demo;\n"
+ "import demo.model.DemoVo;\n"
+ "public class DemoService {\n"
+ " private RedisTemplate<String, Object> redisTemplate;\n"
+ " public void save(String key, String field, DemoVo vo) {\n"
+ " redisTemplate.opsForHash().put(key, field, vo);\n"
+ " }\n"
+ "}\n";
String vo = ""
+ "package demo.model;\n"
+ "public class DemoVo { private String name; }\n";
SourceIndex index = new SourceIndex();
index.addSource(vo);
index.addSource(source);
Set<String> patterns = new HashSet<>(Arrays.asList("W05"));
List<WritePoint> wps = new RedisWritePointDetector(index, patterns)
.detect("DemoService.java", source);
assertEquals(1, wps.size());
assertEquals("W05", wps.get(0).getPattern());
assertEquals("demo.model.DemoVo", wps.get(0).getResolvedValueType());
}
@Test
void ignoresLockAndLiteralWrites() {
String source = TestSupport.fixture("fixtures/lock/LockService.txt");
Set<String> patterns = new HashSet<>(Arrays.asList("W01", "W02", "W03", "W04", "W05"));
List<WritePoint> wps = new RedisWritePointDetector(new SourceIndex(), patterns)
.detect("LockService.java", source);
assertTrue(wps.isEmpty(), "锁/计数器/token 写入应被忽略");
}
@Test
void stillDetectsW01ToW03() {
String helper = TestSupport.fixture("fixtures/tenant/HelperOld.txt");
String tenantVo = TestSupport.fixture("fixtures/tenant/TenantVO.txt");
String tenantLink = TestSupport.fixture("fixtures/tenant/TenantLinkModel.txt");
SourceIndex index = new SourceIndex();
index.addSource(tenantVo);
index.addSource(tenantLink);
index.addSource(helper);
Set<String> patterns = new HashSet<>(Arrays.asList("W01", "W02", "W03"));
List<WritePoint> wps = new RedisWritePointDetector(index, patterns)
.detect("Helper.java", helper);
assertEquals(1, wps.size());
assertEquals("W01", wps.get(0).getPattern());
}
}

View File

@@ -0,0 +1,68 @@
package com.codechecker.cache.key;
import com.codechecker.cache.schema.SourceIndex;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class RedisKeyResolverTest {
@Test
void resolvesStringFormatWithConstant() {
String source = ""
+ "package demo;\n"
+ "public class AttendanceService {\n"
+ " private static final String ATTENDANCE_BASE_SETTING_CACHE_KEY = "
+ "\"fbt:attendance:base_setting:cache:%s\";\n"
+ " public void save(String tenantId) {\n"
+ " String key = String.format(ATTENDANCE_BASE_SETTING_CACHE_KEY, tenantId);\n"
+ " }\n"
+ "}\n";
SourceIndex index = new SourceIndex();
index.addSource(source);
CompilationUnit cu = StaticJavaParser.parse(source);
ClassOrInterfaceDeclaration clazz = cu.getType(0).asClassOrInterfaceDeclaration();
MethodCallExpr formatCall = cu.findAll(MethodCallExpr.class).stream()
.filter(m -> "format".equals(m.getNameAsString()))
.findFirst()
.orElseThrow(IllegalStateException::new);
String pattern = new RedisKeyResolver(index).resolve(
formatCall, clazz, index.get("demo.AttendanceService"));
assertEquals("fbt:attendance:base_setting:cache:*", pattern);
}
@Test
void resolvesConstantConcatAndBuildMethod() {
String source = ""
+ "package jnpf.util;\n"
+ "public class TenantDbContentCacheHelper {\n"
+ " private static final String CACHE_KEY_PREFIX = \"tenant:db:content:\";\n"
+ " public String buildCacheKey(String encode) {\n"
+ " return CACHE_KEY_PREFIX + encode;\n"
+ " }\n"
+ " public void cache(String encode) {\n"
+ " String key = buildCacheKey(encode);\n"
+ " }\n"
+ "}\n";
SourceIndex index = new SourceIndex();
index.addSource(source);
CompilationUnit cu = StaticJavaParser.parse(source);
ClassOrInterfaceDeclaration clazz = cu.getType(0).asClassOrInterfaceDeclaration();
MethodCallExpr buildCall = cu.findAll(MethodCallExpr.class).stream()
.filter(m -> "buildCacheKey".equals(m.getNameAsString()))
.findFirst()
.orElseThrow(IllegalStateException::new);
String pattern = new RedisKeyResolver(index).resolve(
buildCall, clazz, index.get("jnpf.util.TenantDbContentCacheHelper"));
assertEquals("tenant:db:content:*", pattern);
}
}

View File

@@ -59,7 +59,7 @@ class ReportBuilderTest {
String md = new ReportBuilder("[缓存结构变更]").toMarkdown(report);
assertTrue(md.contains("- Key --> `saas:period-config:migration:current`"));
assertFalse(md.contains("key 解析)"));
assertFalse(md.contains("key 无法解析)"));
assertTrue(md.contains("> **位置**: `SaasPeriodConfigMigrationRedisSupport#putCurrent:41`"));
assertTrue(md.contains("> **类型**: `MigrationCurrentVo`"));
@@ -158,10 +158,10 @@ class ReportBuilderTest {
report.getKeyChanges().add(key);
String md = new ReportBuilder("[缓存结构变更]").toMarkdown(report);
assertTrue(md.contains("- Key --> `req.getKey()` <font color=\"comment\">key 解析)</font>"));
assertTrue(md.contains("- Key --> `req.getKey()` <font color=\"comment\">key 无法解析)</font>"));
assertTrue(md.contains("> **位置**: `ClockInXxxService#export:128`"));
assertTrue(md.contains("> **类型**: `List<ClockInExportVo>`"));
assertFalse(md.contains("unknown-key"));
assertFalse(md.contains("`unknown-key`"));
}
@Test

View File

@@ -0,0 +1,84 @@
package com.codechecker.cache.schema;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class JavaSchemaExtractorTest {
@Test
void honorsJsonIgnoreAndPropertyRename() {
String source = ""
+ "package demo;\n"
+ "import com.fasterxml.jackson.annotation.JsonIgnore;\n"
+ "import com.fasterxml.jackson.annotation.JsonProperty;\n"
+ "public class AnnotatedVo {\n"
+ " @JsonProperty(\"display_name\")\n"
+ " private String name;\n"
+ " @JsonIgnore\n"
+ " private String secret;\n"
+ " private String visible;\n"
+ "}\n";
SourceIndex index = new SourceIndex();
index.addSource(source);
TypeSchema schema = new JavaSchemaExtractor(index, 8).extract("demo.AnnotatedVo");
assertTrue(schema.getFields().containsKey("display_name"));
assertTrue(schema.getFields().containsKey("visible"));
assertFalse(schema.getFields().containsKey("secret"));
assertFalse(schema.getFields().containsKey("name"));
}
@Test
void honorsFastjsonFieldAndClassIgnoreProperties() {
String source = ""
+ "package demo;\n"
+ "import com.alibaba.fastjson.annotation.JSONField;\n"
+ "import com.fasterxml.jackson.annotation.JsonIgnoreProperties;\n"
+ "@JsonIgnoreProperties({\"password\"})\n"
+ "public class FastVo {\n"
+ " @JSONField(name = \"user_id\")\n"
+ " private String userId;\n"
+ " @JSONField(serialize = false)\n"
+ " private String token;\n"
+ " private String password;\n"
+ "}\n";
SourceIndex index = new SourceIndex();
index.addSource(source);
TypeSchema schema = new JavaSchemaExtractor(index, 8).extract("demo.FastVo");
assertTrue(schema.getFields().containsKey("user_id"));
assertFalse(schema.getFields().containsKey("token"));
assertFalse(schema.getFields().containsKey("password"));
}
@Test
void jsonNameHelpersWorkOnFieldAnnotations() {
CompilationUnit cu = StaticJavaParser.parse(""
+ "class X {\n"
+ " @com.fasterxml.jackson.annotation.JsonProperty(\"alias\")\n"
+ " private String field;\n"
+ "}");
FieldDeclaration field = cu.getType(0).asClassOrInterfaceDeclaration().getFields().get(0);
assertEquals("alias", AnnotationSupport.jsonName(field, "field"));
assertTrue(AnnotationSupport.isSerialized(field));
}
@Test
void classIgnorePropertiesCollected() {
CompilationUnit cu = StaticJavaParser.parse(""
+ "@com.fasterxml.jackson.annotation.JsonIgnoreProperties({\"a\", \"b\"})\n"
+ "class X {}");
ClassOrInterfaceDeclaration type = cu.getType(0).asClassOrInterfaceDeclaration();
assertTrue(AnnotationSupport.ignoredProperties(type).contains("a"));
assertTrue(AnnotationSupport.ignoredProperties(type).contains("b"));
}
}