Java Code Review - Java项目代码评审工具
概述
智能化Java项目代码评审工具,完整流程(7步):
- Git Diff 变更获取 - 拉取代码变更内容(支持 --cached / 分支对比)
- 完整上下文分析 - 分析调用链路,推断业务逻辑
- PRD文档生成 - 覆盖所有变更点(新增+修改)
- 代码检测(P0/P1快速扫描) - 严重缺陷和代码缺陷快速定位
- 细粒度代码审查清单 - 12类深度检查(A-L):Null安全、异常处理、Streams、并发、Java惯用法、资源管理、API设计、性能、测试、MyBatis/ORM、事务边界、SQL/DDL质量
- 多维度评分 - 代码质量评分
- Review报告生成 - 输出结构化报告
何时使用此技能
- 用户需要进行代码评审
- 用户需要理解变更的业务含义
- 用户需要生成完整PRD文档
- 用户需要评分和质量评估
- 用户说 "review代码" / "检查PR" / "代码review" / "检查下代码"
第1步:Git Diff 变更获取
1.1 确定基准分支
优先询问用户,如用户未指定则使用以下策略:
- 当前分支有上游跟踪 → 使用
@{u}(上游分支)
- 存在
develop 分支 → 使用 develop
- 存在
main/master 分支 → 使用 main/master
- 否则 → 使用
HEAD~10(最近10次提交)
1.2 获取变更
# 场景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文档结构
# 产品需求文档 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
// 原代码
修改后:
// 新代码
变更原因:
[为什么要这样修改]
核心链路:
[入口] Controller.method() [POST /api/xxx]
↓ Service.method()
[核心] 修改的方法 ← 变更内容
↓ DAO.method()
[终点] 数据库/外部服务
影响分析:
- 影响的上游:xxx
- 影响的下游:xxx
- 兼容性:是否向前兼容
- 数据库:是否需要迁移
2.2.3 [功能点C-优化功能]
功能类型:优化
优化前:
[优化前的描述]
优化后:
[优化后的描述]
优化效果:
3. 数据库变更
3.1 新增表
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 修改表
-- 修改字段长度
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 |
请求参数:
{
"ids": ["xxx"],
"operator": "xxx"
}
响应参数:
{
"code": "0",
"message": "成功",
"data": null
}
4.2 修改接口
| 接口名称 | 请求方式 | 路径 | 说明 |
|---|
| 接口A | POST | /api/xxx | 参数调整 |
变更说明:
[描述接口参数变更]
5. 涉及的代码变更
5.1 新增文件
5.2 修改文件
| 文件路径 | 修改内容 |
|---|
| xxx.java | 方法A新增、方法B修改 |
5.3 删除文件
6. 非功能需求
6.1 性能要求
- 响应时间:< xxx ms
- 并发能力:xxx TPS
6.2 安全要求
6.3 兼容性要求
- 向前兼容:是/否
- 多数据库支持:MySQL/Oracle/PostgreSQL
7. 测试要点
7.1 功能测试
7.2 异常测试
7.3 边界测试
7.4 回归测试
8. 上线注意点
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();
检查项:
🔧 检测方法:
grep: Optional\.get\(\) → 检查是否有 isPresent() 先导
ast_grep_search: $OBJ.$METHOD() pattern 查找链式调用中缺少 null 守卫
lsp_find_references: 追踪方法返回 null 的调用方
建议:
- 返回值可能为空时,使用
Optional<T> 替代 null
- 构造/方法入参使用
Objects.requireNonNull()
- 返回空集合用
Collections.emptyList() 替代 null
B. 异常处理
// ❌ 吞掉异常(最坏实践)
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
}
检查项:
🔧 检测方法:
ast_grep_search: catch (Exception 和 catch (Throwable → 宽泛捕获
grep: catch\s*\( 带上下文行查找空 catch 块
grep: throw new.*getMessage\(\) → 丢失原始异常栈
建议:
- 日志同时记录上下文 + 完整栈信息
- 使用具体的异常子类
- 领域错误使用自定义异常
C. Streams & 集合操作
// ❌ 遍历中修改集合 → 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));
检查项:
🔧 检测方法:
ast_grep_search: .stream().forEach → 可能的滥用模式
grep: \.remove\( 在 for\s*\( 块内 → 遍历时修改
grep: \.collect\(Collectors\.toList\(\)\) → 检查后续是否有 add/remove 操作
建议:
- 防御性拷贝用
List.copyOf()
- Stream 用于数据转换,循环用于副作用操作
- 简单逻辑(1-2行映射)用Stream,复杂逻辑用for循环
D. 并发安全
// ❌ 非线程安全的集合
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;
检查项:
🔧 检测方法:
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:
// ❌ 只重写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:
// ❌ 包含敏感数据
return "User{password='" + password + "'}";
// ✅ 调试友好但安全
return "User{id=" + id + ", name='" + name + "'}";
Builder模式:
// ✅ 当构造参数 > 3 时推荐
User user = User.builder()
.name("张三")
.email("zhangsan@example.com")
.build();
检查项:
🔧 检测方法:
ast_grep_search: public boolean equals → 检查同文件是否有 public int hashCode
grep: Objects\.hash\(.*this\. → hashCode 使用了实例字段(可能可变)
grep: password|secret|token 在 toString\(\) 方法内 → 敏感数据泄露
F. 资源管理 (Try-with-resources)
// ❌ 资源泄漏
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)) {
// 两者都被保证关闭
}
检查项:
🔧 检测方法:
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 设计
// ❌ 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");
}
检查项:
🔧 检测方法:
ast_grep_search: $VISIBILITY $RET $NAME(boolean $PARAM → Boolean 参数
grep: public.*\(.*,.*,.*,.*, → 参数超过3个的方法签名
grep: Objects\.requireNonNull → 检查公共方法是否都有入参校验
H. 性能注意事项
// ❌ 循环内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);
检查项:
🔧 检测方法:
grep: \+= 在 for\s*\( 块内 → 循环内 String 拼接
grep: \.matches\( 在 for\s*\( 块内 → 循环内正则编译
ast_grep_search: for.*\{.*\.(select|find|query) → N+1 查询模式
I. 测试提示
建议为以下场景补充测试:
🔧 检测方法:
glob: **/src/test/**/*Test*.java → 检查是否有对应测试文件
grep: @Test → 统计测试方法数量 vs 变更方法数量
J. MyBatis/ORM 检查
// ❌ 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);
检查项:
🔧 检测方法:
grep: <select|<update|<insert|<delete 在 *.xml → 检查 id 命名是否与方法名一致
ast_grep_search: for.*\{.*Mapper\.|for.*\{.*mapper\. → N+1 查询模式
grep: PageHelper\.startPage → 检查是否紧跟 Mapper 调用(分页生效)
grep: \$\{ 在 *.xml → SQL 注入风险(应使用 #{})
K. 事务边界检查
// ❌ 事务传播级别错误: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
}
检查项:
🔧 检测方法:
grep: @Transactional → 检查 propagation 属性
grep: REQUIRES_NEW → 嵌套事务回滚边界
grep: @Transactional 在非 public 方法上 → 事务不生效
ast_grep_search: @Transactional.*\{.*(?:send|http|file|sleep) → 长事务嫌疑
L. SQL/DDL 质量检查
-- ❌ 缺索引:大表无索引导致全表扫描
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 '订单金额';
检查项:
🔧 检测方法:
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报告生成
# 代码评审报告
## 基本信息
- 项目名称: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
- 是否可以合并:是/需修改/拒绝
输出
- Review报告 →
code-review-report.md(含评分 + 业务分析 + 细粒度代码检查)
- PRD文档 →
prd-{版本号}.md
- 控制台摘要 → 评分+问题统计
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时,每个变更点都必须包含: