safety-eval-service-frontend/.cursor/skills/specs/10-通用业务组件使用规范/08-数据脱敏与解密/SKILL.md

133 lines
4.2 KiB
Markdown
Raw Normal View History

2026-08-14 16:54:03 +08:00
---
name: jjb-desensitization
description: 数据脱敏与解密查看规范。适用场景:展示加密的敏感数据(姓名、手机号、身份证号、图片等),并提供点击解密查看明文的能力。不限于表格,表单、详情页等场景均可使用。
---
# 通用业务组件使用规范
## 数据脱敏与解密
脱敏数据在后端以密文存储,前端拿到的数据包含脱敏后的展示文本和加密串。对于有解密权限的数据,用户可**点击解密查看明文**。
### 数据格式
后端返回的脱敏数据为对象结构,各场景统一使用此格式:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `flag` | `'TRUE'` \| `'FALSE'` | ✅ | 是否为加密数据。`'TRUE'` 时可解密查看,`'FALSE'` 时仅展示脱敏文本 |
| `label` | `string` | ✅ | 脱敏后的展示文本(如 `张**`、`138****1234` |
| `value` | `string` | ✅ | 加密串,用于调用解密接口 |
| `type` | `string` | ✅ | 数据类型枚举:`CHINESE_NAME` / `MOBILE_PHONE` / `ID_CARD` / `PICTURE` 等 |
```json
{
"flag": "TRUE",
"label": "张**",
"value": "encrypted_string_here...",
"type": "CHINESE_NAME"
}
```
> 若 `content` 为普通字符串(非对象),说明数据未脱敏,直接展示即可。
### 解密接口
#### 请求方式
```javascript
import { http } from '@cqsjjb/jjb-common-lib';
const res = await http.Post('@/user/desensitizations/client/decrypt/one', {
maskDTO: content, // 脱敏数据对象 { flag, label, value, type }
fieldKey: fieldKey // 字段标识,一般传表单字段名(如表单场景下的 name 属性值)
});
```
#### 参数说明
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `maskDTO` | `Object` | ✅ | 完整的脱敏数据对象,即接口返回的原始 content |
| `fieldKey` | `string` | ✅ | 字段标识,一般对应表单字段名。如表单字段 `name="userName"`,则传 `"userName"` |
#### 响应处理
```javascript
import { tools } from '@cqsjjb/jjb-common-lib';
if (res.success) {
const decodedData = tools.toObject(tools.toObject(res.data).maskDTO);
const plainText = decodedData?.label || null; // 解密后的明文
}
```
### 交互模式
适用于**任意场景**(表格、表单、详情页等),核心交互一致:
#### 脱敏态(默认)
- 展示 `label`(脱敏文本)
- `*` 号超过 5 个时折叠为 5 个(`*****`),避免展示过长
-`flag === 'TRUE'`,展示眼睛图标 `<EyeOutlined />`,提示可解密查看
-`flag === 'FALSE'`,不展示图标,仅展示脱敏文本
#### 解密态(点击后)
- 发起解密接口请求,期间 `<Spin>` 展示加载状态
- 解密成功后切换展示明文,图标变为 `<EyeInvisibleOutlined />`
- 点击隐藏图标切回脱敏态,再次查看使用缓存,不重复请求接口
#### 图片脱敏
- 展示不可识别的占位缩略图
- 点击预览时自动触发解密,展示真实图片
- 关闭预览时清理解密数据
```jsx
// 文本脱敏 — 交互骨架
const [showDecode, setShowDecode] = useState(false);
const [decodeData, setDecodeData] = useState(null);
const [loading, setLoading] = useState(false);
const decrypt = async () => {
if (decodeData) {
setShowDecode(true); // 已有缓存,直接展示
return;
}
setLoading(true);
const res = await http.Post('@/user/desensitizations/client/decrypt/one', {
maskDTO: content,
fieldKey: fieldKey,
});
setLoading(false);
if (res.success) {
const decoded = tools.toObject(tools.toObject(res.data).maskDTO);
setDecodeData(decoded?.label || null);
setShowDecode(true);
}
};
// 脱敏态
<Space>
<span>{maskedLabel}</span>
<Spin spinning={loading}>
{content.flag === 'TRUE' && <EyeOutlined onClick={decrypt} />}
</Spin>
</Space>
// 解密态
<Space>
<span>{decodeData}</span>
<EyeInvisibleOutlined onClick={() => setShowDecode(false)} />
</Space>
```
### 注意事项
- 解密接口调用可能较慢,**必须**有 Loading 状态反馈
- 解密成功后缓存结果,避免重复请求
- 解密失败时静默处理,不切换展示状态
- 该模式不仅限于表格列,表单回填、详情展示等场景均可复用