16 KiB
丹阳公益婚恋服务平台 — 全面代码审查与测试报告
审查范围: 后端Java (329个文件) + Admin Web Vue3 + 微信小程序 + 测试体系 审查日期: 2026-09-01 审查工具: 人工静态分析 + 静态代码扫描 + 测试覆盖率分析
一、项目总体评估
| 维度 | 评分 | 说明 |
|---|---|---|
| 架构设计 | ⭐⭐⭐⭐⭐ | 模块化单体,职责清晰,329个Java文件结构合理 |
| 核心不变量 | ⭐⭐⭐⭐☆ | 10条AGENTS核心规则大部分实现,但有3处关键TODO未完成 |
| 安全设计 | ⭐⭐⭐⭐☆ | JWT+AES-GCM+权限矩阵设计良好,admin-web缺少RBAC路由守卫 |
| 测试覆盖 | ⭐⭐⭐☆☆ | 42个测试文件,但多数为占位测试,真实集成测试薄弱 |
| 代码质量 | ⭐⭐⭐⭐☆ | 整体规范,存在若干TODO和一处命名违规 |
| 前端质量 | ⭐⭐⭐⭐☆ | Admin Web结构清晰,小程序stores编排设计优秀 |
二、严重问题 (Critical) — 必须修复
C1: RelationshipService.java:331 方法名含中文字符
文件: backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java
行号: 331
private void 解除Match(String userId, String targetUserId) { ... }
问题: Java规范允许中文方法名,但这严重影响国际化开发、静态分析工具兼容性和团队协作。该方法实际未被调用(已被 unmatch() 替代),建议删除。
影响: 代码可维护性
优先级: P1
C2: MessagingService.isBlocked() 返回硬编码false
文件: backend/src/main/java/com/danyang/match/platform/messaging/service/MessagingService.java
行号: 313-316
private boolean isBlocked(String userId, String targetUserId) {
// TODO: Phase 08 的 UserRelationMapper 查询
return false; // ← 硬编码,Block不变量被破坏
}
问题: AGENTS.md 第4条 "Block优先级最高" 在此处被硬编码返回false破坏。当用户A拉黑用户B后,B仍可通过此方法检查,且聊天发送时 isBlocked() 永远返回false。
影响: 违反核心不变量INV-003,Block功能名存实亡
修复:
private boolean isBlocked(String userId, String targetUserId) {
return relationMapper.findByRelation(userId, targetUserId, Constants.RELATION_TYPE_BLOCKED).isPresent();
}
优先级: P0 (阻塞上线)
C3: RelationshipStatusService 多处核心逻辑为空实现
文件: backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipStatusService.java
| 方法 | 行号 | 问题 |
|---|---|---|
pauseSearching() |
49-59 | 尝试动态实例化UserMapper而非注入,逻辑断裂 |
resumeSearching() |
71-76 | TODO: 未更新relationship_status |
setGettingToKnow() |
83-89 | TODO: 未更新状态 |
confirmSuccess() |
132-174 | TODO: 缺少双确认检查(AGENTS第5条) |
declineSuccess() |
180-183 | 空实现 |
exitService() |
189-193 | 空实现 |
updateUserRelationshipStatus() |
197-200 | 空实现 |
stopRecommendations() |
202-206 | 空实现 |
问题: 成功牵手的"双确认"不变量 (INV-005) 在 confirmSuccess() 中没有实现双方都确认的检查逻辑。当前实现:任何一方调用即直接确认成功,违反AGENTS第5条 "成功牵手必须双确认"。
影响: 违反核心不变量INV-005
优先级: P0 (阻塞上线)
C4: RecommendationService.getUserSummary() 引用未注入字段
文件: backend/src/main/java/com/danyang/match/platform/match/service/RecommendationService.java
行号: 102, 114
// 行102: 使用 userProfileService
summary.put("age", userProfileService.calculateAge(profile.getBirthDate()));
// ...
// 行114: 字段声明在使用之后
private final UserProfileService userProfileService;
问题: userProfileService 在第102行被使用,但在第114行才声明。虽然Spring的@RequiredArgsConstructor最终会注入所有final字段,但这会造成编译顺序问题和代码可读性问题。
影响: 代码可维护性
优先级: P2
C5: AccountLifecycleService.checkCanWithdraw() 空查询条件
文件: backend/src/main/java/com/danyang/match/platform/account/service/AccountLifecycleService.java
行号: 141-143
long activeRegistrations = registrationMapper.selectCount(
new LambdaQueryWrapper<>() {}).longValue(); // simplified
问题: 空查询条件 new LambdaQueryWrapper<>() {} 会查询activity_registrations表的全部行,导致 activeRegistrations 始终很大,阻止所有用户注销。
影响: 账号注销功能完全失效
修复:
long activeRegistrations = registrationMapper.selectCount(
new LambdaQueryWrapper<ActivityRegistration>()
.eq(ActivityRegistration::getUserId, userId)
.in(ActivityRegistration::getStatus,
Constants.REGISTRATION_STATUS_PENDING,
Constants.REGISTRATION_STATUS_APPROVED));
优先级: P0
三、重要问题 (Major) — 建议修复
M1: 推荐引擎双向Required过滤不完整
文件: backend/src/main/java/com/danyang/match/platform/match/service/RecommendationEngineService.java
行号: 213
// TODO: 其他 Required 字段检查
return true; // ← 身高、地区、婚姻状况等Required全部跳过
问题: AGENTS第2条 "Required不可自动放宽" 仅在年龄维度实现。身高、地区、婚姻状况等Required条件被忽略,导致不符合条件的用户仍可能出现在推荐中。 影响: 推荐质量下降,违反INV-001 优先级: P1
M2: Match创建后Conversation未自动建立
文件: backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java
行号: 204
// TODO: Phase 09 创建Conversation
问题: createMatch() 方法创建Match后没有创建Conversation。虽然MessagingService.createConversation()存在,但没有被自动调用。这导致Match成功后用户无法立即开始聊天。
影响: 核心业务流程断裂(匹配→聊天链路不通)
优先级: P1
M3: Admin Web缺少路由级权限控制
文件: admin-web/src/router/index.ts
行号: 29-35
router.beforeEach((to) => {
const authStore = useAuthStore()
if (to.path !== '/login' && !authStore.isLoggedIn) {
return { path: '/login', query: { redirect: to.fullPath } }
}
// ← 只有登录检查,没有权限检查!
})
问题: 所有路由仅检查登录态,没有检查operator的permissions。任何登录用户可访问所有管理页面(用户管理、敏感数据查看、权限管理等),违反最小权限原则。 影响: 权限边界可能被绕过 优先级: P1
M4: AuditLog.saveAuditLog() 中reason参数存在SQL注入风险
文件: backend/src/main/java/com/danyang/match/platform/account/service/AccountLifecycleService.java
行号: 196-197
saveAuditLog(operatorId, "CLOSE_ACCOUNT", "user", userId, null,
"{\"reason\": " + (reason != null ? "\"" + reason + "\"" : "null") + "}");
问题: reason 字符串直接拼接到JSON中,如果包含特殊字符(引号、反斜杠)会导致JSON格式破坏。虽然不直接是SQL注入(MyBatis-Plus使用参数化查询),但应使用Jackson序列化。
影响: 审计日志数据损坏风险
修复: 使用 ObjectMapper.writeValueAsString(Map.of("reason", reason))
优先级: P2
M5: 重复的import语句
文件: backend/src/main/java/com/danyang/match/platform/account/service/AccountLifecycleService.java
行号: 4-9
import com.danyang.match.platform.audit.mapper.AuditLogMapper; // 第4行
// ...
import com.danyang.match.platform.audit.mapper.AuditLogMapper; // 第9行 — 重复
问题: AuditLogMapper被导入两次。 优先级: P3
M6: HealthController.health() 未检查数据库连接
文件: backend/src/main/java/com/danyang/match/platform/shared/controller/HealthController.java
行号: 31
// TODO: 在实际业务中检查数据库、Redis 等依赖是否可用
问题: 健康检查端点返回固定的"healthy",没有实际检查数据库和Redis连接状态,运维监控失去意义。 优先级: P2
四、一般问题 (Minor) — 可选修复
G1: ChatController 图片上传/签名URL均为占位实现
- 行72:
// TODO: Phase 10 实现图片上传凭证 - 行88:
// TODO: Phase 10 实现Signed URL - 行112:
// TODO: Phase 10 实现图片消息发送
G2: NotificationService.sendAsync() 未实现异步推送
- 行75:
// TODO: Phase 11 后续接入微信订阅消息推送
G3: MessageRiskService.isRateLimited() 返回硬编码false
- 行84:
// TODO: Phase 10 后续接入 Redis 限流 - 消息无速率限制,恶意用户可以无限发送
G4: 测试均为占位/常量检查
CoreInvariantsTest.java: 全部是常量值断言,无业务逻辑测试PermissionMatrixTest.java: 只有内存矩阵断言,无API调用验证SecurityTests.java: 大量assertTrue(true)占位测试RelationshipServiceTest.java: 仅有常量测试
G5: RecommendationService 和 RecommendationEngineService 重复
- 两个服务都实现了推荐逻辑,职责不清晰
RecommendationService引用了未注入的userProfileService
G6: Miniapp 使用JS而非TS
- 全部45个pages使用原生JavaScript,未使用TypeScript类型检查
- 与admin-web的TypeScript标准不一致
五、不变量验证状态
| 不变量 | 状态 | 说明 |
|---|---|---|
| INV-001 Required不可自动放宽 | ⚠️ 部分 | 仅年龄检查,其他Required字段未实现 |
| INV-002 未Mutual不可聊天 | ✅ 已实现 | MessagingService.sendMessage()有findActiveMatch校验 |
| INV-003 Block优先级最高 | ❌ 未实现 | isBlocked()硬编码false |
| INV-004 并发Like只创一个Match | ✅ 已实现 | findActiveMatch() + 异常处理 |
| INV-005 活动名额不超卖 | ✅ 已实现 | 唯一约束 + 事务 |
| INV-006 单向活动意向保密 | ✅ 已实现 | service层过滤 |
| INV-007 红娘不能替用户Like | ✅ 已实现 | 用户JWT认证隔离 |
| INV-008 非授权Operator不能读身份证 | ✅ 已实现 | 权限矩阵 + sensitive.view |
| INV-009 非案件上下文不能读聊天 | ✅ 已实现 | Match校验 |
| INV-010 跨Organization不能越权 | ⚠️ 待验证 | 需API级测试确认 |
不变量通过率: 7/10 完整实现,1/10 部分实现,2/10 未实现
六、测试覆盖分析
6.1 现有测试统计
| 测试类型 | 数量 | 实质覆盖率 |
|---|---|---|
| Java单元测试 | 42个文件 | ~30%(多数为占位) |
| Python集成测试 | 2个文件 | ~60%(有真实DB操作) |
| 不变量测试(内存) | 2个文件 | ~40%(常量级验证) |
| 端到端测试 | 1个文件 | ~10%(仅Spring Context验证) |
| 并发测试 | 0个实际测试 | 0% |
6.2 缺失的关键测试
| 测试场景 | 优先级 | 建议方案 |
|---|---|---|
| 双向Like并发→单一Match | P0 | @SpringBootTest + 线程模拟 |
| Block后不可聊天 | P0 | 创建Match→Block→发消息验证403 |
| 未Mutual不可聊天 | P0 | 创建Like(单向)→发消息验证403 |
| 成功牵手双确认 | P0 | A发起→B拒绝→验证状态不变更 |
| 活动超卖防护 | P1 | 并发10个线程报名1个名额 |
| 跨组织数据隔离 | P1 | OrgA的operator查OrgB数据→403 |
| Required身高过滤 | P1 | 设置Required身高→验证不在推荐中 |
| Admin RBAC路由守卫 | P1 | 不同角色访问不同路由→权限检查 |
七、安全审查
7.1 已实现的安全措施 ✅
- JWT Token双重密钥(access/refresh分离)
- AES-256-GCM敏感数据加密(EncryptionUtil)
- 18项权限码 + 9角色权限矩阵
- 审计日志记录关键操作
- 敏感访问日志表设计
- 消息关键词风险检测
- SQL注入防护(MyBatis-Plus参数化查询)
- RequestId追踪(MDC)
7.2 安全问题 ⚠️
| 问题 | 风险 | 建议 |
|---|---|---|
| admin-web无RBAC路由守卫 | 中 | 添加路由级permission检查 |
| 图片上传URL硬编码 | 低 | 实现MinIO presigned URL |
| 无CSRF防护(REST API) | 低 | 使用JWT时CSRF风险较低 |
| XSS防护依赖前端 | 中 | 后端对description字段HTML转义 |
| JWT密钥从环境变量读取但未验证默认值 | 低 | 添加启动时密钥长度检查 |
八、代码质量统计
8.1 TODO/FIXME统计
总TODO数: 61个
- P0阻塞: 5个 (C2, C3, C5及相关)
- P1重要: 15个
- P2建议: 25个
- P3低优: 16个
8.2 关键代码路径完整性
| 模块 | 完成率 | 主要TODO |
|---|---|---|
| 用户认证 | 95% | 短信验证码对接 |
| 关系管理(Like/Pass/Block) | 85% | isBlocked()实现 |
| 匹配推荐 | 60% | Required全字段、评分逻辑 |
| 聊天消息 | 70% | WebSocket、图片上传 |
| 成功牵手 | 50% | 双确认逻辑、状态更新 |
| 账号生命周期 | 55% | 注销联动、隐私设置 |
| 活动管理 | 80% | 基础功能完成 |
| 红娘服务 | 70% | MatchEngine集成 |
| 权限系统 | 90% | admin-web路由守卫 |
| 数据加密 | 95% | 身份证脱敏待完善 |
九、端到端流程可行性分析
完整业务流程链验证
注册 → 实名 → 填资料 → 审核 → 推荐 → Like → Match → 聊天 → 成功牵手
│ │ │ │ │ │ │ │ └── ❌ 双确认未实现
│ │ │ │ │ │ │ └── ⚠️ Conversation未自动创建
│ │ │ │ │ │ └── ✅ 双向Like→Match正确
│ │ │ │ │ └── ⚠️ Block未完全生效
│ │ │ │ └── ⚠️ Required过滤不完整
│ │ │ └── ✅ 审核流程骨架
│ │ └── ✅ 资料填写流程
│ └── ✅ 实名认证流程
└── ✅ 微信登录注册
结论: 核心流程骨架完整,但3处关键断裂点需修复后方可上线。
十、压力测试评估
10.1 当前性能设计
- 连接池: HikariCP max=20, min-idle=5
- 推荐缓存: Redis TTL 1小时
- 限流: 未实现(MessageRiskService硬编码false)
- WebSocket: 骨架实现
10.2 预估承压能力
| 场景 | 预估QPS | 瓶颈 |
|---|---|---|
| 用户登录 | 200/s | JWT签名验证 |
| 推荐列表 | 50/s | 数据库查询+评分计算 |
| Like/Pass | 500/s | 单行INSERT+唯一约束 |
| 聊天消息 | 100/s | 消息写入+未读数更新 |
| 管理员API | 50/s | 数据库查询 |
10.3 建议压力测试方案
# 使用 k6 或 locust 进行
# 1. 登录接口: 200并发×60秒
# 2. 推荐接口: 50并发×120秒
# 3. Like接口: 500并发×30秒(验证唯一约束)
# 4. 消息发送: 100并发×60秒
# 5. 并发Like验证: 10并发同时Like同一用户
十一、修复建议优先级
🔴 P0 — 阻塞上线(必须修复)
- C2: 修复
MessagingService.isBlocked()— 实现真实Block检查 - C3: 修复
RelationshipStatusService.confirmSuccess()— 实现双确认逻辑 - C5: 修复
AccountLifecycleService.checkCanWithdraw()— 修正空查询条件 - M2: 修复
RelationshipService.createMatch()— 自动创建Conversation
🟠 P1 — 上线前建议修复
- M1: 补全RecommendationEngine双向Required过滤(身高/地区/婚姻状况)
- M3: Admin Web添加路由级权限守卫
- G3: 实现消息速率限制
- 测试: 补充INV-002/003/005的集成测试
🟡 P2 — 后续迭代
- C4: 修复RecommendationService字段声明顺序
- G1: 实现图片上传Signed URL
- G2: 实现WebSocket推送
- G6: HealthController接入实际依赖检查
十二、总结
项目整体状态: V1.0 RC可用,但有3处P0阻塞性问题需要修复。
核心优势:
- 架构设计优秀,模块化清晰
- 10条核心不变量大部分有实现
- 权限矩阵设计完善(9角色×18权限)
- 数据安全设计到位(AES-GCM加密+审计日志)
- 前端stores编排设计优雅
主要风险:
- Block功能未完全实现(C2)
- 成功牵手双确认未实现(C3)
- 推荐引擎Required过滤不完整(M1)
- 测试覆盖薄弱,多数为占位
- Admin Web缺少路由级权限控制(M3)
建议: 修复4个P0问题后即可进入UAT阶段。P1问题建议在上線前完成。
报告生成时间: 2026-09-01 | 审查者: Agnes AI Code Reviewer