diff --git a/docs/architecture/详细设计/供应商信息维护模块.md b/docs/architecture/详细设计/供应商信息维护模块.md new file mode 100644 index 0000000..b17ff73 --- /dev/null +++ b/docs/architecture/详细设计/供应商信息维护模块.md @@ -0,0 +1,1157 @@ +# 供应商信息维护模块 + +| 文档名称 | 供应商信息维护模块详细设计 | +| -------- | ------------------------------------------------ | +| **文档版本** | V1.0 | +| **编制日期** | 2026-07-01 | +| **关联文档** | [[供应商注册模块]]《[[登录模块]]》《[[SRM 与 STP 数据交换接口方案说明书]]》 | + +--- + +## 1. 模块概述 + +### 1.1 建设目标 +供应商成功注册并登录后,需具备**自主维护企业信息**的能力。本模块覆盖**密码修改、联系人信息变更、公司证明文件(营业执照)更新**三大高频维护场景,确保账户安全、业务通知可达、企业资质动态更新,所有变更实时同步至SRM并全量审计。 + +### 1.2 设计原则 +| 原则 | 说明 | +| :------------ | :------------------------------------- | +| **安全优先** | 修改密码需验证旧密码;修改核心联系方式需双因子验证;敏感操作记录哈希链审计。 | +| **平滑同步** | 信息变更后实时同步SRM,确保两平台数据一致(最终一致性)。 | +| **版本可溯** | 营业执照等文件更新时保留历史版本,支持追溯。 | +| **自助为主,人工兜底** | 90%场景供应商自助完成,旧手机不可用时提供线下人工变更通道。 | + +### 1.3 适用角色与场景 +| 角色 | 场景 | +| :--- | :--- | +| **已登录供应商** | 修改密码、更新联系人、重新上传营业执照。 | +| **供应商管理专员** | 查看变更记录、处理异常变更申请(旧联系方式不可用时)。 | +| **系统管理员** | 配置安全策略(密码复杂度、会话有效期)。 | + + +## 2. 页面UI详细设计(工作台端) + +### 2.1 “企业信息”总览页 + +#### 2.1.1 “基本资料”Tab + +#### (一)页面 UI 设计 + +```svg + + + + + + + + + + + + + + + + + + + + 企业信息管理 + 管理您的企业资料、安全凭证和资质文件 + + + + + + 基本资料 + + + + 安全设置 + + + + 资质文件 + + + + + + + 📋 企业基本信息 + + 企业全称: + 滨化新材料有限公司 + + 统一社会信用代码: + 91370100MA3XXXXXXX + + 注册资本: + 5,000.00 万元 + 法定代表人: + 张伟 + + 行业分类: + C2614 有机化学原料制造 + + 注册地址: + 山东省滨州市黄河五路888号 + + + + 📞 联系人信息 + + 联系人姓名: + 李强 + + 手机号码: + 138****8001 + ✅ 已验证 + + 电子邮箱: + liqiang@example.com + ✅ 已验证 + + + 变更联系人 + + + 资料最后更新:2026-06-30 14:30 + | 供应商编号:SUP_20260630_0158 + +``` + +### (二)“变更联系人”完整处理流程 + +> **业务语义**:此操作表示企业联系人已更换(如人员离职、调岗),需要将姓名、手机、邮箱**作为一个整体**替换为新联系人的信息,而非逐项零散修改。 + +#### 1. 整体流程泳道图 + +```mermaid +flowchart TD + subgraph 供应商端 + A[点击“变更联系人”] --> B[弹出变更向导(Step 1/3)] + B --> C[Step 1:验证当前联系人] + C --> D[选择验证方式:手机或邮箱] + D --> E[发送验证码至选定方式] + E --> F[输入验证码] + F --> G{验证通过?} + G -->|失败| H[提示错误,可重试] + G -->|成功| I[进入 Step 2] + + I --> J[Step 2:填写新联系人信息] + J --> K[填写新姓名、新手机、新邮箱] + K --> L[点击“下一步”] + L --> M[后端校验:新手机/邮箱全局唯一] + M -->|已被占用| N[提示具体冲突项] + M -->|可用| O[进入 Step 3] + + O --> P[Step 3:验证新联系方式] + P --> Q[分别发送验证码至新手机和新邮箱] + Q --> R[输入手机验证码 + 邮箱验证码] + R --> S[点击“确认变更”] + S --> T[提交全部变更] + end + + subgraph STP后端 + STP1[接收变更请求] --> STP2[校验旧联系人验证Token] + STP2 --> STP3[校验新手机验证Token] + STP3 --> STP4[校验新邮箱验证Token] + STP4 --> STP5[原子性更新三个字段] + STP5 --> STP6[更新 login_name(若登录名为手机号)] + STP6 --> STP7[调用SRM批量更新联系人接口] + STP7 --> STP8[记录审计日志(含新旧完整数据)] + STP8 --> STP9[发送通知至新旧联系人] + STP9 --> STP10[返回成功] + end + + subgraph 外部系统 + SRM[SRM系统] + NOTIFY[通知服务] + end + + STP7 --> SRM + STP9 --> NOTIFY +``` + + + + +#### 2. 详细交互时序图 + +```mermaid +sequenceDiagram + participant U as 供应商 + participant FE as 前端 + participant Svc as 供应商服务 + participant Cache as Redis + participant SMS as 短信网关 + participant Email as 邮件网关 + participant DB as 数据库 + participant SRM as SRM系统 + + Note over U,SRM: === Step 1: 验证旧联系人 === + U->>FE: 点击“变更联系人” + FE-->>U: 弹出第一步验证窗 + U->>FE: 选择验证方式(手机/邮箱) + FE->>Svc: POST /api/v1/auth/verify/send { type:PHONE/EMAIL } + Svc->>SMS/Email: 发送验证码 + Svc->>Cache: SET verify:old:{userId} TTL=300 + U->>FE: 输入验证码 + FE->>Svc: POST /api/v1/auth/verify/check { code } + Svc->>Cache: 比对 + Svc-->>FE: { oldOtpToken: "temp_token_old" } + + Note over U,SRM: === Step 2: 填写新联系人 === + U->>FE: 填写新姓名、新手机、新邮箱 + FE->>Svc: 前端校验格式 + U->>FE: 点击“下一步” + FE->>Svc: POST /api/v1/supplier/contact/check-unique { newPhone, newEmail } + Svc->>DB: 查询新手机/邮箱是否已被占用 + alt 已被占用 + Svc-->>FE: 409 冲突,提示具体占用项 + else 可用 + Svc-->>FE: 200 可用,进入Step 3 + end + + Note over U,SRM: === Step 3: 验证新联系方式 === + FE->>Svc: POST /api/v1/auth/verify/send { type:PHONE, target:新手机, purpose:NEW } + Svc->>SMS: 发送验证码 + Svc->>Cache: SET verify:new_phone:{userId} TTL=300 + + FE->>Svc: POST /api/v1/auth/verify/send { type:EMAIL, target:新邮箱, purpose:NEW } + Svc->>Email: 发送验证码 + Svc->>Cache: SET verify:new_email:{userId} TTL=300 + + U->>FE: 输入两个验证码 + FE->>Svc: POST /api/v1/auth/verify/check { type:PHONE, code } + FE->>Svc: POST /api/v1/auth/verify/check { type:EMAIL, code } + Svc-->>FE: { newPhoneToken, newEmailToken } + + Note over U,SRM: === 最终提交 === + U->>FE: 点击“确认变更” + FE->>Svc: PUT /api/v1/supplier/contact/change-all + + Svc->>Svc: 校验三个Token有效性(old/newPhone/newEmail) + Svc->>DB: 开启事务 + Svc->>DB: UPDATE contact_name, contact_phone, contact_email + Svc->>DB: UPDATE login_name(若登录名=手机号) + Svc->>DB: 提交事务 + + Svc->>SRM: POST /api/v1/supplier/contact/batch-update { contactName, phone, email } + SRM-->>Svc: 200 OK + + Svc->>Cache: DEL 所有验证码缓存 + Svc->>DB: INSERT 审计日志(含完整新旧数据) + Svc->>SMS: 发送通知至旧手机(告知已变更) + Svc->>Email: 发送通知至旧邮箱 + Svc->>SMS: 发送通知至新手机(告知已绑定) + Svc->>Email: 发送通知至新邮箱 + + Svc-->>FE: 200 成功 + FE-->>U: 变更成功,页面刷新 +``` + + +#### 3. 关键业务规则 + +|规则项|说明| +|---|---| +|**原子性**|姓名、手机、邮箱必须在同一个数据库事务中同时更新,不允许部分成功。| +|**唯一性校验**|新手机号和新邮箱必须全局唯一(未被其他ACTIVE供应商占用),与注册规则一致。| +|**登录名联动**|若系统使用手机号作为登录名(`login_name`),更换手机号后`login_name`同步更新,旧手机号不可再用于登录。| +|**双因子验证**|必须同时验证三个Token(旧联系人、新手机、新邮箱),任一缺失或失效则拒绝变更。| +|**Token有效期**|所有验证Token有效期为5分钟,超时需重新获取。| +|**操作频率限制**|同一供应商30天内最多变更联系人**3次**。| +|**SRM同步**|调用SRM的**批量更新接口**一次性传递三个字段,确保SRM侧原子更新。| +|**会话处理**|变更手机号后,不强制踢出当前登录会话(旧会话仍有效),但密码找回等使用手机号的功能将指向新号。| +|**审计粒度**|审计日志需记录变更前的完整联系人信息和变更后的完整信息。| + +#### 4. 异常场景处理 + +|异常场景|用户侧表现|后端处理| +|---|---|---| +|**新手机号已被占用**|提示“该手机号已被其他供应商绑定”|返回冲突字段,阻塞提交| +|**新邮箱已被占用**|提示“该邮箱已被其他供应商绑定”|返回冲突字段,阻塞提交| +|**旧联系人验证超时(>5min)**|提示“验证已过期,请重新获取”|清除旧Token,要求重新验证| +|**新手机验证超时**|提示“手机验证码已过期”|清除该Token,要求重发| +|**新邮箱验证超时**|提示“邮箱验证码已过期”|清除该Token,要求重发| +|**SRM同步失败**|用户无感知(STP侧先成功)|异步重试5次,仍失败告警管理员| +|**30天内变更超限**|提示“30天内仅允许变更联系人3次”|返回限流错误码| + +#### 5. 接口规范 + +**最终提交接口** + +|项目|内容| +|---|---| +|**路径**|`/api/v1/supplier/contact/change-all`| +|**方法**|`PUT`| +|**认证**|需要登录态(JWT)| + +**请求体** + +```json +{ + "newContactName": "王芳", + "newPhone": "13900139000", + "newEmail": "wangfang@example.com", + "oldOtpToken": "eyJ...", + "newPhoneOtpToken": "eyJ...", + "newEmailOtpToken": "eyJ..." +} + +``` + +**成功响应** + +```json +{ + "code": "0", + "message": "联系人变更成功", + "data": { + "contactName": "王芳", + "contactPhone": "13900139000", + "contactEmail": "wangfang@example.com" + } +} +``` + +--- + +### 2.1.2 Tab 2:安全设置 + +#### (一)页面 UI 设计(切换到“安全设置”Tab 时的展示) + +```svg + + + + + + + + + + + + + + + + + + + + + 企业信息管理 + 管理您的企业资料、安全凭证和资质文件 + + + + + 基本资料 + + + 安全设置 + + + 资质文件 + + + + + + 🔐 账户安全设置 + 维护您的登录凭证,查看登录记录,确保账户安全 + + + + 登录密码 + 最后修改时间:2026-06-01 09:30 + 密码强度:强 + + 修改密码 + + + + 绑定手机号 + ✅ 已验证:138****8001 + + 更换手机号 + + + + 绑定邮箱 + ✅ 已验证:liqiang@example.com + + 更换邮箱 + + + + 当前登录设备 + 当前有 2 台设备保持登录状态 + ▶ 设备 1:Chrome/Windows · IP 192.168.1.100 · 2026-07-01 09:30(当前设备) + ▶ 设备 2:Safari/iPhone · IP 10.0.0.50 · 2026-06-30 22:15 + + + + 踢出所有设备 + 💡 点击“踢出所有设备”将强制所有设备(包括当前设备)重新登录 + + + + + + 📋 登录日志 + 查看最近 90 天的登录记录,如发现异常登录请及时修改密码 + + + 刷新日志 + + 查看全部 + + + + + + + 登录时间 + IP 地址 + 地理位置 + 设备/浏览器 + 状态 + + + + 2026-07-01 09:30:25 + 192.168.1.100 + 山东·滨州 + Chrome/Windows + + 当前设备 + + + + 2026-06-30 22:15:03 + 10.0.0.50 + 山东·滨州 + Safari/iOS + + 正常 + + + + 2026-06-29 03:45:12 + 203.0.113.45 + 境外·未知地区 + Firefox/Linux + + 异常 + + + + 2026-06-28 18:00:01 + 192.168.1.100 + 山东·滨州 + Chrome/Windows + + 正常 + + + + 🟢 安全状态 + • 密码强度:强 + • 手机/邮箱:已验证 + • 登录设备:2 台(含异常告警) + • 近 90 天异常登录:1 次 ⚠️ + +``` + +#### (二)登录日志查看详细功能设计 + +##### 1. 日志数据结构 + +|字段|类型|说明| +|---|---|---| +|`log_id`|VARCHAR(32)|日志ID| +|`user_id`|VARCHAR(32)|供应商用户ID| +|`login_time`|DATETIME|登录时间| +|`logout_time`|DATETIME|登出/Token过期时间(可选)| +|`ip_address`|VARCHAR(64)|登录IP| +|`location`|VARCHAR(128)|地理位置(根据IP库解析)| +|`user_agent`|VARCHAR(256)|设备/浏览器信息| +|`device_fingerprint`|VARCHAR(128)|设备指纹(Canvas指纹,用于同一设备识别)| +|`login_type`|VARCHAR(20)|REGISTER(注册自动登录)/ PASSWORD(密码)/ SMS(短信验证码)/ TOKEN(RefreshToken)| +|`status`|VARCHAR(20)|SUCCESS / FAIL / LOCKED| +|`fail_reason`|VARCHAR(64)|登录失败原因(如“密码错误”)| +|`is_current_session`|BOOLEAN|是否当前活跃会话| +|`hash_chain`|VARCHAR(256)|审计哈希链指针| + +> 日志数据来源于 `t_user_login_log` 和 `t_user_session` 表,保留期限 ≥ 90 天(对外展示),后台归档 7 年。 + +##### 2. 登录日志查看流程 + +```mermaid +flowchart TD + A[供应商进入安全设置Tab] --> B[页面自动加载最近20条登录日志] + B --> C[按登录时间倒序排列] + C --> D[高亮显示异常登录记录] + + D --> E{用户操作} + E -->|点击“刷新日志”| F[重新请求最新日志] + E -->|点击“查看全部”| G[跳转至完整的日志列表页
(支持分页/筛选/导出)] + E -->|点击“异常”标记| H[弹出详细告警提示] + + F --> B + G --> I[完整日志页:支持按时间范围筛选] + I --> J[支持按登录状态筛选
(全部/正常/异常/失败)] + J --> K[支持按设备筛选] + K --> L[支持导出为CSV(脱敏处理)] + + H --> M[展示:异常时间/IP/位置/设备] + M --> N[提示建议:立即修改密码 / 检查账户绑定] + N --> O[提供快捷跳转至“修改密码”] + + style D fill:#ffcdd2,stroke:#c62828 + style O fill:#e3f2fd,stroke:#1565c0 +``` + +##### 3. 异常登录判定规则(后台自动标注) + +| 判定规则 | 说明 | 告警级别 | +| ---------- | ----------------------------------- | ------- | +| **地理位置突变** | 本次登录IP归属地与前5次登录归属地跨省/跨境,且时间间隔小于12小时 | ⚠️ 高风险 | +| **设备指纹突变** | 本次登录设备指纹与历史记录完全不匹配 | ⚠️ 中风险 | +| **非常规时间段** | 登录时间为北京时间 00:00-06:00 | ⚠️ 中风险 | +| **登录失败连续** | 同一账户在5分钟内连续失败3次以上,随后成功登录 | ⚠️ 中风险 | +| **IP黑名单** | 登录IP落入已知威胁IP库(由安全团队维护) | 🔴 严重风险 | +| **暴力破解特征** | 同一IP尝试多个供应商账号登录 | 🔴 严重风险 | + +##### 4. 登录日志查询接口 + +|项目|内容| +|---|---| +|**路径**|`/api/v1/supplier/login-logs`| +|**方法**|`GET`| +|**认证**|需要登录态| + +**请求参数(分页 + 筛选)** + +| 参数名 | 类型 | 必填 | 说明 | +| ----------- | ------- | --- | ---------------------------------- | +| `pageNo` | Integer | 否 | 页码,默认1 | +| `pageSize` | Integer | 否 | 每页条数,默认20,最大100 | +| `startDate` | String | 否 | 开始时间(ISO 8601) | +| `endDate` | String | 否 | 结束时间 | +| `status` | String | 否 | 筛选:ALL / SUCCESS / FAIL / ABNORMAL | +| `device` | String | 否 | 设备筛选关键词 | +**成功响应** + +```json +{ + "code": "0", + "data": { + "total": 156, + "pageNo": 1, + "pageSize": 20, + "abnormalCount": 2, + "list": [ + { + "logId": "LOG_20260701_001", + "loginTime": "2026-07-01 09:30:25", + "ipAddress": "192.168.1.100", + "location": "山东·滨州", + "device": "Chrome/Windows 10", + "loginType": "PASSWORD", + "status": "SUCCESS", + "isCurrentSession": true, + "isAbnormal": false + }, + { + "logId": "LOG_20260629_003", + "loginTime": "2026-06-29 03:45:12", + "ipAddress": "203.0.113.45", + "location": "境外·未知", + "device": "Firefox/Linux", + "loginType": "PASSWORD", + "status": "SUCCESS", + "isCurrentSession": false, + "isAbnormal": true, + "abnormalReason": "地理位置突变" + } + ] + } +} +``` + +#### (三)“安全设置”Tab 完整操作流程汇总 + +| 功能项 | 验证强度 | 是否同步SRM | 频率限制 | 审计记录 | +| ---------- | ----------- | ----------- | --------- | -------- | +| **修改密码** | 高(验证旧密码) | 否(SRM侧密码独立) | 每天≤5次失败锁定 | ✅ 完整哈希链 | +| **更换手机号** | 最高(旧+新双因子) | ✅ 是 | 30天≤3次 | ✅ 完整哈希链 | +| **更换邮箱** | 最高(旧+新双因子) | ✅ 是 | 30天≤3次 | ✅ 完整哈希链 | +| **踢出所有设备** | 中(登录态+二次确认) | 否 | 无限制 | ✅ 完整哈希链 | +| **查看登录日志** | 低(仅登录态) | 否 | 无限制 | ✅ 仅查询审计 | +| **异常告警反馈** | 低 | 否 | 无限制 | ✅ 生成反馈工单 | +#### (四)安全设置各子项处理流程 + +##### 1. 修改密码流程(双因子验证旧密码) + +```mermaid +flowchart TD + A[点击“修改密码”] --> B[弹窗:输入旧密码 + 新密码 + 确认] + B --> C[点击“确认修改”] + C --> D[前端校验:非空/强度/新旧一致/新旧不同] + D -->|校验失败| E[提示具体错误,表单高亮] + D -->|校验通过| F[提交 PUT /api/v1/supplier/password] + F --> G[后端:查询DB获取当前密码哈希] + G --> H[Argon2id 比对旧密码] + H -->|失败| I[失败计数 Redis + 1] + I --> J{失败次数 ≥ 5?} + J -->|是| K[锁定账号 30 分钟 + 告警] + J -->|否| L[返回 401 剩余尝试次数] + K --> M[提示“账户已锁定”] + H -->|成功| N[重置失败计数] + N --> O[新密码哈希后 UPDATE DB] + O --> P[清除 Redis 中所有该用户的会话 Token] + P --> Q[记录审计日志(哈希链)] + Q --> R[发送通知至旧联系人(仅告知)] + R --> S[返回成功,要求重新登录] + S --> T[前端清除本地 Token,跳转登录页] +``` + +##### 2. 更换手机号流程(双因子验证旧+新) + +```mermaid +flowchart TD + A[点击“更换手机号”] --> B[弹窗:Step1 验证旧手机] + B --> C[发送验证码至旧手机] + C --> D[用户输入验证码] + D --> E[后端比对 Redis 验证码] + E -->|失败| F[提示错误,可重试 5 次] + E -->|成功| G[生成 oldOtpToken(有效期 5 分钟)] + G --> H[Step2:输入新手机号] + H --> I[点击“发送验证码”] + I --> J[后端校验新手机号唯一性(未绑定其他账户)] + J -->|已被占用| K[提示“该手机号已被其他供应商绑定”] + J -->|可用| L[发送验证码至新手机] + L --> M[用户输入新手机验证码] + M --> N[后端比对 Redis 验证码] + N -->|失败| O[提示错误] + N -->|成功| P[生成 newOtpToken(有效期 5 分钟)] + P --> Q[Step3:点击“确认变更”] + Q --> R[提交 PUT /supplier/contact 带两个 Token] + R --> S[后端校验两个 Token 有效性] + S --> T[UPDATE DB:contact_phone 字段] + T --> U[UPDATE DB:login_name(若手机号为登录名)] + U --> V[调用 SRM 更新接口] + V --> W[记录审计日志(含新旧值)] + W --> X[清除所有验证码缓存] + X --> Y[发送通知至新旧手机号] + Y --> Z[返回成功,保持登录态(旧 Token 仍有效)] +``` + +##### 3. 更换邮箱流程(流程与手机号完全对称) + +> 验证介质从“短信验证码”替换为“邮箱验证码”,调用 `/api/v1/auth/email/send` 接口,其余逻辑与更换手机号完全一致。 + +##### 4. 踢出所有设备流程 + +```mermaid +flowchart TD + A[点击“踢出所有设备”] --> B[二次确认弹窗] + B -->|取消| C[关闭弹窗] + B -->|确认| D[POST /api/v1/supplier/session/revoke-all] + D --> E[后端:删除 Redis 中该用户所有 session_key] + E --> F[记录审计日志] + F --> G[返回成功] + G --> H[前端清除本地 Token 跳转登录页] +``` + +--- + +### 2.1.3 Tab 3:资质文件 + +#### (一)页面 UI 设计 + +```svg + + + + + + + + + + + + + + + 企业信息管理 + 管理您的企业资料、安全凭证和资质文件 + + + + + 基本资料 + + + 安全设置 + + + 资质文件 + + + + + 📄 营业执照 + + + 营业执照_20260630.pdf + 版本:V3 | 上传时间:2026-06-30 14:30 | AI识别:✅ 已识别 + 识别字段:企业全称、信用代码、法人、地址、经营范围 + + + + 📤 更新营业执照 + + 📋 查看历史版本 + + + + + + 📎 其他资质文件 + ISO认证、安全生产许可证、产品检测报告、行业资质证书等 + + + + + 上传资质文件 + + + + + + + 文件名称 + 上传时间 + 大小 + 操作 + + + + ISO9001_质量管理体系认证.pdf + 2026-05-20 + 2.3MB + 重命名 + 删除 + + + + 安全生产许可证_2024.pdf + 2025-12-10 + 1.8MB + 重命名 + 删除 + + + + + + 🏆 业绩证明文件 + 近三年同类项目合同、中标通知书、验收报告、用户评价等 + + + + + 上传业绩文件 + + + + + + + 文件名称 + 上传时间 + 大小 + 操作 + + + + 滨化集团_催化剂供货合同_2025.pdf + 2025-08-15 + 5.6MB + 重命名 + 删除 + + + + 万华化学_项目验收报告_2024.pdf + 2024-12-20 + 3.2MB + 重命名 + 删除 + + + + 该分类下暂无其他文件 + + + + + + 📌 文件管理说明 + • 支持格式:PDF/JPG/PNG + • 单个文件 ≤ 20MB + • 文件总数量 ≤ 50 个 + • 删除后不可恢复,请谨慎操作 + • 投标报名时可一键引用已上传文件 + +``` + +#### (二)完整数据模型 + +##### 资质文件表 `t_supplier_qualification` + +|字段名|类型|约束|说明| +|---|---|---|---| +|`id`|BIGINT(20)|PK, AUTO_INCREMENT|自增主键| +|`file_id`|VARCHAR(32)|UNIQUE, NOT NULL|文件业务ID(Q_YYYYMMDD_XXXX)| +|`supplier_id`|VARCHAR(32)|FK, NOT NULL|供应商ID| +|`category`|VARCHAR(32)|NOT NULL|**分类**:LICENSE / OTHER_QUALIFICATION / PERFORMANCE| +|`file_name`|VARCHAR(256)|NOT NULL|文件显示名称(可修改)| +|`file_original_name`|VARCHAR(256)|NOT NULL|原始文件名(上传时的名称,不可修改)| +|`file_url`|VARCHAR(512)|NOT NULL|OSS存储URL| +|`file_size`|BIGINT||文件大小(字节)| +|`file_type`|VARCHAR(16)||MIME类型(application/pdf, image/jpeg等)| +|`upload_time`|DATETIME|NOT NULL|上传时间| +|`last_modified_time`|DATETIME||最后修改时间(重命名时更新)| +|`version`|INT|DEFAULT 1|版本号(仅营业执照使用,其他文件固定为1)| +|`status`|VARCHAR(20)|NOT NULL|ACTIVE / DELETED(软删除)| +|`deleted_time`|DATETIME||删除时间| +|`created_at`|DATETIME||创建时间| +|`updated_at`|DATETIME||更新时间| + +##### 营业执照单独管理(专用字段,与上表冗余关联) + +> 营业执照固定存在且唯一,使用`t_supplier_profile`中的字段存储主版本,历史版本通过`license_history` JSON字段保存,方便追溯。 + +| 供应商主表字段 | 说明 | +| -------------------- | ------------------------------------------- | +| `license_file_url` | 当前版本营业执照文件URL | +| `license_version` | 当前版本号 | +| `license_ocr_result` | OCR识别结果JSON | +| `license_history` | 历史版本数组:`[{version, url, ocr, upload_time}]` | + +#### (三)详细处理流程 + +##### 1. 上传资质/业绩文件 + +```mermaid +flowchart TD + subgraph 供应商端 + A[点击“上传资质文件”或“上传业绩文件”] --> B[弹出文件选择窗口] + B --> C[选择本地文件] + C --> D[前端校验:格式/大小] + D -->|不合格| E[提示错误:格式不支持或文件过大] + D -->|合格| F[点击“确认上传”] + F --> G[显示上传进度条] + G --> H[调用 POST /api/v1/supplier/qualification/upload] + H --> I[等待后端响应] + I --> J{响应结果} + J -->|成功| K[列表新增一行,显示新文件] + J -->|失败| L[提示具体错误原因] + K --> M[完成] + end + subgraph STP后端 + STP1[接收上传请求] --> STP2[校验供应商权限] + STP2 --> STP3[校验文件数量是否超限] + STP3 -->|超限| STP4[返回错误:文件数超50] + STP3 -->|未超限| STP5[流式上传至OSS] + STP5 --> STP6[获取文件URL] + STP6 --> STP7[INSERT t_supplier_qualification] + STP7 --> STP8[记录审计日志] + STP8 --> STP9[返回成功 + 文件元数据] + end + subgraph 外部系统 + OSS[OSS对象存储] + end + STP5 --> OSS + +``` + +**关键校验规则(上传)** + +| 校验项 | 规则 | +| ----- | ----------------------------- | +| 文件格式 | JPG、PNG、PDF(MIME类型校验) | +| 文件大小 | ≤ 20MB | +| 总数量限制 | 每个分类(资质/业绩)各 ≤ 30 个,总计 ≤ 50 个 | +| 文件名称 | 自动去除特殊字符,防止路径遍历攻击 | +| 文件去重 | 校验MD5,避免同一文件重复上传(提示“该文件已存在”) | + +**接口定义** + +|项目|内容| +|---|---| +|**路径**|`/api/v1/supplier/qualification/upload`| +|**方法**|`POST`| +|**认证**|需要登录态| +|**请求**|`multipart/form-data`| + +|表单参数|类型|必填|说明| +|---|---|---|---| +|`file`|File|是|文件内容| +|`category`|String|是|`OTHER_QUALIFICATION` / `PERFORMANCE`| +|`fileName`|String|否|自定义显示名称(不传则使用原始文件名)| + +**成功响应** + +```json +{ + "code": "0", + "data": { + "fileId": "Q_20260701_001", + "fileName": "ISO9001_质量管理体系认证.pdf", + "fileUrl": "https://oss.stp.com/...", + "fileSize": 2411724, + "uploadTime": "2026-07-01 10:30:00" + } +} +``` + +##### 2. 重命名文件 + +```mermaid +flowchart TD + A[点击文件行的“重命名”] --> B[文件名变为可编辑输入框] + B --> C[供应商输入新名称] + C --> D[点击“保存”或按回车] + D --> E[前端校验:非空、长度≤100、不含特殊字符] + E -->|校验失败| F[提示具体错误] + E -->|校验通过| G[调用 PUT /api/v1/supplier/qualification/rename] + G --> H[后端校验权限:文件归属当前供应商] + H --> I[更新 file_name 字段] + I --> J[更新 last_modified_time] + J --> K[记录审计日志] + K --> L[返回成功] + L --> M[页面刷新显示新名称] +``` + +**接口定义** + +|项目|内容| +|---|---| +|**路径**|`/api/v1/supplier/qualification/rename`| +|**方法**|`PUT`| +|**认证**|需要登录态| + +**请求体** + +```json + +{ + "fileId": "Q_20260701_001", + "newName": "ISO9001_质量管理体系认证证书_2026版.pdf" +} +``` + + +##### 3. 删除文件 + +```mermaid +flowchart TD + A[点击文件行的“删除”] --> B[弹出二次确认弹窗] + B -->|取消| C[关闭弹窗] + B -->|确认| D[调用 DELETE /api/v1/supplier/qualification/delete] + D --> E[后端校验权限] + E --> F[检查文件是否被正在进行的投标报名引用] + F -->|被引用| G[返回错误:文件正在使用中,不可删除] + F -->|未被引用| H[软删除:status='DELETED' + deleted_time] + H --> I[记录审计日志(含文件名和删除人)] + I --> J[返回成功] + J --> K[前端移除该行] +``` + +> **特别规则**: +> - 软删除仅标记状态,OSS文件暂不物理删除(保留期≥90天,便于审计追溯)。 +> - 若文件已被**进行中的投标报名**引用,则不允许删除,提示“该文件已被报名项目引用,无法删除”。 + +**接口定义** + +| 项目 | 内容 | +| :--- | :--- | +| **路径** | `/api/v1/supplier/qualification/delete` | +| **方法** | `DELETE` | +| **认证** | 需要登录态 | + +**请求体** +```json +{ + "fileId": "Q_20260701_001" +} +``` + +##### 4. 营业执照更新 + +> 营业执照更新逻辑与资质文件独立,使用专门接口。 + +```mermaid +flowchart TD + subgraph 供应商端 + A[点击“更新营业执照”] --> B[上传新营业执照文件] + B --> C[显示“上传中...AI审查中...”] + C --> D{等待后端最终结果} + D -->|审查通过| E[显示成功:营业执照已更新] + D -->|审查不通过| F[显示具体驳回原因
+ 重新上传按钮] + E --> G[页面自动刷新,显示新信息] + F --> H[供应商修改后重新上传] + end + + subgraph STP后端处理链路 + STP1[接收上传请求] --> STP2[文件病毒扫描 + OSS上传] + STP2 --> STP3[调用 OCR 服务提取文字字段] + STP3 --> STP4[AI 真实性审查(见下方子流程)] + STP4 --> STP5{真实性审查通过?} + STP5 -->|否| STP6[驳回:营业执照疑似伪造或篡改] + STP5 -->|是| STP7[AI 一致性审查(与数据库/工商库比对)] + STP7 --> STP8{一致性审查通过?} + STP8 -->|否| STP9[驳回:信息不一致,需人工复核] + STP8 -->|是| STP10[生成审查报告 + 数字指纹] + STP10 --> STP11[更新供应商主表企业字段] + STP11 --> STP12[保存历史版本至 license_history] + STP12 --> STP13[调用 SRM 批量更新接口] + STP13 --> STP14[记录审计日志(含审查报告哈希)] + STP14 --> STP15[返回成功] + + STP6 --> STP16[记录审计日志(含失败原因)] + STP9 --> STP16 + STP16 --> STP17[返回驳回 + 原因] + end + + subgraph AI审查引擎 + AI1[真实性审查] --> AI1a[图像真伪检测
(PS/拼接/翻拍识别)] + AI1 --> AI1b[印章/水印检测] + AI1 --> AI1c[版面一致性检测] + AI1a --> AI2[输出:真实/可疑/伪造] + AI1b --> AI2 + AI1c --> AI2 + + AI3[一致性审查] --> AI3a[OCR字段 vs 数据库现有字段] + AI3 --> AI3b[OCR字段 vs 工商API返回数据] + AI3a --> AI4[输出:一致/部分不一致/严重不一致] + AI3b --> AI4 + end + + subgraph 外部系统 + EXT1[OSS对象存储] + EXT2[工商信息API] + EXT3[SRM系统] + end + + STP2 --> EXT1 + STP3 --> STP4 + AI3b --> EXT2 + STP13 --> EXT3 +``` + +##### 5. 营业执照历史版本查看 + +点击“查看历史版本”弹出时间线,展示所有历史版本: + +``` +V3 (当前) - 2026-06-30 14:30 滨化新材料有限公司_2026.pdf ✅ 当前使用 +V2 - 2025-12-01 09:20 滨化新材料有限公司_2025.pdf +V1 - 2024-06-15 11:00 滨化新材料有限公司_2024.pdf +``` + +每个版本可预览/下载。 + +#### (四)业务规则汇总 + +| 规则ID | 规则 | 适用范围 | +| :--- | :------------------------------------------------- | :---- | +| R01 | 营业执照全局唯一,供应商仅可维护一份,版本递增。 | 营业执照 | +| R02 | 营业执照信用代码识别结果与系统不一致时,系统不自动更新,提示人工复核。 | 营业执照 | +| R03 | 其他资质文件分类:自动归类为`OTHER_QUALIFICATION`或`PERFORMANCE`。 | 资质/业绩 | +| R04 | 同一分类文件数量上限为30个,总上限50个。 | 资质/业绩 | +| R05 | 文件格式仅支持PDF/JPG/PNG,大小≤20MB。 | 资质/业绩 | +| R06 | 文件重命名后需同步更新`last_modified_time`。 | 资质/业绩 | +| R07 | 删除为软删除,数据永久保留(不物理删除OSS文件,保留≥90天)。 | 资质/业绩 | +| R08 | 被进行中的投标报名引用的文件不可删除。 | 资质/业绩 | +| R09 | 所有上传/删除/重命名操作记录审计日志(哈希链)。 | 全场景 | +| R10 | 资质/业绩文件不自动同步至SRM(由供应商自主管理,报名时按需引用)。 | 资质/业绩 | +| R11 | 营业执照更新后自动同步至SRM,保持企业信息一致。 | 营业执照 | +#### (五)异常场景处理矩阵 + +| 异常场景 | 用户侧表现 | 后端处理 | +| :----------------- | :---------------------------- | :--------------- | +| **上传文件格式不支持** | 前端提示“仅支持PDF/JPG/PNG格式” | 返回400 | +| **上传文件超过20MB** | 前端提示“文件不能超过20MB” | 返回400 | +| **文件数量达到上限(50个)** | 提示“文件数量已满(50个),请删除旧文件后再上传” | 返回429 | +| **重命名时名称重复** | 提示“该名称已存在,请修改” | 返回409 | +| **重命名时含非法字符** | 提示“文件名不能包含特殊字符” | 返回400 | +| **删除时文件被报名引用** | 提示“该文件已被项目XXX引用,无法删除” | 返回403 | +| **营业执照OCR识别失败** | 提示“识别失败,请确保图片清晰” | 返回错误,允许用户重试或手动修改 | +| **营业执照信用代码不一致** | 页面高亮告警“识别结果与系统记录不一致,请人工确认” | 记录告警日志 | +| **OSS上传超时(>10s)** | 提示“上传超时,请重试” | 记录WARN日志 | +| **翻拍检测报警** | “请上传营业执照原件扫描件,而非照片/复印件” | 驳回 | +| **信用代码与工商API不匹配** | “统一社会信用代码与工商登记信息不一致,请核实后重新上传” | 驳回,记录差异 | +| **OCR识别结果与数据库冲突** | “识别结果与系统记录不一致,进入人工复核流程” | 冻结变更,生成待办 | +| **AI审查服务超时(>20s)** | “审查服务繁忙,请稍后重试” | 返回503,不更新任何数据 | +| **工商API不可用** | “企业信息核验服务暂不可用,请稍后重试” | 阻断更新(不降级),记录告警 | + +--- +## 3. SRM同步与异常重试 + +### 8.1 同步触发条件 + +| 变更类型 | 调用SRM接口 | 同步内容 | +| :------ | :---------------------- | :-------------------------------------- | +| 修改密码 | 不调用SRM | 无(SRM侧密码独立管理) | +| 修改联系人姓名 | `POST /contact/update` | contactName | +| 修改手机号 | `POST /contact/update` | contactPhone | +| 修改邮箱 | `POST /contact/update` | contactEmail | +| 更新营业执照 | `POST /supplier/update` | companyName, address, legalPerson等(若变更) | + +### 8.2 重试机制 + +```mermaid +stateDiagram-v2 + [*] --> SYNC_PENDING: 变更提交 + SYNC_PENDING --> SYNCING: 立即调用SRM + SYNCING --> SYNC_SUCCESS: 成功 + SYNCING --> SYNC_FAILED: 失败 + SYNC_FAILED --> SYNCING: 异步重试(间隔5/15/30/60/120分钟) + SYNC_FAILED --> ALERT: 5次均失败 + ALERT --> [*]: 管理员人工介入 +``` + + +## 9. 异常场景处理矩阵 + +| 异常场景 | 用户侧表现 | 后端处理 | +| :--- | :--- | :--- | +| **修改密码时旧密码错误** | 提示“当前密码错误,剩余X次机会” | 失败计数+1,写入审计日志 | +| **密码错误达5次** | 提示“账户已锁定30分钟” | 锁定并告警 | +| **修改手机号时新号已被占用** | 提示“该手机号已被其他供应商绑定” | 返回409冲突 | +| **验证码发送频率过高** | 提示“发送过于频繁,请60秒后重试” | 返回429限流 | +| **双因子验证Token超时(5分钟)** | 提示“验证已过期,请重新获取” | 清除旧Token,要求重发 | +| **营业执照OCR识别失败** | 提示“识别失败,请检查图片清晰度” | 允许手动填写关键字段并标记 | +| **SRM同步超时(>5s)** | 用户无感知(显示成功) | 异步重试,后台告警 | +| **上传文件格式/大小不符** | 前端拦截,提示具体原因 | 后端二次校验 | + + +## 10. 通知策略 + +| 触发事件 | 通知方式 | 接收方 | 文案 | +| :--- | :--- | :--- | :--- | +| **密码修改成功** | 短信+邮件 | 当前联系人与旧联系人(若变更) | “您的STP账户密码已于XX时间修改,若非本人操作请立即联系管理员。” | +| **手机号修改成功** | 短信+邮件 | 新旧手机号 | “您的STP账户绑定手机号已变更为XXX,请确认。” | +| **邮箱修改成功** | 邮件 | 新旧邮箱 | “您的STP账户绑定邮箱已变更为XXX。” | +| **营业执照更新成功** | 站内信 | 供应商联系人 | “企业营业执照已更新,请留意后续通知。” | +| **SRM同步失败告警** | 邮件+飞书 | 系统管理员 | “供应商ID {id} 信息变更同步SRM失败,请人工介入。” |