Skill: qfann/deep-java-review (version 1.0.2)

--- name: java-code-review description: "Java项目代码review工具。分析Git变更+完整调用链路上下文，推断业务需求，进行多维度评分和分类汇总，生成完整PRD文档。包含细粒度Java代码审查清单（Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DDL质量）。" category: development risk: safe source: custom date_added: "2026-04-28" author: user tags: [java, code-review, git-diff, business-analysis, risk-detection, performance, optimization, spring, mybatis, transaction, sql-ddl, concurrency, scoring, prd, null-safety, streams, exception-handling, resource-management, api-design] tools: [git, glob, grep, read, write, bash, lsp_diagnostics, ast_grep_search, lsp_find_references, lsp_goto_definition] compatibility: opencode trigger_phrase: - 代码评审 - review代码 - 分析变更 - 代码检查 - 检查bug - 风险分析 - 性能检查 - 需求分析 - 业务分析 - 生成PRD --- # Java Code Review - Java项目代码评审工具 ## 概述 **智能化Java项目代码评审工具**，完整流程（7步）： 1. **Git Diff 变更获取** - 拉取代码变更内容（支持 --cached / 分支对比） 2. **完整上下文分析** - 分析调用链路，推断业务逻辑 3. **PRD文档生成** - 覆盖所有变更点（新增+修改） 4. **代码检测（P0/P1快速扫描）** - 严重缺陷和代码缺陷快速定位 5. **细粒度代码审查清单** - 12类深度检查（A-L）：Null安全、异常处理、Streams、并发、Java惯用法、资源管理、API设计、性能、测试、MyBatis/ORM、事务边界、SQL/DDL质量 6. **多维度评分** - 代码质量评分 7. **Review报告生成** - 输出结构化报告 ## 何时使用此技能 - 用户需要进行代码评审 - 用户需要理解变更的业务含义 - 用户需要生成完整PRD文档 - 用户需要评分和质量评估 - 用户说 "review代码" / "检查PR" / "代码review" / "检查下代码" --- ## 第1步：Git Diff 变更获取 ### 1.1 确定基准分支 **优先询问用户**，如用户未指定则使用以下策略： - 当前分支有上游跟踪 → 使用 `@{u}`（上游分支） - 存在 `develop` 分支 → 使用 `develop` - 存在 `main`/`master` 分支 → 使用 `main`/`master` - 否则 → 使用 `HEAD~10`（最近10次提交） ### 1.2 获取变更 ```bash # 场景1：工作区+暂存区 vs 基准分支（最常用） git diff <基准分支>..HEAD --name-only git diff <基准分支>..HEAD -U500 git diff <基准分支>..HEAD --stat # 场景2：仅检查已暂存（staged）的变更 git diff --cached -U500 git diff --cached --stat # 场景3：工作区未暂存变更 git diff -U500 ``` ### 1.3 收集信息 - 新增的文件（新增功能） - 修改的文件（功能修改） - 删除的文件（功能删除） - 变更行数统计 --- ## 第2步：完整上下文分析 ### 2.1 确定入口点 对于每个变更的方法/类，向上追溯找到业务入口： - Controller层的HTTP入口（@PostMapping/@GetMapping） - Service层的业务入口方法 - MQ消息入口（@RabbitListener/@KafkaListener） - 定时任务入口（@Scheduled） - 外部API入口（Feign Client） ### 2.2 追踪方法链 使用 lsp_find_references 分析每个关键方法： ``` 入口点 ↓ 调用 变更方法 ↓ 调用 子方法1 → 子方法2 → ... → 数据库/外部服务 ``` ### 2.3 业务逻辑推断 基于完整链路推断业务逻辑： ``` 变更方法：InventoryService.deduct() 完整链路： Controller.createOrder() [下单入口] ↓ OrderService.create() [订单创建] ↓ InventoryService.deduct() [变更点] ← 库存扣减 ↓ PaymentService.pay() [支付] ↓ NotificationService.notify() [通知] 推断业务需求： 用户下单 → 自动扣减库存 → 支付 → 通知 核心流程：订单+库存+支付+消息 四模块联动 ``` --- ## 第3步：PRD文档生成（核心能力） **重要**：PRD必须覆盖**所有变更点**，包括新增功能和修改功能。 ### 3.1 PRD文档结构 ```markdown # 产品需求文档 PRD ## 1. 概述 ### 1.1 产品名称 [模块名称-版本号] ### 1.2 需求背景 [为什么需要这个功能，解决什么问题] ### 1.3 需求范围 - 涉及模块：xxx - 用户类型：xxx - 使用场景：xxx --- ## 2. 功能需求 ### 2.1 功能清单 | # | 功能点名称 | 功能类型 | 优先级 | 涉及文件 | 行号 | |---|-----------|--------|--------|---------|-----| | 1 | 功能点A | 新增 | P0 | XxxService.java | 1-100 | | 2 | 功能点B | 新增 | P1 | XxxServiceImpl.java | 200-300 | | 3 | 功能点C | 修改 | P2 | XxxConverter.java | 10-30 | | 4 | 字段调整 | 修改 | P2 | DDL脚本 | - | ### 2.2 功能详情（每个变更点都需要详细描述） #### 2.2.1 [功能点A-新增功能] **功能类型**：新增 **功能描述**： [详细描述这个功能做什么] **业务规则**： - 规则1 - 规则2 **核心链路**： ``` [入口] Controller.method() [POST /api/xxx] ↓ Service.method() [核心] 核心业务逻辑 ← 变更内容 ↓ DAO.method() [终点] 数据库/外部服务 ``` **入参说明**： | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | param1 | String | 是 | xxx | | param2 | Integer | 否 | xxx | **出参说明**： | 参数 | 类型 | 说明 | |------|------|------| | result1 | String | xxx | **异常处理**： | 异常场景 | 错误码 | 提示信息 | |----------|--------|----------| | 场景1 | E_xxx | xxx | --- #### 2.2.2 [功能点B-修改功能] **功能类型**：修改 **涉及文件**：`xxx.java` **涉及行号**：xxx-xxx **修改前**： ```java // 原代码 ``` **修改后**： ```java // 新代码 ``` **变更原因**： [为什么要这样修改] **核心链路**： ``` [入口] Controller.method() [POST /api/xxx] ↓ Service.method() [核心] 修改的方法 ← 变更内容 ↓ DAO.method() [终点] 数据库/外部服务 ``` **影响分析**： - 影响的上游：xxx - 影响的下游：xxx - 兼容性：是否向前兼容 - 数据库：是否需要迁移 --- #### 2.2.3 [功能点C-优化功能] **功能类型**：优化 **优化前**： [优化前的描述] **优化后**： [优化后的描述] **优化效果**： - 性能提升：xxx% - 代码改进：xxx --- ## 3. 数据库变更 ### 3.1 新增表 ```sql CREATE TABLE xxx ( id VARCHAR(36) PRIMARY KEY, tenant_id VARCHAR(32) NOT NULL, org_id VARCHAR(32), doc_id VARCHAR(36) NOT NULL, retry_type VARCHAR(32), retry_count INT DEFAULT 0, last_retry_time DATETIME, create_time DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_doc_id (doc_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx表'; ``` ### 3.2 修改表 ```sql -- 修改字段长度 ALTER TABLE xxx MODIFY COLUMN goods_name VARCHAR(300) COMMENT '商品名称'; -- 新增字段 ALTER TABLE xxx ADD COLUMN operator VARCHAR(300) COMMENT '操作人'; ``` ### 3.3 数据迁移 [如有数据迁移需求，描述迁移脚本] --- ## 4. 接口设计 ### 4.1 新增接口 | 接口名称 | 请求方式 | 路径 | 说明 | |---------|---------|------|------| | 接口A | POST | /api/xxx | xxx | **请求参数**： ```json { "ids": ["xxx"], "operator": "xxx" } ``` **响应参数**： ```json { "code": "0", "message": "成功", "data": null } ``` ### 4.2 修改接口 | 接口名称 | 请求方式 | 路径 | 说明 | |---------|---------|------|------| | 接口A | POST | /api/xxx | 参数调整 | **变更说明**： [描述接口参数变更] --- ## 5. 涉及的代码变更 ### 5.1 新增文件 | 文件路径 | 说明 | |---------|------| | xxx.java | 新增功能 | ### 5.2 修改文件 | 文件路径 | 修改内容 | |---------|---------| | xxx.java | 方法A新增、方法B修改 | ### 5.3 删除文件 | 文件路径 | 删除原因 | |---------|---------| | xxx.java | 功能废弃 | --- ## 6. 非功能需求 ### 6.1 性能要求 - 响应时间：< xxx ms - 并发能力：xxx TPS ### 6.2 安全要求 - 权限校验：是/否 - 数据加密：是/否 ### 6.3 兼容性要求 - 向前兼容：是/否 - 多数据库支持：MySQL/Oracle/PostgreSQL --- ## 7. 测试要点 ### 7.1 功能测试 - [ ] 测试点1 - [ ] 测试点2 ### 7.2 异常测试 - [ ] 测试点1 - [ ] 测试点2 ### 7.3 边界测试 - [ ] 测试点1 - [ ] 测试点2 ### 7.4 回归测试 - [ ] 测试点1（确认原有功能不受影响） - [ ] 测试点2 --- ## 8. 上线注意点 - [ ] 注意点1 - [ ] 注意点2 --- ## 9. 变更记录 | 版本 | 日期 | 变更内容 | 作者 | |------|------|----------|------| | v2.4.0.0 | 2026-04-30 | 初始版本 | xxx | ``` --- ## 第4步：代码检测（P0/P1快速扫描） 在进入细粒度审查前，先快速扫描严重缺陷和代码缺陷。 ### P0严重缺陷 | 检查项 | 检测模式 | 扣分 | |--------|----------|------| | 空指针 | `$OBJ.$METHOD()` obj未检查 | -10 | | SQL注入 | `"SELECT " + $TABLE` 直接拼接 | -10 | | 密钥泄露 | `password/token = "xxx"` | -10 | | 事务缺失 | @Transactional 关键方法无事务 | -10 | ### P1代码缺陷 | 检查项 | 检测模式 | 扣分 | |--------|----------|------| | 资源泄漏 | new FileInputStream 未关闭 | -5 | | 线程安全 | private static 共享变量 | -5 | | 空catch | catch块为空或仅打印日志 | -5 | | 循环查询 | for循环内数据库操作 | -5 | ### P2代码规范（快速扫描） 以下为不与第5步细粒度审查清单重叠的独立检查项： | 检查项 | 检测模式 | 扣分 | |--------|----------|------| | 参数过多 | 方法参数超过3个 | -2 | | 魔法数字 | 字面量未定义为常量 | -2 | | 原始泛型 | List/Map 未指定类型参数 | -2 | > **注意**：Streams滥用、Boolean参数、Optional.get()、equals缺hashCode、catch Exception 等 P2 检查项已整合到第5步对应分类中，此处不再重复。 --- ## 第5步：细粒度代码审查清单 (Detailed Review Checklist) ### 审查策略 1. **Quick scan** - 理解变更意图，确定审查范围 2. **Checklist pass** - 按以下分类逐项检查 3. **Summary** - 按严重度汇总（Critical → Improvement → Minor） ### A. Null 安全 ```java // ❌ NPE 风险：链式调用不检查中间null String name = user.getName().toUpperCase(); // ✅ 安全：Optional + map String name = Optional.ofNullable(user.getName()) .map(String::toUpperCase) .orElse(""); // ✅ 也可以：提前返回 if (user.getName() == null) { return ""; } return user.getName().toUpperCase(); ``` **检查项：** - [ ] 链式方法调用是否缺少null检查 - [ ] 公共API缺少 `@Nullable` / `@NonNull` 注解 - [ ] `Optional.get()` 没有 `isPresent()` 检查——**改用 `orElseThrow()`** - [ ] 方法返回 `null` 而非 `Optional` 或空集合 - [ ] 对集合入参做null检查 → 使用 `Objects.requireNonNull()` 🔧 **检测方法：** - `grep`: `Optional\.get\(\)` → 检查是否有 `isPresent()` 先导 - `ast_grep_search`: `$OBJ.$METHOD()` pattern 查找链式调用中缺少 null 守卫 - `lsp_find_references`: 追踪方法返回 null 的调用方 **建议：** - 返回值可能为空时，使用 `Optional<T>` 替代 `null` - 构造/方法入参使用 `Objects.requireNonNull()` - 返回空集合用 `Collections.emptyList()` 替代 `null` ### B. 异常处理 ```java // ❌ 吞掉异常（最坏实践） try { process(); } catch (Exception e) { // 没有日志，没有处理 } // ❌ 捕获范围过宽 catch (Exception e) { } catch (Throwable t) { } // ❌ 丢失原始异常栈（不传cause） catch (IOException e) { throw new RuntimeException(e.getMessage()); // 丢失堆栈！ } // ✅ 正确处理 catch (IOException e) { log.error("处理文件失败: {}", filename, e); throw new ProcessingException("文件处理失败", e); // 传递cause } ``` **检查项：** - [ ] 空 catch 块 - [ ] 捕获范围过宽（`Exception`, `Throwable`） - [ ] 丢失原始异常（未链式传递 cause） - [ ] 用异常做流程控制 - [ ] 受检异常泄露到API边界 🔧 **检测方法：** - `ast_grep_search`: `catch (Exception` 和 `catch (Throwable` → 宽泛捕获 - `grep`: `catch\s*\(` 带上下文行查找空 catch 块 - `grep`: `throw new.*getMessage\(\)` → 丢失原始异常栈 **建议：** - 日志同时记录**上下文 + 完整栈信息** - 使用具体的异常子类 - 领域错误使用自定义异常 ### C. Streams & 集合操作 ```java // ❌ 遍历中修改集合 → ConcurrentModificationException for (Item item : items) { if (item.isExpired()) { items.remove(item); } } // ✅ 使用 removeIf items.removeIf(Item::isExpired); // ❌ 简单逻辑滥用Stream list.stream().forEach(System.out::println); // ✅ 简单场景直接用for循环 for (Item item : list) { System.out.println(item); } // ❌ 假设 Collectors.toList() 返回可变List List<String> names = users.stream() .map(User::getName) .collect(Collectors.toList()); names.add("extra"); // 可能不可变！ // ✅ 显式要求可变集合 List<String> names = users.stream() .map(User::getName) .collect(Collectors.toCollection(ArrayList::new)); ``` **检查项：** - [ ] 遍历时修改集合（用 `removeIf()` 替代） - [ ] 简单操作用Stream替代for循环（可读性反而差） - [ ] `Collectors.toList()` 假定返回可变列表 - [ ] 未使用 `List.of()`, `Set.of()`, `Map.of()` 创建不可变集合 - [ ] 不理解就乱用并行流（parallelStream） 🔧 **检测方法：** - `ast_grep_search`: `.stream().forEach` → 可能的滥用模式 - `grep`: `\.remove\(` 在 `for\s*\(` 块内 → 遍历时修改 - `grep`: `\.collect\(Collectors\.toList\(\)\)` → 检查后续是否有 add/remove 操作 **建议：** - 防御性拷贝用 `List.copyOf()` - Stream 用于**数据转换**，循环用于**副作用操作** - 简单逻辑（1-2行映射）用Stream，复杂逻辑用for循环 ### D. 并发安全 ```java // ❌ 非线程安全的集合 private Map<String, User> cache = new HashMap<>(); // ✅ 线程安全集合 private Map<String, User> cache = new ConcurrentHashMap<>(); // ❌ check-then-act 竞态条件 if (!map.containsKey(key)) { map.put(key, computeValue()); // 多线程下可能重复计算 } // ✅ 原子操作 map.computeIfAbsent(key, k -> computeValue()); // ❌ 双重检查锁（没有volatile则broken） if (instance == null) { synchronized(this) { if (instance == null) { instance = new Instance(); // 可能看到半初始化对象 } } } // ✅ 使用静态内部类 / volatile private volatile Instance instance; ``` **检查项：** - [ ] 共享可变状态缺少同步 - [ ] check-then-act 操作缺少原子性 - [ ] 共享变量缺少 `volatile` - [ ] 在非final对象上同步 - [ ] 线程不安全的懒初始化 🔧 **检测方法：** - `grep`: `private static.*Map|List|Set` → 静态共享集合 - `ast_grep_search`: `synchronized\(` → 检查同步对象是否为 final - `grep`: `if \(.*== null\)` 在 synchronized 块内 → 双重检查锁 **建议：** - 优先使用不可变对象 - 优先用 `java.util.concurrent` 并发集合类 - 简单场景用 `AtomicReference`, `AtomicInteger` - 考虑添加 `@ThreadSafe` / `@NotThreadSafe` 注解 ### E. Java 惯用法 (equals/hashCode/toString/Builder) **equals & hashCode:** ```java // ❌ 只重写equals不重写hashCode → 破坏HashMap契约！ @Override public boolean equals(Object o) { ... } // hashCode 缺失！ // ❌ hashCode用了可变字段 → 放入HashMap后字段变了就找不到了 @Override public int hashCode() { return Objects.hash(id, mutableField); // 危险！ } // ✅ 同时重写，只用不可变字段 @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User user)) return false; return Objects.equals(id, user.id); } @Override public int hashCode() { return Objects.hash(id); } ``` **toString:** ```java // ❌ 包含敏感数据 return "User{password='" + password + "'}"; // ✅ 调试友好但安全 return "User{id=" + id + ", name='" + name + "'}"; ``` **Builder模式:** ```java // ✅ 当构造参数 > 3 时推荐 User user = User.builder() .name("张三") .email("[REDACTED_SECRET]") .build(); ``` **检查项：** - [ ] `equals` 有但 `hashCode` 缺失 → **确定要破坏HashMap/HashSet吗？** - [ ] `hashCode` 使用了可变字段 - [ ] 领域对象缺少 `toString`（调试困难） - [ ] 构造参数 > 3-4 → 建议Builder - [ ] 未使用 Java 16+ `instanceof` pattern matching 🔧 **检测方法：** - `ast_grep_search`: `public boolean equals` → 检查同文件是否有 `public int hashCode` - `grep`: `Objects\.hash\(.*this\.` → hashCode 使用了实例字段（可能可变） - `grep`: `password|secret|token` 在 `toString\(\)` 方法内 → 敏感数据泄露 ### F. 资源管理 (Try-with-resources) ```java // ❌ 资源泄漏 FileInputStream fis = new FileInputStream(file); // ... 抛异常前没close // ✅ try-with-resources try (FileInputStream fis = new FileInputStream(file)) { // 自动关闭 } // ❌ 多个资源嵌套，可能内层未关闭 try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { // 如果 BufferedWriter 构造失败，FileWriter 可能泄漏 } // ✅ 独立声明 try (FileWriter fw = new FileWriter(file); BufferedWriter writer = new BufferedWriter(fw)) { // 两者都被保证关闭 } ``` **检查项：** - [ ] `Closeable`/`AutoCloseable` 未使用 try-with-resources - [ ] 数据库连接/Statement/ResultSet 未正确关闭 - [ ] 嵌套资源声明顺序错误 🔧 **检测方法：** - `grep`: `new (File|Input|Output|Reader|Writer|Connection|Statement)` → 检查是否在 try-with-resources 内 - `ast_grep_search`: `new $CLOSEABLE($$$)` 不在 `try (` 块内 → 资源泄漏风险 **强制规则：任何实现了 `Closeable`/`AutoCloseable` 的资源，必须使用 try-with-resources。** ### G. API 设计 ```java // ❌ Boolean参数 → 调用端看不懂语义 process(data, true, false); // ✅ 使用枚举 process(data, ProcessMode.ASYNC, ErrorHandling.STRICT); // ❌ 找不到返回null (调用方不知道) public User findById(Long id) { return users.get(id); // 可能null } // ✅ 语义明确的 Optional public Optional<User> findById(Long id) { return Optional.ofNullable(users.get(id)); } // ❌ 入参允许null → 调用方不知道能不能传null public void process(List<Item> items) { if (items == null) items = Collections.emptyList(); } // ✅ 明确要求非null public void process(List<Item> items) { Objects.requireNonNull(items, "items must not be null"); } ``` **检查项：** - [ ] Boolean参数 → 建议用枚举替代 - [ ] 方法参数 > 3 → 考虑参数对象 - [ ] 同一模块内null处理不一致 - [ ] 公共API入参缺少校验（`Objects.requireNonNull`） 🔧 **检测方法：** - `ast_grep_search`: `$VISIBILITY $RET $NAME(boolean $PARAM` → Boolean 参数 - `grep`: `public.*\(.*,.*,.*,.*,` → 参数超过3个的方法签名 - `grep`: `Objects\.requireNonNull` → 检查公共方法是否都有入参校验 ### H. 性能注意事项 ```java // ❌ 循环内String拼接 → 每次创建新String对象 String result = ""; for (String s : strings) { result += s; } // ✅ StringBuilder StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append(s); } // ❌ 循环内编译正则 → 每次都重新编译 for (String line : lines) { if (line.matches("pattern.*")) { } } // ✅ 预编译为静态常量 private static final Pattern PATTERN = Pattern.compile("pattern.*"); for (String line : lines) { if (PATTERN.matcher(line).matches()) { } } // ❌ N+1 查询 for (User user : users) { List<Order> orders = orderRepo.findByUserId(user.getId()); } // ✅ 批量查询 Map<Long, List<Order>> ordersByUser = orderRepo.findByUserIds(userIds); ``` **检查项：** - [ ] 循环内 String 拼接 → 用 StringBuilder - [ ] 循环内正则编译 → 预编译为 Pattern 常量 - [ ] N+1 查询模式 - [ ] 循环内创建可复用对象 - [ ] 未使用原始类型Stream（`IntStream`, `LongStream`） 🔧 **检测方法：** - `grep`: `\+=` 在 `for\s*\(` 块内 → 循环内 String 拼接 - `grep`: `\.matches\(` 在 `for\s*\(` 块内 → 循环内正则编译 - `ast_grep_search`: `for.*\{.*\.(select|find|query)` → N+1 查询模式 ### I. 测试提示 建议为以下场景补充测试： - [ ] Null入参 - [ ] 空集合入参 - [ ] 边界值 - [ ] 异常路径 - [ ] 并发访问（如适用） 🔧 **检测方法：** - `glob`: `**/src/test/**/*Test*.java` → 检查是否有对应测试文件 - `grep`: `@Test` → 统计测试方法数量 vs 变更方法数量 ### J. MyBatis/ORM 检查 ```java // ❌ N+1 查询：循环内逐条查询 for (Order order : orders) { List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId()); order.setItems(items); } // ✅ 批量查询 + 内存组装 List<Long> orderIds = orders.stream().map(Order::getId).collect(Collectors.toList()); List<OrderItem> allItems = orderItemMapper.selectByOrderIds(orderIds); Map<Long, List<OrderItem>> itemsMap = allItems.stream() .collect(Collectors.groupingBy(OrderItem::getOrderId)); orders.forEach(o -> o.setItems(itemsMap.getOrDefault(o.getId(), Collections.emptyList()))); // ❌ 分页插件滥用：先查全部再内存分页 List<User> allUsers = userMapper.selectAll(); List<User> page = allUsers.subList(offset, offset + limit); // ✅ 使用分页插件 PageHelper.startPage(pageNum, pageSize); List<User> users = userMapper.selectByCondition(condition); ``` **检查项：** - [ ] XML映射ID与方法名不一致 - [ ] N+1 查询模式（循环内调用 Mapper） - [ ] 分页插件滥用（全量查询后内存分页） - [ ] 一级缓存/二级缓存不当使用（脏读风险） - [ ] `<if test>` 条件缺少对空字符串的判断 - [ ] `#{}` 与 `${}` 混用（`${}` 有 SQL 注入风险） 🔧 **检测方法：** - `grep`: `<select|<update|<insert|<delete` 在 `*.xml` → 检查 id 命名是否与方法名一致 - `ast_grep_search`: `for.*\{.*Mapper\.|for.*\{.*mapper\.` → N+1 查询模式 - `grep`: `PageHelper\.startPage` → 检查是否紧跟 Mapper 调用（分页生效） - `grep`: `\$\{` 在 `*.xml` → SQL 注入风险（应使用 `#{}`） ### K. 事务边界检查 ```java // ❌ 事务传播级别错误：REQUIRES_NEW 导致外层事务回滚失效 @Transactional(propagation = Propagation.REQUIRED) public void processOrder(Order order) { orderMapper.update(order); paymentService.pay(order); // 内部 REQUIRES_NEW，pay 成功但外层回滚 } // ❌ 同类方法调用事务失效（Spring AOP 代理限制） public void batchProcess(List<Order> orders) { for (Order order : orders) { this.processSingle(order); // @Transactional 不生效！ } } // ✅ 注入自身代理或拆分到不同类 @Transactional public void processSingle(Order order) { ... } // ❌ 长事务导致锁竞争 @Transactional public void exportReport() { // 大量查询 + 文件生成 + 邮件发送 都在一个事务内 List<Data> data = dataMapper.selectAll(); // 持有数据库连接 File file = generateExcel(data); // 耗时操作 emailService.send(file); // 外部IO } ``` **检查项：** - [ ] 事务传播级别错误（`REQUIRES_NEW` 嵌套回滚边界） - [ ] 同类方法调用导致事务失效 - [ ] 长事务（事务内包含外部IO、大量计算） - [ ] `@Transactional` 仅用于 public 方法（非 public 不生效） - [ ] 事务内捕获异常未重新抛出（吞掉回滚） - [ ] `readOnly=true` 未设置（查询方法应标记只读） 🔧 **检测方法：** - `grep`: `@Transactional` → 检查 `propagation` 属性 - `grep`: `REQUIRES_NEW` → 嵌套事务回滚边界 - `grep`: `@Transactional` 在非 public 方法上 → 事务不生效 - `ast_grep_search`: `@Transactional.*\{.*(?:send|http|file|sleep)` → 长事务嫌疑 ### L. SQL/DDL 质量检查 ```sql -- ❌ 缺索引：大表无索引导致全表扫描 CREATE TABLE t_order ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, order_no VARCHAR(64) NOT NULL, status TINYINT, create_time DATETIME ) ENGINE=InnoDB; -- 缺少 user_id 和 order_no 索引！ -- ✅ 合理索引 CREATE TABLE t_order ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, order_no VARCHAR(64) NOT NULL, status TINYINT DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_id (user_id), UNIQUE KEY uk_order_no (order_no), KEY idx_status_create (status, create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -- ❌ 字段类型不当 ALTER TABLE t_order ADD COLUMN amount VARCHAR(32); -- 金额应用 DECIMAL -- ✅ 正确类型 ALTER TABLE t_order ADD COLUMN amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额'; ``` **检查项：** - [ ] 缺索引（WHERE/JOIN/ORDER BY 字段无索引） - [ ] 字段类型不当（金额用 VARCHAR、状态用 VARCHAR 而非 TINYINT） - [ ] 缺 COMMENT（表、字段无注释） - [ ] 字符集不一致（表间 JOIN 字段字符集不同） - [ ] DDL 缺少回滚脚本 - [ ] VARCHAR 长度不合理（过长或过短） - [ ] 缺默认值（NOT NULL 字段无 DEFAULT） 🔧 **检测方法：** - `grep`: `CREATE TABLE` 无 `COMMENT` → 缺表注释 - `grep`: `ALTER TABLE` → 检查是否有对应回滚脚本 - `grep`: `VARCHAR\(` → 检查长度是否合理（如 VARCHAR(1) 或 VARCHAR(5000)） - `grep`: `amount|price|money.*VARCHAR` → 金额字段类型不当 ### 严重度分级参考 | 严重度 | 标准 | 对应评分 | |--------|------|----------| | **Critical** | 安全漏洞、可能丢失数据、生产环境必崩 | P0 (-10) | | **High** | 大概率Bug、严重性能问题、破坏API契约 | P1 (-5) | | **Medium** | 代码坏味、可维护性问题、缺少最佳实践 | P2 (-2) | | **Low** | 风格问题、微优化、建议性 | P2 (-2) | --- ## 第6步：多维度评分系统 ### 评分维度 | 维度 | 权重 | 计算方式 | |------|------|----------| | 代码质量 | 20% | (100 - 扣分) * 权重 | | 安全性 | 25% | (100 - P0问题*10) * 权重 | | 性能 | 20% | (100 - 性能问题*8) * 权重 | | 可维护性 | 15% | (100 - 规范问题*5) * 权重 | | 业务风险 | 20% | (100 - 风险问题*10) * 权重 | ### 扣分规则 | 问题类型 | 扣分 | |--------|------| | P0问题 | -10分/个 | | P1问题 | -5分/个 | | P2建议 | -2分/个 | ### 最终评级 | 等级 | 分数 | 颜色 | 行动 | |------|------|------|------| | S | 90-100 | 🟢 绿 | 可合并 | | A | 80-89 | 🟢 绿 | 可合并 | | B | 70-79 | 🟡 黄 | 需要确认 | | C | 60-69 | 🟠 橙 | 需要修改 | | D | <60 | 🔴 红 | 拒绝合并 | --- ## 第7步：Review报告生成 ```markdown # 代码评审报告 ## 基本信息 - 项目名称：xxx - 评审范围：xxx → xxx - 变更文件数：N - 变更行数：+X / -Y ## 📊 质量评分 | 维度 | 得分 | 评级 | |------|------|------| | 代码质量 | 85 | A | | 安全性 | 90 | S | | 性能 | 80 | A | | 可维护性 | 75 | B | | 业务风险 | 70 | B | | **总分** | **82** | **A** | ## 📋 需求汇总 ### 需求1：[功能点A] - 涉及文件：XxxService.java (新增100行) - 变更类型：新增功能 - 入口：XxxController.create() (POST /api/xxx) - 核心链路： ``` [入口] XxxController.create() ↓ XxxService.process() [核心] XxxHandler.handle() ← 变更点 ↓ XxxRepository.save() [终点] 数据库持久化 ``` - 风险等级：🟡 中 ### 需求2：[功能点B] - 涉及文件：XxxServiceImpl.java, XxxController.java - 变更类型：新增功能 - 入口：POST /api/xxx/action - 核心链路： ``` [入口] XxxController.action() [核心] XxxServiceImpl.action() ← 变更点 ↓ 逻辑处理 [终点] 数据库更新 ``` - 风险等级：🟡 中 ## 🔴 严重问题 (P0) - [ ] 问题1 - 文件：xxx.java - 行号：xxx - 修复方案 ## 🟡 中等问题 (P1) - [ ] 问题1 - 文件：xxx.java - 优化方案 ## 🟢 建议优化 (P2) - [ ] 优化1 ## 评审结论 - 总体评级：S/A/B/C/D - 是否可以合并：是/需修改/拒绝 ``` --- ## 输出 1. **Review报告** → `code-review-report.md`（含评分 + 业务分析 + 细粒度代码检查） 2. **PRD文档** → `prd-{版本号}.md` 3. **控制台摘要** → 评分+问题统计 --- ## Review 执行流程总结 ``` Trigger: "review代码" / "检查PR" / "代码评审" Step 1: git diff 获取变更 ├─ 确定基准分支（询问用户 / 自动检测） ├─ 支持 --cached（仅暂存区）/ 分支对比 ├─ 统计变更文件和行数 └─ 识别变更类型（新增/修改/删除） Step 2: 上下文分析 ├─ lsp_find_references 追踪调用链 ├─ 确定业务入口（Controller/MQ/定时任务/Feign） └─ 推断业务逻辑 Step 3: PRD文档生成（覆盖所有变更点） ├─ 功能清单（含文件+行号） ├─ 核心链路图 ├─ 数据库变更 └─ 接口设计 Step 4: 代码检测（P0/P1快速扫描） ├─ P0严重缺陷（NPE/SQL注入/密钥/事务） ├─ P1代码缺陷（资源泄漏/线程安全/空catch/循环查询） └─ P2代码规范（参数过多/魔法数字/原始泛型） Step 5: 细粒度审查清单（12类深度检查） ├─ A. Null安全 ├─ B. 异常处理 ├─ C. Streams & 集合 ├─ D. 并发安全 ├─ E. Java惯用法 (equals/hashCode) ├─ F. 资源管理 (try-with-resources) ├─ G. API设计 ├─ H. 性能 ├─ I. 测试提示 ├─ J. MyBatis/ORM 检查 ├─ K. 事务边界检查 └─ L. SQL/DDL 质量检查 Step 6: 多维度评分 ├─ 代码质量 20% ├─ 安全性 25% ├─ 性能 20% ├─ 可维护性 15% └─ 业务风险 20% Step 7: 生成Review报告 ├─ 基本信息 ├─ 质量评分（含维度拆分） ├─ 需求汇总（含调用链路） ├─ 问题清单（Critical/High/Medium/Low） └─ 评审结论 ``` --- ## PRD生成 Checklist 生成PRD时，**每个变更点都必须包含**： - [ ] 功能名称 - [ ] 功能类型（新增/修改/删除/优化） - [ ] 涉及文件和行号 - [ ] 核心业务链路 - [ ] 入参/出参说明 - [ ] 异常处理 - [ ] 数据库变更（如有） - [ ] 测试要点


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)