# 供应商信息维护模块 | 文档名称 | 供应商信息维护模块详细设计 | | -------- | ------------------------------------------------ | | **文档版本** | 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失败,请人工介入。” |