Files
huangping 1333cb38d6 docs: add AGENTS.md, code audit reports, and implementation plans
Added hierarchical AGENTS.md files for root, java, native, services, web modules. Added comprehensive audit reports covering PRD progress, UI audit, full version gap analysis, code audit findings, and ONLYOFFICE status.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:24 +08:00

13 KiB
Raw Permalink Blame History

代码实现审计报告 — PRD vs 实际实现

审计日期: 2026-05-26 审计范围:

  • PRD 文档: chuangfei-platform-product-modules.md (M1-M11), FRONTEND_UI_SPECIFICATION.md, chuangfei-platform-bpm-and-roadmap.md
  • 后端: services/delivery-platform-api/ (153 Java 文件) + services/license-webhook-ingress/
  • 前端: web/delivery-platform-ui/src/ (47 源文件)

1. 严重缺陷 (Critical)

CR-01: 认证系统硬编码用户凭据

位置: AuthController.java:54-89 PRD 对照: M11-F14 要求「用户与账号生命周期:创建、启用/禁用、离职归档」 实际实现: 4 个用户硬编码在 Java switch 语句中:

case "admin"  role=SYS_ADMIN
case "sales"  role=SALES
case "delivery"  role=DELIVERY
case "ops"  role=LICENSE_OPS

缺陷:

  • 密码 = 小写用户名 (pass.equals(user.toLowerCase())) — admin/admin, sales/sales
  • 无数据库用户表 — 无法 CRUD、禁用、归档
  • 注入的 PasswordEncoder (BCrypt) 仅用于 changePassword 端点,登录完全不使用
  • changePassword 验证旧密码时硬编码 passwordEncoder.encode("admin"),对非 admin 用户永远失败

影响: 无法管理用户、密码与用户名相同、安全基线不达标


CR-02: JWT Token 存储在 localStorage

位置: stores/auth.js:4,8,29,37 PRD 对照: §16.6 已知局限明确标注「前端 Token 存 localStorage(非 HttpOnly Cookie)」为已知安全缺陷,计划 Mid 迁移 实际实现: Token 通过 localStorage.setItem(TOKEN_KEY) 持久化

// auth.js:29
localStorage.setItem(TOKEN_KEY, this.token);
axios.defaults.headers.common.Authorization = `Bearer ${this.token}`;

缺陷: XSS 攻击可窃取 localStorage 中的 JWT,获得完整 API 访问权限 影响: 安全 — XSS 窃取 → 权限丢失 缓解: 当前通过 CSP + 前端无富文本渲染降低风险,但未根本解决


CR-03: Error Message 泄露

位置:

  • LicenseController.java:25ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()))
  • ContractController.java:136ResponseEntity.status(500).body(Map.of("error", e.getMessage())) PRD 对照: 无明确的错误消息规范,但全局 ApiExceptionHandler 已返回泛化消息 "服务器内部错误" 缺陷: 这两个 Controller 使用 try-catch 捕获 Exception 并将异常消息原文 (e.getMessage()) 返回给客户端,绕过了全局异常处理器。可能泄露实现细节(表名、SQL、文件路径)。 影响: 信息安全 — 生产环境可能泄露堆栈或内部路径信息

CR-04: resetPassword 和 forceLogout 是空操作

位置: AuthController.java:131-148 PRD 对照: M11-F08「密码重置: 管理员重置密码或邮件/短信重置链接」、M11-F12「管理员强制下线」 实际实现: 两个端点均仅有参数校验,无实际逻辑

// resetPassword — 校验参数后直接返回 200 OK,未更新任何密码
@PostMapping("/admin/reset-password")
public ResponseEntity<Void> resetPassword(@RequestBody Map<String, String> body) {
    String username = body.get("username");
    String newPassword = body.get("newPassword");
    if (username == null || newPassword == null || newPassword.length() < 6) {
        throw new ResponseStatusException(...);
    }
    return ResponseEntity.ok().build();  // 没有实际更新密码!
}

// forceLogout — 同上,无会话失效逻辑
@PostMapping("/admin/force-logout")
public ResponseEntity<Void> forceLogout(@RequestBody Map<String, String> body) {
    String username = body.get("username");
    if (username == null) throw new ResponseStatusException(...);
    return ResponseEntity.ok().build();  // 没有实际使会话失效!
}

影响: 功能完全不可用 — 前端调用后显示成功,实际无效果


2. 高危缺陷 (High)

HI-01: 无用户管理数据库表

位置: AuthController.java (全部) PRD 对照: M11-F14「用户与账号生命周期:创建、启用/禁用、离职归档」— P0 实际:platform_user 表或类似实体。4 个用户硬编码。Flyway 迁移 V15 seed_product_roles.sql 仅涉及角色种子数据。 影响: M11-F14 完全未实现,无法添加/禁用/管理用户


HI-02: 权限模型硬编码

位置: AuthController.java:91-114 PRD 对照: §13.4 要求权限码命名规范(如 customer:project:rwcontract:order:export 实际: 权限字符串在 Java switch 中硬编码,非数据库驱动、不可配置

case "SALES":
    permissions.add("customer:*");
    permissions.add("project:*");
    permissions.add("contract:*");
    permissions.add("delivery:read");
    break;

影响: 新增角色或调整权限需改代码重启;权限码 v-permission 指令在前端存在但后端无对应校验


HI-03: 会话管理后端无状态

位置: SecurityConfig.java:55-56SessionCreationPolicy.STATELESS PRD 对照: M11-F03「空闲超时自动登出」、M11-F11「并发会话策略」 实际: JWT 无状态设计意味着后端无法主动使会话失效(无会话存储)。空闲超时仅在前端实现(idleTimer.js),后端无法强制登出。forceLogout API 为空操作。 影响: 并发会话、强制下线、空闲超时功能均无法在后端层面实现


HI-04: LicenseController 异常处理绕过全局 Handler

位置: LicenseController.java:19-27 PRD 对照: 全局 ApiExceptionHandler 已提供统一错误格式 {status, message} 实际: create 方法手动 try-catch,返回非标准错误格式

try {
    return ResponseEntity.ok(licenseService.create(request));
} catch (Exception e) {
    return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}

返回格式为 {"error": "..."} 而非全局标准的 {"status": 500, "message": "..."} 影响: API 响应格式不一致,前端 apiErrorMessage.js 可能无法解析


3. 中危缺陷 (Medium)

ME-01: 合同附件上传无校验

位置: ContractController.java:118-137 PRD 对照: M2-F05「合同附件:上传扫描件/电子签输出(存储与权限受控)」 实际: 上传端点无文件大小限制、无文件类型白名单、无病毒扫描

@PostMapping("/{id}/attachments")
public ResponseEntity<Map<String, Object>> uploadAttachment(@PathVariable Long id, @RequestParam("file") MultipartFile file) {
    // 无 file.getSize() 校验
    // 无 file.getContentType() 白名单
    // 直接将文件写入本地磁盘

影响: 可能被用于上传恶意文件;磁盘可能被大文件填满


ME-02: 部分 Controller 返回格式不统一

位置: 多文件 PRD 对照: 无明确 API 响应规范 实际: 存在三种返回风格:

  1. 全局 ApiExceptionHandler{status, message} (标准)
  2. LicenseController{error, ...} (非标准)
  3. ContractController{error, ...} (非标准)
  4. 部分端点直接返回实体对象(非 Map

影响: 前端 apiErrorMessage.js 兼容多种格式但无法覆盖所有情况


ME-03: 系统参数仅存于 localStorage

位置: SystemParamsView.vue:14-33 PRD 对照: M11-F20「系统参数」— 期望持久化到后端数据库 实际:

// 直接保存到浏览器 localStorage
localStorage.setItem('systemParams', JSON.stringify(params.value))
ElMessage.success('参数已保存(MVP: 存储于浏览器本地)')

影响: 参数仅对当前浏览器有效,不同用户/设备参数不一致;清除浏览器数据后丢失


ME-04: 后端 changePassword 验证逻辑错误

位置: AuthController.java:151-168 PRD 对照: M11-F07「已登录用户修改本人密码;校验旧密码强度与新密码策略」 实际:

String currentPasswordHash = passwordEncoder.encode("admin");  // 始终比较 admin 的密码!
if (!passwordEncoder.matches(oldPassword, currentPasswordHash)) {
    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误");
}

passwordEncoder.encode("admin") 硬编码为 "admin",导致:

  • admin 用户可以改密(旧密码 = admin 通过)
  • 其他用户(sales/delivery/ops)永远无法通过旧密码验证

ME-05: SN 批量导入缺少事务回滚

位置: LicenseSnService.java:134-155 PRD 对照: M4-F01/F07 批量操作 实际: 逐条插入,失败项跳过继续,但方法未标注 @Transactional

public Map<String, Object> batchImport(List<LicenseSnCreateRequest> requests) {
    // for 循环逐条 insert,无事务保护
    // 部分成功 = 部分写入无法回滚

影响: 批量导入 100 条中第 50 条失败时,前 49 条已写入无法撤销


4. PRD 与实现偏离 (Misalignment)

MA-01: M11 角色模型偏离产品定义

产品定义角色 实际实现 状态
SYS_ADMIN 产品定义包含
SALES I10 新增 产品定义包含
DELIVERY I10 新增 产品定义包含
LICENSE_OPS I10 新增 产品定义包含
ORDER_SUPPORT 产品定义但未实现
FINANCE_VIEW 产品定义但未实现
COMPLIANCE 产品定义但未实现
EXEC_VIEW 产品定义但未实现
SECURITY_ADMIN 产品定义但未实现
DEVELOPER (应废弃) MVP 遗留非标角色
OPS (应废弃) MVP 遗留非标角色

前端路由角色标记(router/index.js)仍广泛使用 SYS_ADMINSALES,但 DEVELOPER 已从路由角色列表中移除,而 LICENSE_OPSDELIVERY 已加入。

MA-02: M1-F07 客户冻结后端就绪前端缺 UI

位置: 后端 CustomerController.javaPATCH /{id}/freeze/unfreeze 端点,前端 CustomersView.vue 无冻结操作入口

MA-03: M11-F07 密码修改已实现但产品文档未标记

ProfileView.vue 已包含完整的改密弹窗,AuthController 有对应端点,但文档标注为 ○。


5. 代码质量问题 (Code Quality)

CQ-01: SecurityConfig 重复 import (已修复)

位置: SecurityConfig.java:5,20 — 两次 import org.springframework.context.annotation.Bean 状态: 本次审计已修复

CQ-02: 个别 Controller 使用 @PreAuthorize 而非 JWT Filter 角色

位置: LicenseController.java:20,30,40 — 使用 @PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')") 问题: 与 JWT Filter 的双重验证增加了 role 前缀处理的复杂性(JwtAuthFilter 添加 ROLE_ 前缀,@PreAuthorize 期望 ROLE_ 格式)

CQ-03: 前端 API 层中个别函数含 query 参数拼接

位置: platform.js:460

export function createSkuMapping(contractLineId, body) {
    return axios.post(`/api/v1/integration/sku-mappings?contractLineId=${contractLineId}`, body);
}

影响: 非紧急,但建议统一使用 { params: { contractLineId } } 方式


6. 未被 PRD 覆盖但代码已实现的模块(超前实现)

模块 功能 建议
M7 设备管理 登记/列表/详情/绑定/换机申请 核对 PRD 需求后决定是否纳入正式范围
M8 通知待办 待办中心 + 通知通道配置 UI 需补充实际发送逻辑
M9 报表对账 4 个报表页面均已上线 补充导出按钮和推送逻辑
M6 ID/特征/SKU 映射 前后端均已实现 更新产品文档状态

7. 汇总统计

严重级别 数量 编号
🔴 Critical 4 CR-01~CR-04
🟠 High 4 HI-01~HI-04
🟡 Medium 5 ME-01~ME-05
🔵 Misalignment 3 MA-01~MA-03
Code Quality 3 CQ-01~CQ-03
合计 19

8. 修复建议优先级

P0 — 立即修复(安全基线)

  1. CR-01 (硬编码用户): 创建 platform_user 表 + Flyway 迁移 + AuthController 改用数据库查询 + BCrypt 密码校验
  2. CR-04 (空操作端点): resetPasswordforceLogout 补充实际逻辑 — resetPassword 更新用户密码,forceLogout 增加黑名单机制
  3. CR-03 (错误泄露): LicenseControllerContractController 移除 try-catch,让全局 ApiExceptionHandler 接管
  4. ME-04 (改密逻辑错误): changePassword 从 SecurityContext 获取当前用户名,从数据库查询对应用户的密码哈希

P1 — 短期修复

  1. HI-01 (用户管理): 在 P0 用户表基础上实现用户 CRUD API + 前端管理页面
  2. ME-01 (附件校验): ContractController upload 增加 @Size 注解和文件类型白名单
  3. ME-05 (事务缺失): batchImport 方法添加 @Transactional

P2 — 中长期

  1. CR-02 (Token 存储): 迁移至 HttpOnly Cookie(需后端配合返回 Set-Cookie header
  2. HI-02 (权限模型): 权限码持久化到数据库,实现可配置 RBAC
  3. HI-03 (会话管理): 引入 Token 黑名单/白名单机制或 Redis 会话存储