1158 lines
55 KiB
Markdown
1158 lines
55 KiB
Markdown
# 供应商信息维护模块
|
||
|
||
| 文档名称 | 供应商信息维护模块详细设计 |
|
||
| -------- | ------------------------------------------------ |
|
||
| **文档版本** | 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
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 800" font-family="'Microsoft YaHei', sans-serif">
|
||
<defs>
|
||
<filter id="shadowLight" x="-2%" y="-2%" width="104%" height="104%">
|
||
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.04"/>
|
||
</filter>
|
||
<linearGradient id="brandGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#c2185b"/><stop offset="100%" stop-color="#e91e63"/>
|
||
</linearGradient>
|
||
<linearGradient id="tabActive" x1="0%" y1="0%" x2="0%" y2="100%">
|
||
<stop offset="0%" stop-color="#c2185b"/><stop offset="100%" stop-color="#c2185b"/>
|
||
</linearGradient>
|
||
</defs>
|
||
|
||
<rect width="1100" height="800" fill="#f5f7fa"/>
|
||
|
||
<!-- 主卡片 -->
|
||
<rect x="60" y="30" width="980" height="740" rx="16" fill="#fff" filter="url(#shadowLight)"/>
|
||
|
||
<!-- 页面标题 -->
|
||
<text x="100" y="75" font-size="22" font-weight="bold" fill="#1a2332">企业信息管理</text>
|
||
<text x="100" y="102" font-size="14" fill="#8895aa">管理您的企业资料、安全凭证和资质文件</text>
|
||
<line x1="100" y1="125" x2="1000" y2="125" stroke="#f0f2f7" stroke-width="2"/>
|
||
|
||
<!-- ===== Tab 切换栏(三个) ===== -->
|
||
<!-- Tab1: 基本资料(激活状态) -->
|
||
<rect x="100" y="145" width="120" height="40" rx="8" fill="#c2185b"/>
|
||
<text x="160" y="171" text-anchor="middle" font-size="14" font-weight="bold" fill="#fff">基本资料</text>
|
||
|
||
<!-- Tab2: 安全设置 -->
|
||
<rect x="230" y="145" width="120" height="40" rx="8" fill="#f5f5f5"/>
|
||
<text x="290" y="171" text-anchor="middle" font-size="14" fill="#666">安全设置</text>
|
||
|
||
<!-- Tab3: 资质文件 -->
|
||
<rect x="360" y="145" width="120" height="40" rx="8" fill="#f5f5f5"/>
|
||
<text x="420" y="171" text-anchor="middle" font-size="14" fill="#666">资质文件</text>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ===== 当前展示:基本资料 Tab 内容(默认激活) ===== -->
|
||
<!-- ============================================================ -->
|
||
|
||
<!-- 企业基本信息展示区 -->
|
||
<text x="140" y="215" font-size="15" font-weight="bold" fill="#4a5568">📋 企业基本信息</text>
|
||
|
||
<text x="140" y="248" font-size="14" fill="#888">企业全称:</text>
|
||
<text x="250" y="248" font-size="14" fill="#1a2332" font-weight="bold">滨化新材料有限公司</text>
|
||
|
||
<text x="140" y="278" font-size="14" fill="#888">统一社会信用代码:</text>
|
||
<text x="300" y="278" font-size="14" fill="#1a2332">91370100MA3XXXXXXX</text>
|
||
|
||
<text x="140" y="308" font-size="14" fill="#888">注册资本:</text>
|
||
<text x="250" y="308" font-size="14" fill="#1a2332">5,000.00 万元</text>
|
||
<text x="440" y="308" font-size="14" fill="#888">法定代表人:</text>
|
||
<text x="550" y="308" font-size="14" fill="#1a2332">张伟</text>
|
||
|
||
<text x="140" y="338" font-size="14" fill="#888">行业分类:</text>
|
||
<text x="250" y="338" font-size="14" fill="#1a2332">C2614 有机化学原料制造</text>
|
||
|
||
<text x="140" y="368" font-size="14" fill="#888">注册地址:</text>
|
||
<text x="250" y="368" font-size="14" fill="#1a2332">山东省滨州市黄河五路888号</text>
|
||
|
||
<!-- 联系人信息区 -->
|
||
<line x1="140" y1="400" x2="1000" y2="400" stroke="#f0f2f7" stroke-width="1"/>
|
||
<text x="140" y="430" font-size="15" font-weight="bold" fill="#4a5568">📞 联系人信息</text>
|
||
|
||
<text x="140" y="462" font-size="14" fill="#888">联系人姓名:</text>
|
||
<text x="250" y="462" font-size="14" fill="#1a2332" font-weight="bold">李强</text>
|
||
|
||
<text x="140" y="498" font-size="14" fill="#888">手机号码:</text>
|
||
<text x="250" y="498" font-size="14" fill="#1a2332">138****8001</text>
|
||
<text x="250" y="518" font-size="12" fill="#4caf50">✅ 已验证</text>
|
||
|
||
<text x="140" y="550" font-size="14" fill="#888">电子邮箱:</text>
|
||
<text x="250" y="550" font-size="14" fill="#1a2332">liqiang@example.com</text>
|
||
<text x="250" y="570" font-size="12" fill="#4caf50">✅ 已验证</text>
|
||
|
||
<rect x="145" y="585" width="80" height="28" rx="6" fill="#e3f2fd" stroke="#1565c0"/>
|
||
<text x="185" y="605" text-anchor="middle" font-size="12" fill="#1565c0">变更联系人</text>
|
||
|
||
<!-- 资料更新时间 -->
|
||
<text x="140" y="645" font-size="12" fill="#b0bbcc">资料最后更新:2026-06-30 14:30</text>
|
||
<text x="380" y="645" font-size="12" fill="#b0bbcc">| 供应商编号:SUP_20260630_0158</text>
|
||
</svg>
|
||
```
|
||
|
||
### (二)“变更联系人”完整处理流程
|
||
|
||
> **业务语义**:此操作表示企业联系人已更换(如人员离职、调岗),需要将姓名、手机、邮箱**作为一个整体**替换为新联系人的信息,而非逐项零散修改。
|
||
|
||
#### 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
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 920" font-family="'Microsoft YaHei', sans-serif">
|
||
<defs>
|
||
<filter id="shadowLight" x="-2%" y="-2%" width="104%" height="104%">
|
||
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.04"/>
|
||
</filter>
|
||
<linearGradient id="brandGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#c2185b"/><stop offset="100%" stop-color="#e91e63"/>
|
||
</linearGradient>
|
||
<linearGradient id="statusGreen" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#4caf50"/><stop offset="100%" stop-color="#66bb6a"/>
|
||
</linearGradient>
|
||
<linearGradient id="statusRed" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#f44336"/><stop offset="100%" stop-color="#ef5350"/>
|
||
</linearGradient>
|
||
</defs>
|
||
|
||
<rect width="1100" height="920" fill="#f5f7fa"/>
|
||
<rect x="60" y="30" width="980" height="860" rx="16" fill="#fff" filter="url(#shadowLight)"/>
|
||
|
||
<!-- 页面标题 -->
|
||
<text x="100" y="75" font-size="22" font-weight="bold" fill="#1a2332">企业信息管理</text>
|
||
<text x="100" y="102" font-size="14" fill="#8895aa">管理您的企业资料、安全凭证和资质文件</text>
|
||
<line x1="100" y1="125" x2="1000" y2="125" stroke="#f0f2f7" stroke-width="2"/>
|
||
|
||
<!-- ===== Tab 切换栏(安全设置高亮) ===== -->
|
||
<rect x="100" y="145" width="120" height="40" rx="8" fill="#f5f5f5"/>
|
||
<text x="160" y="171" text-anchor="middle" font-size="14" fill="#666">基本资料</text>
|
||
|
||
<rect x="230" y="145" width="120" height="40" rx="8" fill="#c2185b"/>
|
||
<text x="290" y="171" text-anchor="middle" font-size="14" font-weight="bold" fill="#fff">安全设置</text>
|
||
|
||
<rect x="360" y="145" width="120" height="40" rx="8" fill="#f5f5f5"/>
|
||
<text x="420" y="171" text-anchor="middle" font-size="14" fill="#666">资质文件</text>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ===== 安全设置 Tab 内容 ===== -->
|
||
<!-- ============================================================ -->
|
||
|
||
<text x="140" y="215" font-size="15" font-weight="bold" fill="#4a5568">🔐 账户安全设置</text>
|
||
<text x="140" y="242" font-size="13" fill="#8895aa">维护您的登录凭证,查看登录记录,确保账户安全</text>
|
||
|
||
<!-- 1. 登录密码 -->
|
||
<rect x="140" y="270" width="820" height="60" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
<text x="170" y="298" font-size="14" font-weight="bold" fill="#1a2332">登录密码</text>
|
||
<text x="170" y="318" font-size="12" fill="#8895aa">最后修改时间:2026-06-01 09:30</text>
|
||
<text x="170" y="336" font-size="12" fill="#4caf50">密码强度:强</text>
|
||
<rect x="770" y="278" width="160" height="34" rx="6" fill="#c2185b"/>
|
||
<text x="850" y="300" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">修改密码</text>
|
||
|
||
<!-- 2. 绑定手机号 -->
|
||
<rect x="140" y="350" width="820" height="60" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
<text x="170" y="378" font-size="14" font-weight="bold" fill="#1a2332">绑定手机号</text>
|
||
<text x="170" y="398" font-size="12" fill="#4caf50">✅ 已验证:138****8001</text>
|
||
<rect x="770" y="358" width="160" height="34" rx="6" fill="#1565c0"/>
|
||
<text x="850" y="380" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">更换手机号</text>
|
||
|
||
<!-- 3. 绑定邮箱 -->
|
||
<rect x="140" y="430" width="820" height="60" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
<text x="170" y="458" font-size="14" font-weight="bold" fill="#1a2332">绑定邮箱</text>
|
||
<text x="170" y="478" font-size="12" fill="#4caf50">✅ 已验证:liqiang@example.com</text>
|
||
<rect x="770" y="438" width="160" height="34" rx="6" fill="#1565c0"/>
|
||
<text x="850" y="460" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">更换邮箱</text>
|
||
|
||
<!-- 4. 当前登录设备管理 -->
|
||
<rect x="140" y="510" width="820" height="100" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
<text x="170" y="538" font-size="14" font-weight="bold" fill="#1a2332">当前登录设备</text>
|
||
<text x="170" y="562" font-size="12" fill="#8895aa">当前有 2 台设备保持登录状态</text>
|
||
<text x="170" y="578" font-size="12" fill="#1565c0">▶ 设备 1:Chrome/Windows · IP 192.168.1.100 · 2026-07-01 09:30(当前设备)</text>
|
||
<text x="170" y="590" font-size="12" fill="#666">▶ 设备 2:Safari/iPhone · IP 10.0.0.50 · 2026-06-30 22:15</text>
|
||
|
||
<!-- 操作按钮 -->
|
||
<rect x="770" y="518" width="160" height="34" rx="6" fill="#ff6f00"/>
|
||
<text x="850" y="540" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">踢出所有设备</text>
|
||
<text x="170" y="610" font-size="12" fill="#888">💡 点击“踢出所有设备”将强制所有设备(包括当前设备)重新登录</text>
|
||
|
||
<!-- ===== 5. 登录日志查看(新增区域) ===== -->
|
||
<line x1="140" y1="635" x2="1000" y2="635" stroke="#f0f2f7" stroke-width="1"/>
|
||
|
||
<!-- 日志区域标题+操作栏 -->
|
||
<text x="140" y="665" font-size="15" font-weight="bold" fill="#4a5568">📋 登录日志</text>
|
||
<text x="140" y="690" font-size="13" fill="#8895aa">查看最近 90 天的登录记录,如发现异常登录请及时修改密码</text>
|
||
|
||
<rect x="770" y="650" width="160" height="34" rx="6" fill="#e0e4ec"/>
|
||
<text x="850" y="672" text-anchor="middle" font-size="13" fill="#666">刷新日志</text>
|
||
<rect x="940" y="650" width="100" height="34" rx="6" fill="#c2185b"/>
|
||
<text x="990" y="672" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">查看全部</text>
|
||
|
||
<!-- 日志表格 -->
|
||
<rect x="140" y="710" width="820" height="180" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
|
||
<!-- 表头 -->
|
||
<rect x="140" y="710" width="820" height="36" rx="8" fill="#f0f2f7"/>
|
||
<text x="170" y="733" font-size="13" font-weight="bold" fill="#4a5568">登录时间</text>
|
||
<text x="330" y="733" font-size="13" font-weight="bold" fill="#4a5568">IP 地址</text>
|
||
<text x="470" y="733" font-size="13" font-weight="bold" fill="#4a5568">地理位置</text>
|
||
<text x="610" y="733" font-size="13" font-weight="bold" fill="#4a5568">设备/浏览器</text>
|
||
<text x="800" y="733" font-size="13" font-weight="bold" fill="#4a5568">状态</text>
|
||
|
||
<!-- 行1:当前设备 -->
|
||
<rect x="150" y="755" width="800" height="30" rx="4" fill="#e8f5e9"/>
|
||
<text x="170" y="775" font-size="13" fill="#1a2332">2026-07-01 09:30:25</text>
|
||
<text x="330" y="775" font-size="13" fill="#1a2332">192.168.1.100</text>
|
||
<text x="470" y="775" font-size="13" fill="#1a2332">山东·滨州</text>
|
||
<text x="610" y="775" font-size="13" fill="#1a2332">Chrome/Windows</text>
|
||
<rect x="790" y="763" width="60" height="16" rx="3" fill="url(#statusGreen)"/>
|
||
<text x="820" y="775" text-anchor="middle" font-size="11" fill="#fff">当前设备</text>
|
||
|
||
<!-- 行2:历史登录 -->
|
||
<rect x="150" y="790" width="800" height="30" rx="4" fill="#fff"/>
|
||
<text x="170" y="810" font-size="13" fill="#1a2332">2026-06-30 22:15:03</text>
|
||
<text x="330" y="810" font-size="13" fill="#1a2332">10.0.0.50</text>
|
||
<text x="470" y="810" font-size="13" fill="#1a2332">山东·滨州</text>
|
||
<text x="610" y="810" font-size="13" fill="#1a2332">Safari/iOS</text>
|
||
<rect x="790" y="798" width="60" height="16" rx="3" fill="url(#statusGreen)"/>
|
||
<text x="820" y="810" text-anchor="middle" font-size="11" fill="#fff">正常</text>
|
||
|
||
<!-- 行3:异常登录预警示例 -->
|
||
<rect x="150" y="825" width="800" height="30" rx="4" fill="#ffebee"/>
|
||
<text x="170" y="845" font-size="13" fill="#c62828">2026-06-29 03:45:12</text>
|
||
<text x="330" y="845" font-size="13" fill="#c62828">203.0.113.45</text>
|
||
<text x="470" y="845" font-size="13" fill="#c62828">境外·未知地区</text>
|
||
<text x="610" y="845" font-size="13" fill="#c62828">Firefox/Linux</text>
|
||
<rect x="790" y="833" width="60" height="16" rx="3" fill="url(#statusRed)"/>
|
||
<text x="820" y="845" text-anchor="middle" font-size="11" fill="#fff">异常</text>
|
||
|
||
<!-- 行4 -->
|
||
<rect x="150" y="860" width="800" height="30" rx="4" fill="#fff"/>
|
||
<text x="170" y="880" font-size="13" fill="#1a2332">2026-06-28 18:00:01</text>
|
||
<text x="330" y="880" font-size="13" fill="#1a2332">192.168.1.100</text>
|
||
<text x="470" y="880" font-size="13" fill="#1a2332">山东·滨州</text>
|
||
<text x="610" y="880" font-size="13" fill="#1a2332">Chrome/Windows</text>
|
||
<rect x="790" y="868" width="60" height="16" rx="3" fill="url(#statusGreen)"/>
|
||
<text x="820" y="880" text-anchor="middle" font-size="11" fill="#fff">正常</text>
|
||
|
||
<!-- 安全状态聚合(右侧浮窗) -->
|
||
<rect x="720" y="120" width="240" height="130" rx="8" fill="#e8f5e9" stroke="#4caf50"/>
|
||
<text x="740" y="148" font-size="14" font-weight="bold" fill="#2e7d32">🟢 安全状态</text>
|
||
<text x="740" y="175" font-size="12" fill="#1b5e20">• 密码强度:强</text>
|
||
<text x="740" y="198" font-size="12" fill="#1b5e20">• 手机/邮箱:已验证</text>
|
||
<text x="740" y="221" font-size="12" fill="#1b5e20">• 登录设备:2 台(含异常告警)</text>
|
||
<text x="740" y="244" font-size="12" fill="#c62828">• 近 90 天异常登录:1 次 ⚠️</text>
|
||
</svg>
|
||
```
|
||
|
||
#### (二)登录日志查看详细功能设计
|
||
|
||
##### 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[跳转至完整的日志列表页<br>(支持分页/筛选/导出)]
|
||
E -->|点击“异常”标记| H[弹出详细告警提示]
|
||
|
||
F --> B
|
||
G --> I[完整日志页:支持按时间范围筛选]
|
||
I --> J[支持按登录状态筛选<br>(全部/正常/异常/失败)]
|
||
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
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 950" font-family="'Microsoft YaHei', sans-serif">
|
||
<defs>
|
||
<filter id="shadowLight" x="-2%" y="-2%" width="104%" height="104%">
|
||
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.04"/>
|
||
</filter>
|
||
<linearGradient id="brandGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#c2185b"/><stop offset="100%" stop-color="#e91e63"/>
|
||
</linearGradient>
|
||
</defs>
|
||
|
||
<rect width="1100" height="950" fill="#f5f7fa"/>
|
||
<rect x="60" y="30" width="980" height="890" rx="16" fill="#fff" filter="url(#shadowLight)"/>
|
||
|
||
<!-- 页面标题 -->
|
||
<text x="100" y="75" font-size="22" font-weight="bold" fill="#1a2332">企业信息管理</text>
|
||
<text x="100" y="102" font-size="14" fill="#8895aa">管理您的企业资料、安全凭证和资质文件</text>
|
||
<line x1="100" y1="125" x2="1000" y2="125" stroke="#f0f2f7" stroke-width="2"/>
|
||
|
||
<!-- ===== Tab 切换栏(资质文件高亮) ===== -->
|
||
<rect x="100" y="145" width="120" height="40" rx="8" fill="#f5f5f5"/>
|
||
<text x="160" y="171" text-anchor="middle" font-size="14" fill="#666">基本资料</text>
|
||
|
||
<rect x="230" y="145" width="120" height="40" rx="8" fill="#f5f5f5"/>
|
||
<text x="290" y="171" text-anchor="middle" font-size="14" fill="#666">安全设置</text>
|
||
|
||
<rect x="360" y="145" width="120" height="40" rx="8" fill="#c2185b"/>
|
||
<text x="420" y="171" text-anchor="middle" font-size="14" font-weight="bold" fill="#fff">资质文件</text>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ===== 1. 营业执照(固定,顶部展示) ===== -->
|
||
<!-- ============================================================ -->
|
||
<text x="140" y="215" font-size="15" font-weight="bold" fill="#4a5568">📄 营业执照</text>
|
||
|
||
<rect x="140" y="235" width="820" height="95" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
<text x="170" y="263" font-size="14" fill="#1a2332" font-weight="bold">营业执照_20260630.pdf</text>
|
||
<text x="170" y="285" font-size="13" fill="#8895aa">版本:V3 | 上传时间:2026-06-30 14:30 | AI识别:<tspan fill="#4caf50">✅ 已识别</tspan></text>
|
||
<text x="170" y="308" font-size="13" fill="#8895aa">识别字段:企业全称、信用代码、法人、地址、经营范围</text>
|
||
|
||
<!-- 营业执照操作按钮 -->
|
||
<rect x="770" y="245" width="160" height="34" rx="6" fill="url(#brandGrad)"/>
|
||
<text x="850" y="267" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">📤 更新营业执照</text>
|
||
<rect x="770" y="285" width="160" height="34" rx="6" fill="#e0e4ec"/>
|
||
<text x="850" y="307" text-anchor="middle" font-size="13" fill="#666">📋 查看历史版本</text>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ===== 2. 其他资质文件 ===== -->
|
||
<!-- ============================================================ -->
|
||
<line x1="140" y1="355" x2="1000" y2="355" stroke="#f0f2f7" stroke-width="1"/>
|
||
<text x="140" y="390" font-size="15" font-weight="bold" fill="#4a5568">📎 其他资质文件</text>
|
||
<text x="140" y="415" font-size="13" fill="#8895aa">ISO认证、安全生产许可证、产品检测报告、行业资质证书等</text>
|
||
|
||
<!-- 上传按钮 -->
|
||
<rect x="770" y="375" width="160" height="34" rx="6" fill="url(#brandGrad)"/>
|
||
<text x="850" y="397" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">+ 上传资质文件</text>
|
||
|
||
<!-- 文件列表 -->
|
||
<rect x="140" y="435" width="820" height="110" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
|
||
<!-- 表头 -->
|
||
<rect x="140" y="435" width="820" height="34" rx="8" fill="#f0f2f7"/>
|
||
<text x="170" y="457" font-size="12" font-weight="bold" fill="#4a5568">文件名称</text>
|
||
<text x="400" y="457" font-size="12" font-weight="bold" fill="#4a5568">上传时间</text>
|
||
<text x="550" y="457" font-size="12" font-weight="bold" fill="#4a5568">大小</text>
|
||
<text x="650" y="457" font-size="12" font-weight="bold" fill="#4a5568">操作</text>
|
||
|
||
<!-- 行1 -->
|
||
<rect x="150" y="475" width="800" height="32" rx="4" fill="#fff"/>
|
||
<text x="170" y="497" font-size="13" fill="#1a2332">ISO9001_质量管理体系认证.pdf</text>
|
||
<text x="400" y="497" font-size="13" fill="#666">2026-05-20</text>
|
||
<text x="550" y="497" font-size="13" fill="#666">2.3MB</text>
|
||
<text x="660" y="497" font-size="13" fill="#1565c0" text-decoration="underline">重命名</text>
|
||
<text x="740" y="497" font-size="13" fill="#f44336" text-decoration="underline">删除</text>
|
||
|
||
<!-- 行2 -->
|
||
<rect x="150" y="510" width="800" height="32" rx="4" fill="#fff"/>
|
||
<text x="170" y="532" font-size="13" fill="#1a2332">安全生产许可证_2024.pdf</text>
|
||
<text x="400" y="532" font-size="13" fill="#666">2025-12-10</text>
|
||
<text x="550" y="532" font-size="13" fill="#666">1.8MB</text>
|
||
<text x="660" y="532" font-size="13" fill="#1565c0" text-decoration="underline">重命名</text>
|
||
<text x="740" y="532" font-size="13" fill="#f44336" text-decoration="underline">删除</text>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ===== 3. 业绩证明文件 ===== -->
|
||
<!-- ============================================================ -->
|
||
<line x1="140" y1="570" x2="1000" y2="570" stroke="#f0f2f7" stroke-width="1"/>
|
||
<text x="140" y="605" font-size="15" font-weight="bold" fill="#4a5568">🏆 业绩证明文件</text>
|
||
<text x="140" y="630" font-size="13" fill="#8895aa">近三年同类项目合同、中标通知书、验收报告、用户评价等</text>
|
||
|
||
<!-- 上传按钮 -->
|
||
<rect x="770" y="590" width="160" height="34" rx="6" fill="url(#brandGrad)"/>
|
||
<text x="850" y="612" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">+ 上传业绩文件</text>
|
||
|
||
<!-- 文件列表 -->
|
||
<rect x="140" y="650" width="820" height="145" rx="8" fill="#fafbfc" stroke="#e0e4ec"/>
|
||
|
||
<!-- 表头 -->
|
||
<rect x="140" y="650" width="820" height="34" rx="8" fill="#f0f2f7"/>
|
||
<text x="170" y="672" font-size="12" font-weight="bold" fill="#4a5568">文件名称</text>
|
||
<text x="400" y="672" font-size="12" font-weight="bold" fill="#4a5568">上传时间</text>
|
||
<text x="550" y="672" font-size="12" font-weight="bold" fill="#4a5568">大小</text>
|
||
<text x="650" y="672" font-size="12" font-weight="bold" fill="#4a5568">操作</text>
|
||
|
||
<!-- 行1 -->
|
||
<rect x="150" y="690" width="800" height="32" rx="4" fill="#fff"/>
|
||
<text x="170" y="712" font-size="13" fill="#1a2332">滨化集团_催化剂供货合同_2025.pdf</text>
|
||
<text x="400" y="712" font-size="13" fill="#666">2025-08-15</text>
|
||
<text x="550" y="712" font-size="13" fill="#666">5.6MB</text>
|
||
<text x="660" y="712" font-size="13" fill="#1565c0" text-decoration="underline">重命名</text>
|
||
<text x="740" y="712" font-size="13" fill="#f44336" text-decoration="underline">删除</text>
|
||
|
||
<!-- 行2 -->
|
||
<rect x="150" y="725" width="800" height="32" rx="4" fill="#fff"/>
|
||
<text x="170" y="747" font-size="13" fill="#1a2332">万华化学_项目验收报告_2024.pdf</text>
|
||
<text x="400" y="747" font-size="13" fill="#666">2024-12-20</text>
|
||
<text x="550" y="747" font-size="13" fill="#666">3.2MB</text>
|
||
<text x="660" y="747" font-size="13" fill="#1565c0" text-decoration="underline">重命名</text>
|
||
<text x="740" y="747" font-size="13" fill="#f44336" text-decoration="underline">删除</text>
|
||
|
||
<!-- 行3 空状态提示 -->
|
||
<rect x="150" y="760" width="800" height="32" rx="4" fill="#fafafa"/>
|
||
<text x="170" y="782" font-size="13" fill="#b0bbcc">该分类下暂无其他文件</text>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ===== 右侧使用提示 ===== -->
|
||
<!-- ============================================================ -->
|
||
<rect x="720" y="25" width="240" height="160" rx="8" fill="#e3f2fd" stroke="#1565c0"/>
|
||
<text x="740" y="53" font-size="14" font-weight="bold" fill="#1565c0">📌 文件管理说明</text>
|
||
<text x="740" y="80" font-size="12" fill="#0d47a1">• 支持格式:PDF/JPG/PNG</text>
|
||
<text x="740" y="103" font-size="12" fill="#0d47a1">• 单个文件 ≤ 20MB</text>
|
||
<text x="740" y="126" font-size="12" fill="#0d47a1">• 文件总数量 ≤ 50 个</text>
|
||
<text x="740" y="149" font-size="12" fill="#0d47a1">• 删除后不可恢复,请谨慎操作</text>
|
||
<text x="740" y="172" font-size="12" fill="#0d47a1">• 投标报名时可一键引用已上传文件</text>
|
||
</svg>
|
||
```
|
||
|
||
#### (二)完整数据模型
|
||
|
||
##### 资质文件表 `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[显示具体驳回原因<br>+ 重新上传按钮]
|
||
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[图像真伪检测<br>(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失败,请人工介入。” |
|