docs(changelog-v2): 图标库 changelog 按团队样板逐端点重写(11接口详情+枚举+错误码汇总+业务边界)

这个提交包含在:
API Changelog Bot 2026-06-03 17:12:23 +08:00
父节点 7b8c70f514
当前提交 8b1b3cd1cb

查看文件

@ -1,30 +1,37 @@
# 【新增接口·管理后台 + 小程序】图标库模块全模块首次推送,11 接口
# 【新增接口·管理后台 + 小程序】图标库模块 11 接口(全模块首次推送 changelog
> **PR**: #3396(模块主体)+ #3398 / #3399mp 端点公开化)+ #3401(上线后审计加固)
> **服务**: hl-user-service | **更新时间**: 2026-06-03
>
> **存放目录**: `changelogs-v2/2026-06/`
> **影响范围**: 管理后台「图标库」管理页(分类 + 图标 CRUD + SVG 上传解析);小程序/前端 图标渲染(公开只读查询)
> **影响范围**: 管理后台「图标库」管理页(分类 + 图标 CRUD + SVG 上传解析);小程序 / 前端 图标渲染(公开只读查询)
---
## ⚠️ 关键说明
1. **两级结构(类似数据字典)**:一级「图标分类」(`icon_category`) + 二级「图标项」(`icon_item`)。图标项归属某分类编码 `categoryCode`
2. **SVG 直接存库(不走 OSS**:图标内容是 SVG 字符串,直存数据库 `svg_content`MEDIUMTEXT,上限 16MB,单图标后端限 256KB。前端拿到的 `svgContent` 即可直接渲染。
3. **上传解析与保存分两步**:先调 `POST /admin/icon/parse-svg` 上传 `.svg` 文件,后端读成字符串校验后**只返回字符串不落库**;前端拿到 `svgContent` 再连同 `color`/`size` 等填进保存表单调 `POST /admin/icon/item` 保存。
4. **SVG 安全校验(重要)**:上传/保存的 SVG 会经服务端 XXE-safe 解析校验,**拒绝**含 `<script>``<foreignObject>``on*` 事件属性onload/onclick…`javascript:` 外链的内容(返回错误码 `210757`);非合法 XML 或根元素不是 `<svg>` 返回 `210754`。**前端渲染 SVG 时仍建议做一次 sanitize 作为纵深防御。**
5. **mp 查询端点公开无需 token**`/mp/icon/categories``/mp/icon/items` 是 UI 参考数据(同字典),**无需登录、无需 token** 即可访问;只返回 `status=ACTIVE` 的分类与图标,且**分类被禁用时其图标也不再返回**。
6. **admin 端点需管理后台 JWT**`color` 为 hex`#FFCC00`),`size` 为像素整数,均为可选展示属性。
图标库是后台统一维护的一套 SVG 图标,结构类似数据字典:一级「图标分类」(`icon_category`) + 二级「图标项」(`icon_item`),图标项归属某分类编码 `categoryCode`
**SVG 直接存库(不走 OSS**:图标内容是 SVG 字符串,直存数据库 `svg_content`MEDIUMTEXT,单图标后端限 256KB。前端拿到 `svgContent` 直接渲染。
**新增图标分两步**:先调 §3.5 `POST /admin/icon/parse-svg` 上传 `.svg` 文件,后端读成字符串并做安全校验后**只返回字符串、不落库**;前端拿到 `svgContent` 再连同 `color` / `size` 等填进保存表单调 §3.6 `POST /admin/icon/item` 保存。
**mp 查询公开无需 token**§3.10 / §3.11 是 UI 参考数据(同字典),无需登录即可访问,只返回 `status=ACTIVE` 的分类与图标,且分类被禁用时其图标也不再返回。
**SVG 安全校验**:上传 / 保存的 SVG 经服务端 XXE-safe 解析校验,拒绝含 `<script>` / `<foreignObject>` 元素、`on*` 事件属性、`javascript:` 外链的内容;前端渲染时仍建议做一次 sanitize 作纵深防御。
---
## 1. 接口背景
图标库给运营在后台统一维护一套 SVG 图标,供小程序/前端按分类拉取渲染(如分类入口图标、标签图标等)。
运营在后台「图标库」页统一维护 SVG 图标,供小程序 / 前端按分类拉取渲染(如分类入口图标、标签图标等)。
- 管理后台「图标库」页:分类增删改查 + 图标增删改查 + 上传 SVG 解析。
- 小程序/前端:按分类拉启用图标列表渲染。
管理后台「图标库」页提供:
- 分类维护(列表 / 分页 / 新增 / 编辑 / 删除)
- 图标维护(分页 / 详情 / 新增 / 编辑 / 删除)
- 上传 SVG 文件解析为字符串
小程序 / 前端:按分类拉启用图标列表渲染(公开只读)。
---
@ -32,24 +39,24 @@
### 管理后台(`/admin/icon/**`,需 admin JWT
| # | 方法 | 路径 | 说明 |
|---|------|------|------|
| 1 | POST | `/admin/icon/category` | 保存分类id 空=新建,非空=修改) |
| 2 | GET | `/admin/icon/category/page` | 分类分页status / keyword 过滤) |
| 3 | GET | `/admin/icon/category/list` | 启用分类列表(下拉用) |
| 4 | DELETE | `/admin/icon/category/{id}` | 删除分类(其下有图标项时拒删) |
| 5 | POST | `/admin/icon/item` | 保存图标id 空=新建,非空=修改 |
| 6 | GET | `/admin/icon/item/page` | 图标分页categoryCode / keyword 过滤 |
| 7 | GET | `/admin/icon/item/{id}` | 图标详情(含 svgContent |
| 8 | DELETE | `/admin/icon/item/{id}` | 删除图标 |
| 9 | POST | `/admin/icon/parse-svg` | 上传 SVG 文件解析为字符串(不落库) |
| # | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|----------|------|
| 1 | POST | `/admin/icon/category` | 首推 | 保存分类id 空=新建,非空=修改) |
| 2 | GET | `/admin/icon/category/page` | 首推 | 分类分页status / keyword 过滤) |
| 3 | GET | `/admin/icon/category/list` | 首推 | 启用分类列表(下拉用) |
| 4 | DELETE | `/admin/icon/category/{id}` | 首推 | 删除分类(其下有图标项时拒删) |
| 5 | POST | `/admin/icon/parse-svg` | 首推 | 上传 SVG 文件解析为字符串(不落库 |
| 6 | POST | `/admin/icon/item` | 首推 | 保存图标id 空=新建,非空=修改 |
| 7 | GET | `/admin/icon/item/page` | 首推 | 图标分页categoryCode / keyword 过滤 |
| 8 | GET | `/admin/icon/item/{id}` | 首推 | 图标详情(含 svgContent |
| 9 | DELETE | `/admin/icon/item/{id}` | 首推 | 删除图标 |
### 小程序 / 前端(`/mp/icon/**`,公开无需 token
| # | 方法 | 路径 | 说明 |
|---|------|------|------|
| 10 | GET | `/mp/icon/categories` | 启用分类列表 |
| 11 | GET | `/mp/icon/items?categoryCode=xxx` | 某分类下启用图标列表 |
| # | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|----------|------|
| 10 | GET | `/mp/icon/categories` | 首推 | 启用分类列表 |
| 11 | GET | `/mp/icon/items` | 首推 | 某分类下启用图标列表 |
---
@ -59,100 +66,494 @@
**POST** `/admin/icon/category`
- **使用场景**:图标库分类新增 / 编辑
- **认证**:管理后台 JWT
- **幂等**:否(同 `categoryCode` 重复新建抛 210701
**Body 入参**`IconCategorySaveReqVO`
| 字段 | 类型 | 必填 | 校验 | 说明 |
|---|---|---|---|---|
| `id` | Long | ❌ | — | 空=新建,非空=修改 |
| `categoryCode` | String | ✅ | ≤64 | 分类编码,全局唯一,创建后不可改 |
| `categoryName` | String | ✅ | ≤100 | 分类名称 |
| `sort` | Integer | ❌ | — | 排序号,升序,默认 0 |
| `status` | String | ❌ | `ACTIVE`/`DISABLED` | 状态,默认 ACTIVE |
| `remark` | String | ❌ | ≤255 | 备注 |
| `categoryCode` | string | ✅ | `@Size(max=64)` | 分类编码(全局唯一,创建后不可改) |
| `categoryName` | string | ✅ | `@Size(max=100)` | 分类名称 |
| `sort` | int | ❌ | — | 排序号(升序),默认 0 |
| `status` | string | ❌ | `@Pattern(ACTIVE\|DISABLED)` | 状态,默认 ACTIVE |
| `remark` | string | ❌ | `@Size(max=255)` | 备注 |
```bash
curl -X POST "https://api.test.1814.love:9443/admin/icon/category" \
-H "Authorization: Bearer {adminToken}" -H "Content-Type: application/json" \
-d '{"categoryCode":"weather","categoryName":"天气","sort":1}'
# 返回: {"code":200,"message":"成功","data":2062087054098898945} // data=分类ID
**出参**`Result<Long>``data` = 分类 ID
**典型示例 请求**
```
POST /admin/icon/category
Authorization: Bearer <token>
Content-Type: application/json
{ "categoryCode": "weather", "categoryName": "天气", "sort": 1 }
```
### 3.2 上传 SVG 解析(保存图标前置步骤)
**POST** `/admin/icon/parse-svg``multipart/form-data`,字段名 `file`.svg 文件)
```bash
curl -X POST "https://api.test.1814.love:9443/admin/icon/parse-svg" \
-H "Authorization: Bearer {adminToken}" \
-F "file=@sunny.svg"
# 返回:
# {"code":200,"message":"成功","data":{
# "svgContent":"<svg viewBox=\"0 0 24 24\">...</svg>",
# "fileName":"sunny.svg","size":1234 }}
**典型示例 响应**
```json
{ "code": 200, "data": 2062087054098898945, "message": "成功" }
```
- 校验失败错误码:`210753` 文件为空 / `210754` 非合法 SVG非 XML 或根非 svg/ `210755` 超 256KB / `210757` 含不安全内容(脚本/事件/外链)。
**异常 响应**(编码重复):
```json
{ "code": 210701, "message": "图标分类编码已存在", "data": null }
```
### 3.3 保存图标
**错误码**400 参数校验 / **210701** 分类编码已存在 / **210702** 分类不存在(修改时 id 无效)/ 401 未登录
---
### 3.2 分类分页
**GET** `/admin/icon/category/page`
- **使用场景**:分类管理列表页
- **认证**:管理后台 JWT
- **幂等**:是(只读)
**Query 入参**`IconCategoryPageReqVO`
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `page` | int | ❌ | 1 | 页码 |
| `pageSize` | int | ❌ | 20 | 每页条数 |
| `status` | string | ❌ | — | 状态精确过滤ACTIVE / DISABLED |
| `keyword` | string | ❌ | — | 模糊匹配分类编码 / 名称 |
**出参**`Result<PageResult<IconCategoryRespVO>>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `records[].id` | string | 分类 IDLong 序列化为 String |
| `records[].categoryCode` | string | 分类编码 |
| `records[].categoryName` | string | 分类名称 |
| `records[].sort` | int | 排序号 |
| `records[].status` | string | 状态ACTIVE / DISABLED |
| `records[].remark` | string | 备注 |
| `records[].createTime` | datetime | 创建时间 |
| `total` / `page` / `pageSize` | int | 分页元数据 |
**错误码**400 参数校验 / 401 未登录
---
### 3.3 启用分类列表
**GET** `/admin/icon/category/list`
- **使用场景**:图标新增页的分类下拉
- **认证**:管理后台 JWT
- **幂等**:是
**入参**:无
**出参**`Result<List<IconCategoryRespVO>>`(仅 `status=ACTIVE`,按 sort 升序)
**错误码**401 未登录
---
### 3.4 删除分类
**DELETE** `/admin/icon/category/{id}`
- **使用场景**:删除空分类
- **认证**:管理后台 JWT
- **幂等**:是
**路径入参**
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | Long | ✅ | 分类 ID |
**出参**`Result<Void>``data: null`
**异常 响应**(分类下仍有图标项):
```json
{ "code": 210703, "message": "该分类下存在图标项,无法删除", "data": null }
```
**错误码****210702** 分类不存在 / **210703** 分类下存在图标项不可删 / 401 未登录
---
### 3.5 上传 SVG 解析(保存图标前置步骤)
**POST** `/admin/icon/parse-svg`
- **使用场景**:新增图标前上传 `.svg` 文件,拿到 SVG 字符串回填表单
- **认证**:管理后台 JWT
- **幂等**:是(仅解析,不落库)
**入参**multipart/form-data
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `file` | file | ✅ | SVG 文件(`.svg` |
**出参**`Result<IconSvgParseRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `svgContent` | string | 解析出的 SVG 字符串(回填保存表单) |
| `fileName` | string | 原始文件名 |
| `size` | long | 文件字节数 |
**典型示例 请求**
```
POST /admin/icon/parse-svg
Authorization: Bearer <token>
Content-Type: multipart/form-data; boundary=...
form-data: file=@sunny.svg
```
**典型示例 响应**
```json
{
"code": 200,
"data": {
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
"fileName": "sunny.svg",
"size": 1234
},
"message": "成功"
}
```
**错误码****210753** 文件为空 / **210754** 非合法 SVG非 XML 或根元素不是 svg/ **210755** 超 256KB / **210756** 文件读取失败 / **210757** 含不安全内容(脚本 / 事件 / 外链)/ 401 未登录
---
### 3.6 保存图标
**POST** `/admin/icon/item`
- **使用场景**:图标新增 / 编辑
- **认证**:管理后台 JWT
- **幂等**:否(同分类 `iconCode` 重复新建抛 210751
**Body 入参**`IconItemSaveReqVO`
| 字段 | 类型 | 必填 | 校验 | 说明 |
|---|---|---|---|---|
| `id` | Long | ❌ | — | 空=新建,非空=修改 |
| `categoryCode` | String | ✅ | ≤64 | 所属分类编码(新建时校验分类存在;修改时不可改) |
| `iconCode` | String | ✅ | ≤64 | 图标编码,同分类内唯一 |
| `name` | String | ✅ | ≤100 | 图标名称 |
| `svgContent` | String | ✅ | ≤256KB | SVG 字符串(来自 3.2 解析结果) |
| `color` | String | ❌ | ≤16 | 默认色值 hex,如 `#FFCC00` |
| `size` | Integer | ❌ | — | 默认尺寸(像素) |
| `keywords` | String | ❌ | ≤255 | 搜索关键词(逗号分隔) |
| `sort` | Integer | ❌ | — | 排序号,默认 0 |
| `status` | String | ❌ | `ACTIVE`/`DISABLED` | 状态,默认 ACTIVE |
| `remark` | String | ❌ | ≤255 | 备注 |
| `categoryCode` | string | ✅ | `@Size(max=64)` | 所属分类编码(新建校验分类存在;**修改时不可改** |
| `iconCode` | string | ✅ | `@Size(max=64)` | 图标编码(同分类内唯一;**修改时不可改** |
| `name` | string | ✅ | `@Size(max=100)` | 图标名称 |
| `svgContent` | string | ✅ | `@Size(max=262144)` | SVG 字符串(来自 §3.5 解析结果,经安全校验 |
| `color` | string | ❌ | `@Size(max=16)` | 默认色值 hex,如 `#FFCC00` |
| `size` | int | ❌ | — | 默认尺寸(像素) |
| `keywords` | string | ❌ | `@Size(max=255)` | 搜索关键词(逗号分隔) |
| `sort` | int | ❌ | — | 排序号,默认 0 |
| `status` | string | ❌ | `@Pattern(ACTIVE\|DISABLED)` | 状态,默认 ACTIVE |
| `remark` | string | ❌ | `@Size(max=255)` | 备注 |
> **注意SaveReqVO 全量快照语义)**:修改时请回填全部字段(先用 3.5 详情拉取再改),未传的可空字段会被覆盖为空。`categoryCode`/`iconCode` 创建后不可变,修改时会被忽略。
**出参**`Result<Long>``data` = 图标 ID
错误码:`210701` 分类编码已存在 / `210702` 分类不存在 / `210703` 分类下有图标不可删 / `210751` 同分类图标编码已存在 / `210752` 图标不存在。
**典型示例 请求**
```
POST /admin/icon/item
Authorization: Bearer <token>
Content-Type: application/json
### 3.4 图标分页
**GET** `/admin/icon/item/page?page=1&pageSize=20&categoryCode=weather&keyword=晴`
- `categoryCode` 精确过滤(可空);`keyword` 模糊匹配 iconCode/name/keywords可空
- 返回标准分页结构 `{records, total, page, pageSize}``records[]` 为完整图标 VO含 svgContent
### 3.5 图标详情
**GET** `/admin/icon/item/{id}` → 返回完整图标 VO含 svgContent/color/size/keywords/status 等)。
### 3.6【公开】小程序查询
**GET** `/mp/icon/categories` (无需 token
```bash
curl "https://api.test.1814.love:9443/mp/icon/categories"
# 返回: {"code":200,"data":[{"categoryCode":"weather","categoryName":"天气","sort":1}, ...]}
{
"categoryCode": "weather",
"iconCode": "sunny",
"name": "晴天",
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
"color": "#FFCC00",
"size": 24,
"keywords": "晴,太阳,sunny"
}
```
**GET** `/mp/icon/items?categoryCode=weather` (无需 token
```bash
curl "https://api.test.1814.love:9443/mp/icon/items?categoryCode=weather"
# 返回: {"code":200,"data":[
# {"iconCode":"sunny","name":"晴天","svgContent":"<svg ...>...</svg>","color":"#FFCC00","size":24}, ...]}
**典型示例 响应**
```json
{ "code": 200, "data": 2062087056506441730, "message": "成功" }
```
- 只返回 `ACTIVE` 分类下的 `ACTIVE` 图标;分类被禁用时该分类的图标不再返回。
- `categoryCode` 必填(缺失返 400
**异常 响应**SVG 含脚本 / 事件 / 外链):
```json
{ "code": 210757, "message": "SVG 含不安全内容(脚本/事件/外链),请清理后重试", "data": null }
```
**错误码**400 参数校验 / **210702** 分类不存在 / **210751** 同分类图标编码已存在 / **210752** 图标不存在(修改时 id 无效)/ **210754** SVG 非法 / **210755** SVG 超 256KB / **210757** SVG 含不安全内容 / 401 未登录
> **SaveReqVO 全量快照语义**:修改时请先用 §3.8 详情拉取再整体回填提交,未传的可空字段会被覆盖为空。`categoryCode` / `iconCode` 创建后不可变,修改时被忽略。
---
## 4. 前端对接要点
### 3.7 图标分页
1. **新增图标流程**:上传 `.svg` → 调 3.2 拿 `svgContent` → 填表单(补 color/size/分类/编码)→ 调 3.3 保存。
2. **渲染 SVG**:拿到 `svgContent` 直接内联渲染;**务必先做一次前端 sanitize**(后端已拒明显恶意内容,前端 sanitize 作纵深防御)。
3. **小程序拉图标**:先 `/mp/icon/categories` 拿启用分类,再用 `categoryCode``/mp/icon/items`。两端点均公开无需 token。
4. **状态语义**`ACTIVE` 启用 / `DISABLED` 禁用;mp 端只见 ACTIVE。
**GET** `/admin/icon/item/page`
- **使用场景**:图标管理列表页
- **认证**:管理后台 JWT
- **幂等**:是
**Query 入参**`IconItemPageReqVO`
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| `page` | int | ❌ | 1 | 页码 |
| `pageSize` | int | ❌ | 20 | 每页条数 |
| `categoryCode` | string | ❌ | — | 所属分类精确过滤 |
| `keyword` | string | ❌ | — | 模糊匹配图标编码 / 名称 / 关键词 |
**出参**`Result<PageResult<IconItemRespVO>>``records[]` 为完整图标 VO字段见 §3.8
**错误码**400 参数校验 / 401 未登录
---
> 本模块已在测试服完成端到端验证admin CRUD + 上传解析 + mp 公开查询 + 大 SVG 入库 + 恶意 SVG 拒绝)。如需调整字段或新增端点请反馈。
### 3.8 图标详情
**GET** `/admin/icon/item/{id}`
- **使用场景**:详情查看 / 编辑前预填
- **认证**:管理后台 JWT
- **幂等**:是
**路径入参**
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | Long | ✅ | 图标 ID |
**出参**`Result<IconItemRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | string | 图标 ID |
| `categoryCode` | string | 所属分类编码 |
| `iconCode` | string | 图标编码 |
| `name` | string | 图标名称 |
| `svgContent` | string | SVG 字符串 |
| `color` | string | 默认色值 hex |
| `size` | int | 默认尺寸(像素) |
| `keywords` | string | 搜索关键词 |
| `sort` | int | 排序号 |
| `status` | string | 状态ACTIVE / DISABLED |
| `remark` | string | 备注 |
| `createTime` / `updateTime` | datetime | 时间 |
**典型示例 响应**
```json
{
"code": 200,
"data": {
"id": "2062087056506441730",
"categoryCode": "weather",
"iconCode": "sunny",
"name": "晴天",
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
"color": "#FFCC00",
"size": 24,
"keywords": "晴,太阳,sunny",
"sort": 0,
"status": "ACTIVE",
"remark": null,
"createTime": "2026-06-03 16:00:00",
"updateTime": "2026-06-03 16:00:00"
},
"message": "成功"
}
```
**错误码****210752** 图标不存在 / 401 未登录
---
### 3.9 删除图标
**DELETE** `/admin/icon/item/{id}`
- **使用场景**:删除图标
- **认证**:管理后台 JWT
- **幂等**:是
**路径入参**
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | Long | ✅ | 图标 ID |
**出参**`Result<Void>``data: null`
**错误码****210752** 图标不存在 / 401 未登录
---
### 3.10【公开】启用分类列表
**GET** `/mp/icon/categories`
- **使用场景**:小程序 / 前端拉图标分类
- **认证****公开,无需 token**
- **幂等**:是
**入参**:无
**出参**`Result<List<IconCategorySimpleRespVO>>`(仅 `status=ACTIVE`,按 sort 升序)
| 字段 | 类型 | 说明 |
|---|---|---|
| `categoryCode` | string | 分类编码 |
| `categoryName` | string | 分类名称 |
| `sort` | int | 排序号 |
**典型示例 请求**
```
GET /mp/icon/categories
(无需 Authorization
```
**典型示例 响应**
```json
{
"code": 200,
"data": [
{ "categoryCode": "weather", "categoryName": "天气", "sort": 1 }
],
"message": "成功"
}
```
---
### 3.11【公开】某分类下启用图标列表
**GET** `/mp/icon/items`
- **使用场景**:小程序 / 前端按分类渲染图标
- **认证****公开,无需 token**
- **幂等**:是
**Query 入参**
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `categoryCode` | string | ✅ | 分类编码 |
**出参**`Result<List<IconItemSimpleRespVO>>`(仅 `status=ACTIVE` 的图标,且分类须为 ACTIVE
| 字段 | 类型 | 说明 |
|---|---|---|
| `iconCode` | string | 图标编码 |
| `name` | string | 图标名称 |
| `svgContent` | string | SVG 字符串(直接渲染) |
| `color` | string | 默认色值 hex |
| `size` | int | 默认尺寸(像素) |
**典型示例 请求**
```
GET /mp/icon/items?categoryCode=weather
(无需 Authorization
```
**典型示例 响应**
```json
{
"code": 200,
"data": [
{
"iconCode": "sunny",
"name": "晴天",
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
"color": "#FFCC00",
"size": 24
}
],
"message": "成功"
}
```
**错误码**400 `categoryCode` 缺失
---
## 6. 枚举 / 数据字典
### 6.1 状态(`status`
**所属字段**:分类 / 图标的 `status` | **类型**`String` | **必填**:❌(默认 ACTIVE
**`@Pattern` 强校验枚举**
| 值 | 含义 |
|---|---|
| `ACTIVE` | 启用 |
| `DISABLED` | 禁用 |
> mp 端只返回 ACTIVE 的分类与图标;分类 DISABLED 时其图标在 mp 端也不返回。
---
## 7. 错误码(全模块汇总)
| code | 含义 | 触发条件 | 涉及接口 |
|---|---|---|---|
| 400 | 参数校验失败 | 字段缺失 / 长度 / 枚举值不合法 | 所有写入 + §3.11 |
| 401 | 未登录 | admin JWT 无效或缺失 | 全部 admin 接口mp 端公开不需要) |
| **210701** | 分类编码已存在 | `categoryCode` 唯一约束 | §3.1 |
| **210702** | 分类不存在 | 分类 id / categoryCode 无效 | §3.1 §3.4 §3.6 |
| **210703** | 分类下存在图标项,不可删 | 删分类时其下仍有图标 | §3.4 |
| **210751** | 同分类图标编码已存在 | `(categoryCode, iconCode)` 唯一约束 | §3.6 |
| **210752** | 图标不存在 | 图标 id 无效 | §3.6 §3.8 §3.9 |
| **210753** | 上传 SVG 文件为空 | 文件为空 | §3.5 |
| **210754** | SVG 内容不合法 | 非合法 XML 或根元素不是 `<svg>` | §3.5 §3.6 |
| **210755** | SVG 内容过大 | 超过 256KB | §3.5 §3.6 |
| **210756** | SVG 文件读取失败 | 文件流读取异常 | §3.5 |
| **210757** | SVG 含不安全内容 | 含 `<script>` / `<foreignObject>` / `on*` 事件 / `javascript:` 外链 | §3.5 §3.6 |
> 错误码段位 210700-210799 归属图标库模块。
---
## 9. 业务边界
- ✅ **两级结构**:分类(`icon_category`+ 图标(`icon_item`),图标按 `categoryCode` 归属分类
- ✅ **SVG 直存库**`svg_content` MEDIUMTEXT 直存 SVG 字符串,不走 OSS;单图标后端限 256KB
- ✅ **上传解析与保存分离**§3.5 只解析返回字符串不落库;§3.6 才落库
- ✅ **唯一约束**`categoryCode` 全局唯一;`(categoryCode, iconCode)` 联合唯一
- ✅ **编码不可变**:图标的 `categoryCode` / `iconCode` 创建后不可改(修改时忽略)
- ✅ **SVG 服务端安全校验**XXE-safe 解析 + 拒绝脚本 / 事件 / 外链
- ✅ **mp 公开只读**§3.10 §3.11 无需 token,只返 ACTIVE;分类禁用其图标也不返
---
## 11. 影响评估
- **是否破坏向后兼容**:否(全新模块,无存量接口改动)
- **前端是否必须同步上线**:是(新功能,前端按本 changelog 对接管理页 + 渲染端)
- **影响已有数据**:无(新建 `icon_category` / `icon_item` 两表)
---
## 12. 注意事项
- **分页参数名**`page` / `pageSize`(不是 pageNo
- **Long 主键序列化**`id` 等 Long 字段 JSON 返回为 String,前端不要当 Number 解析
- **新增图标流程**:先 §3.5 上传解析拿 `svgContent` → 填表单(补 color / size / 分类 / 编码)→ §3.6 保存
- **渲染 SVG**:拿到 `svgContent` 直接内联渲染,**务必先做一次前端 sanitize**(后端已拒明显恶意内容,前端作纵深防御)
- **mp 端公开**§3.10 / §3.11 无需 token;先拉分类再用 `categoryCode` 拉图标
- **状态语义**`ACTIVE` 启用 / `DISABLED` 禁用,mp 端只见 ACTIVE
---
## 13. 关联 / 联系人
### 13.1 链接
- **同模块 PR**#3396(主体)/ #3398 / #3399mp 公开化)/ #3401(审计加固)
- **关联工单**#3390
### 13.2 联系人
- **后端负责人**: @wx
- **前端对接(管理后台 + 小程序)**: 待指派