18 KiB
【新增接口·管理后台 + 小程序】图标库模块 11 接口(全模块首次推送 changelog)
PR: #3396(模块主体)+ #3398 / #3399(mp 端点公开化)+ #3401(上线后审计加固) 服务: hl-user-service | 更新时间: 2026-06-03
存放目录:
changelogs-v2/2026-06/影响范围: 管理后台「图标库」管理页(分类 + 图标 CRUD + SVG 上传解析);小程序 / 前端 图标渲染(公开只读查询)
⚠️ 关键说明
图标库是后台统一维护的一套 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 文件解析为字符串
小程序 / 前端:按分类拉启用图标列表渲染(公开只读)。
2. 变更清单
管理后台(/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/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 |
首推 | 某分类下启用图标列表 |
3. 接口详情
3.1 保存分类
POST /admin/icon/category
- 使用场景:图标库分类新增 / 编辑
- 认证:管理后台 JWT
- 幂等:否(同
categoryCode重复新建抛 210701)
Body 入参(IconCategorySaveReqVO):
| 字段 | 类型 | 必填 | 校验 | 说明 |
|---|---|---|---|---|
id |
Long | ❌ | — | 空=新建,非空=修改 |
categoryCode |
string | ✅ | @Size(max=64) |
分类编码(全局唯一,创建后不可改) |
categoryName |
string | ✅ | @Size(max=100) |
分类名称 |
sort |
int | ❌ | — | 排序号(升序),默认 0 |
status |
string | ❌ | @Pattern(ACTIVE|DISABLED) |
状态,默认 ACTIVE |
remark |
string | ❌ | @Size(max=255) |
备注 |
出参:Result<Long>(data = 分类 ID)
典型示例 请求:
POST /admin/icon/category
Authorization: Bearer <token>
Content-Type: application/json
{ "categoryCode": "weather", "categoryName": "天气", "sort": 1 }
典型示例 响应:
{ "code": 200, "data": 2062087054098898945, "message": "成功" }
异常 响应(编码重复):
{ "code": 210701, "message": "图标分类编码已存在", "data": null }
错误码: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 | 分类 ID(Long 序列化为 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)
异常 响应(分类下仍有图标项):
{ "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)
典型示例 响应:
{
"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 | ✅ | @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) |
备注 |
出参:Result<Long>(data = 图标 ID)
典型示例 请求:
POST /admin/icon/item
Authorization: Bearer <token>
Content-Type: application/json
{
"categoryCode": "weather",
"iconCode": "sunny",
"name": "晴天",
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
"color": "#FFCC00",
"size": 24,
"keywords": "晴,太阳,sunny"
}
典型示例 响应:
{ "code": 200, "data": 2062087056506441730, "message": "成功" }
异常 响应(SVG 含脚本 / 事件 / 外链):
{ "code": 210757, "message": "SVG 含不安全内容(脚本/事件/外链),请清理后重试", "data": null }
错误码:400 参数校验 / 210702 分类不存在 / 210751 同分类图标编码已存在 / 210752 图标不存在(修改时 id 无效)/ 210754 SVG 非法 / 210755 SVG 超 256KB / 210757 SVG 含不安全内容 / 401 未登录
SaveReqVO 全量快照语义:修改时请先用 §3.8 详情拉取再整体回填提交,未传的可空字段会被覆盖为空。
categoryCode/iconCode创建后不可变,修改时被忽略。
3.7 图标分页
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 未登录
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 | 时间 |
典型示例 响应:
{
"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)
典型示例 响应:
{
"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)
典型示例 响应:
{
"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_contentMEDIUMTEXT 直存 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 / #3399(mp 公开化)/ #3401(审计加固)
- 关联工单:#3390
13.2 联系人
- 后端负责人: @wx
- 前端对接(管理后台 + 小程序): 待指派