# DMS 接口文档 **服务**:`http://121.43.55.7:10081/dms`(`DMS_HOST` 可覆盖) **整理时间**:2026-09-16 > 📍 **本文引用的 artifacts / tools / RUNBOOK 都不在本仓库。** > 这些文件属于**另一个项目** `F:\yysk\AI_zhaoshang\DMS_Data_Migration\` > (本文就是从它的 `harness/docs/` 复制过来的)。本文里的链接已改为**绝对路径**指向那边; > 若那边目录挪了位置,链接会失效 —— 记得回来改。 **权威来源**:服务端自带 OpenAPI [`dms_openapi.json`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/harness/artifacts/dms_openapi.json) (`GET /dms/v3/api-docs`,实时抓取,比 Apifox 新) > **可信度标注**:本文每个接口都标了来源。 > - ✅ **实测** —— 本项目真实调用过,参数与响应已验证 > - 📄 **文档** —— 仅见于 SKILL.md / Apifox / OpenAPI,**本项目尚未调用过**,用前先小样本验证 --- ## 1. 通用约定 ### 1.1 鉴权 除登录外所有接口都要在 header 带 token: ``` token: {access_token} ``` `AuthInterceptor` 会拦截 `/content/**`、`/model/**`、`/column/**`、`/task/**`, 内部回调 OAuth 校验: ``` POST {oauth}/api/user/getUserByToken Header: token Body: serviceId=2, strUrl={当前请求URI} ``` **OAuth serviceId 固定传 2**(DMS 专用)。 token 通过环境变量传递,**不要写进任何文件**: ```bash export DMS_TOKEN='...' ``` ### 1.2 请求格式 | 场景 | Content-Type | |------|--------------| | 多数接口 | `application/x-www-form-urlencoded` | | 文件上传 | `multipart/form-data` | > ⚠️ **`model` / `column` 的写接口有个陷阱**:OpenAPI 声明它们是 query 参数, > 但把大 payload(如 41 字段的 `fieldList`)放 query string 会触发 **HTTP 400**。 > **一律用 form body**。详见 §5.3。 ### 1.3 响应格式 ```json { "code": 200, "content": ..., "message": "成功" } ``` | code | 含义 | |------|------| | `200` | 成功 | | `201` | 无权限 | | `202` | 数据不存在(栏目为空) | | `205` | 无数据 / 模型不存在 | | `212` | 无效 token | | `-1` | 参数错误 | | `214` | 数据错误 | > `-1`/`214`/`205` 的**具体触发条件**见 §5.4 —— 这几个是实测踩出来的,文档里没有。 --- ## 2. 接口总览 `GET /dms/v3/api-docs` 共 82 个端点。按业务分组: | 分组 | 主要端点 | |------|----------| | **栏目** | `/column/getColumnList`、`/column/getColumnById`、`/column/addColumn`、`/column/update`、`/column/delColumn`、`/column/selectChild` | | **模型** | `/model/addModel`、`/model/getModelById`、`/model/getModelByName`、`/model/getModelListByPage`、`/model/updateFieldList`、`/model/createModelByOldModel` | | **内容** | `/content/addContent`、`/content/updateContent`、`/content/selectContentList`、`/content/selectContentById`、`/content/delContentById`、`/content/importJSON` | | **字段库** | `/param/selectAll`、`/param/addParam` | | **单/多选框** | `/select/addSelects`、`/select/getSelectByType` | | **类别** | `/category/addCategorys`、`/category/selectByType` | | **任务** | `/task/addDataSync`、`/task/run`、`/task/taskLog` | | **权限** | `/permission/getColumnByPermission` | | **文件** | `/file/uploadFile` | --- ## 3. 本项目已实测的接口 ✅ 这 8 个是本次建栏目真实调用并验证过的。 ### 3.1 `GET /dms/v3/api-docs` ✅ 服务端自带的 OpenAPI 规范。**排查接口问题的第一站**,比 Apifox 权威。 ```bash curl -s "http://${DMS_HOST}/dms/v3/api-docs" -H "token: ${DMS_TOKEN}" -o dms_openapi.json ``` 返回 118KB JSON,含 82 个端点的参数定义。已存档 [`dms_openapi.json`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/harness/artifacts/dms_openapi.json)。 ### 3.2 `POST /column/getColumnList` ✅ **获取分级栏目树**(无参数)。 ```bash curl -s -X POST "http://${DMS_HOST}/dms/column/getColumnList" -H "token: ${DMS_TOKEN}" ``` 返回 `content` 为**顶级栏目数组**,每个节点: | 字段 | 说明 | |------|------| | `id` | 栏目 id | | `title` | 栏目名 | | `type` | `0` 分级栏目 / `1` 内容栏目 | | `tag` | 权限标签,同步 OAuth `permissionName` | | `parentId` | 父栏目 id | | `level` / `state` | 层级 / 状态 | | `columnList` | **子栏目数组**(递归) | > 实测返回 28 个顶级栏目。树是**嵌套**的,找子栏目要递归 `columnList`, > 不要只看顶层。 ### 3.3 `POST /model/getModelById` ✅ ```bash curl -s "http://${DMS_HOST}/dms/model/getModelById?modelId=1839" -H "token: ${DMS_TOKEN}" ``` > ⚠️ 参数走 **query string**(不是 form body),`modelId` 必须带。 `content` 字段: | 字段 | 说明 | |------|------| | `modelName` | 模型名,物理表为 `column_{modelName}` | | `modelAlias` | 中文别名 | | `type` | `1` 基础模型 / `2` 栏目模型 | | `fieldList` | **JSON 字符串**,见 §5.1 | | `searchField` / `sortField` | 实测为 `"[]"` | | `pageSize` | 实测为 `20` | | `authorId` / `authorName` | 创建者 | ### 3.4 `POST /model/getModelByName` ✅ ```bash curl -s "http://${DMS_HOST}/dms/model/getModelByName?modelName=yszs_page_view_model" -H "token: ${DMS_TOKEN}" ``` 参数走 query string。用于**幂等检查**:建模型前先查名字是否存在。 ### 3.5 `POST /model/getModelListByPage` ✅ ```bash curl -s "http://${DMS_HOST}/dms/model/getModelListByPage?type=2" -H "token: ${DMS_TOKEN}" ``` **唯一参数是 `type`**。`type=1` 基础模型,`type=2` 栏目模型。 > ⚠️ 不传 `type` 会返回 `code=205 无数据`(不是空列表)。 > 同名的 `/model/getModelList` 无论怎么传都返回 205 —— 用 `getModelListByPage`。 ### 3.6 `POST /param/selectAll` ✅ **查询所有初始字段**(无参数)。返回 28 个基础字段模板: | id | name | type | alias | |----|------|------|-------| | 1 | summary | text | 摘要 | | 2 | content | text | 正文内容 | | 3 | date_time | timestamp | 时间 | | 4 | text | text | 文本内容 | | 5 | int_num | integer | 整数类型 | | 6 | float_num | double | 浮点数类型 | | 7 | boolean | boolean | 布尔类型 | | 8 | check | check | 单选框 | | 9 | multiple_check | mcheck | 多选框 | | 11 | select | type | 类别 | | 12 | multiple_select | mtype | 多级类别 | | 20 | files | file | 文件 | | 21 | picture | varchar | 图片 | | … | | | | **这个表的 `name` 就是 `fieldList` 里 `frontType` 该填的值。** 见 §5.2。 ### 3.7 `PUT /model/addModel` ✅ **创建基础模型**。 参数(**form body**): | 参数 | 值 | 说明 | |------|-----|------| | `modelName` | 字符串 | 模型名,**必须唯一** | | `modelAlias` | 字符串 | 中文别名 | | `fieldList` | JSON 字符串 | 见 §5.1 | | `searchField` | `"[]"` | 字面量空数组,不是字段名 | | `sortField` | `"[]"` | 同上 | | `type` | `1` | 基础模型 | | `pageSize` | `20` | | | `authorId` | 整数 | **必传**,否则 `code=-1` | | `authorName` | 字符串 | **必传**,否则 `code=-1` | `authorId`/`authorName` 从 token 的 JWT payload 里取(`userId`/`username`)。 ### 3.7b `POST /model/updateProperty` ✅ — 更新模型 改模型字段(含别名)用这个。参数与 `addModel` 相同,外加 `id`(模型 id): | 参数 | 说明 | |------|------| | `id` | **模型 id**(必传) | | `modelName` / `modelAlias` / `fieldList` / `searchField` / `sortField` / `type` / `pageSize` | 与 `addModel` 同 | | `authorId` / `authorName` | 必传 | ⚠️ **端点选择有坑(实测)**: | 端点 | 结果 | |------|------| | `POST /model/updateProperty` | ✅ `code=200` | | `POST /model/updateFields` | ❌ HTTP 400 | | `POST /model/updateFieldList` | ❌ HTTP 500 | 名字最贴切的 `updateFieldList`(摘要「修改FieldList内容字段」)反而不能用。 工具:[`fix_field_alias.py`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/harness/tools/fix_field_alias.py) ### 3.8 `PUT /column/addColumn` ✅ **创建栏目。要求 `modelId` 指向的模型已存在**,否则 `code=205 模型不存在`。 参数(**form body**): | 参数 | 值 | 说明 | |------|-----|------| | `type` | `0`/`1` | `0` 分级栏目(仅目录,无内容表)/`1` 内容栏目 | | `parentId` | 整数 | 父栏目 id;`-1` 表示无父级 | | `tag` | 字符串 | **全局唯一**,同步 OAuth permissionName | | `title` | 字符串 | 栏目名 | | `content` | 字符串 | 说明 | | `state` | `0` | `0` 开启 / `1` 关闭 / `2` 销毁 | | `level` | `0` | 开放 | | `modelId` | 整数 | 基础模型 id | | `modelName` | 字符串 | 模型名 | | `authorId` / `authorName` | | **必传** | **副作用**: 1. DMS 把基础模型(type=1)**克隆**为栏目模型(type=2),字段自动加 `c_` 前缀 2. 生成物理表 `column_{栏目模型名}` 3. 联动 OAuth 注册权限(`Column.tag` → `permissionName`) **幂等做法**:先用 `getModelByName` 查模型是否存在,存在则跳过整个流程。 参考实现 [`create_dms_columns.py`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/harness/tools/create_dms_columns.py)。 --- ## 4. 迁移阶段将用到的接口 📄 > ⚠️ **以下均未实测。** 参数来自 `SKILL.md` / Apifox,OpenAPI 未描述(这些接口用 > `@RequestBody`,服务端规范抓不到)。**首次使用前请先在测试栏目验证。** ### 4.1 `POST /content/selectContentList` 📄 — 列表查询(最常用) ``` columnId=123&page=0&pageSize=20&search=[...]&orderBy=[...]&states=3 ``` 返回 `content.data`(数组)+ `content.count`(总数)。 > **`count` 是行数核对(校验标准 C1)的基准值。** > ⚠️ `states` 过滤会影响 count —— 核对时**不要带 `states`**,否则会误判"少了数据"。 **search 格式:** ```json [{"field":"c_title","searchType":2,"content":{"value":"政策"}}] ``` | searchType | 含义 | |------------|------| | 1 | 精确 | | 2 | 模糊 | | 3 | 区间(start/end) | | 4 | in 数组 | | 5 | between | **orderBy 格式:** ```json [{"field":"c_created_at","orderByType":2}] ``` `orderByType`:`1` 升序,`2` 降序。 **states(发布状态):** `0` 草稿 · `1` 待审 · `2` 审完 · `3` 发布 · `4` 销毁 · `5` 退回 多个用逗号:`states=2,3`。指定 `3` 则为发布态。 ### 4.2 `POST /content/selectContentListInfo` 📄 — 综合查询(详情) 同 `selectContentList` 参数,额外做**字典解析**(把 `check`/`type` 等字段的码值翻译成文本)。 排查详情问题时用它。 ### 4.3 `POST /content/selectContentById` 📄 — 单条查询 ``` columnId=123&contentId={uuid} ``` > `contentId` 是 **UUID 字符串**,不是数字 id。 ### 4.4 `POST /content/addContent` 📄 — 新增 | 参数 | 说明 | |------|------| | `columnId` | 栏目 id | | `modelId` | 栏目模型 id(type=2 的那个) | | `content` | **JSON 字符串**,字段用 `c_` 前缀 | ```bash curl -s -X POST "http://${DMS_HOST}/dms/content/addContent" \ -H "token: ${DMS_TOKEN}" \ -d 'columnId=1885' \ -d 'modelId=2030' \ -d 'content={"c_id":1,"c_business_code":"abc"}' ``` ### 4.5 `POST /content/updateContent` 📄 — 修改 参数同 `addContent`,`content` 里**必须带 `id`**(记录 uuid)。 ### 4.6 `DELETE /content/delContentById` 📄 — 删除 > 服务端 OpenAPI **确认存在**(`DELETE /content/delContentById`,摘要「删除内容」), > 但**参数未文档化**,Apifox 也未收录。 > **在生产栏目用之前,先在测试栏目确认它对 `columnId`/`contentId` 的确切要求。** ### 4.7 `POST /content/importJSON` 📄 — JSON 批量导入 > 服务端 OpenAPI **确认存在**(POST/PUT/GET/DELETE 都注册了), > 但参数未文档化。大批量迁移可能比逐条 `addContent` 快得多,**值得优先验证**。 ### 4.8 `POST /content/importBeautifiedExcel` 📄 — Excel 导入 `multipart/form-data`:`file`、`columnId`、`parseArray`、`titleParam`、`contentParam`。 ### 4.9 `POST /file/uploadFile` 📄 — 附件上传 `multipart/form-data`:`columnId`、`contentId`、`paraName`、`type`、`file`。 `type`:`0` 图 · `1` 音 · `2` 视 · `3` 文件。 --- ## 5. 建栏目实战(实测契约) 这几条是本次**踩出来的**,SKILL.md 与 Apifox 都没有。详见 [`DMS_MAPPING.md`](DMS_MAPPING.md) §5。 ### 5.1 `fieldList` 格式 外层是 dict,值是**转义后的 JSON 字符串**(fastjson 紧凑输出,**无空格**): ```json { "credit_code": "{\"name\":\"credit_code\",\"alias\":\"统一社会信用代码\",\"must\":true,\"index\":0,\"sequence\":0,\"customType\":\"\",\"describe\":\"\",\"defaultValue\":\"\",\"type\":\"text\",\"frontType\":\"varchar\",\"searchType\":\"1\",\"showParam\":\"name,alias,desc,type,front_type,must,default_value\"}" } ``` 字段属性: | 属性 | 要求 | |------|------| | `name` | **不加 `c_` 前缀** —— DMS 克隆时才加。自己加会变成 `c_c_xxx` | | `index` | **必须有**,从 `0` 起 | | `sequence` | 从 `0` 起 | | `type` | DMS 类型(`text`/`integer`/`double`/`boolean`/`timestamp`/`file`…) | | `frontType` | **不是 `"content"`** —— 取 §3.6 基础字段表的 `name` | | `id` | 新格式**不需要** | | `must` | 是否必填 | ### 5.2 PG → DMS 类型映射 | PG 类型 | `type` | `frontType` | 状态 | |---------|--------|-------------|------| | `character varying` / `character` | `text` | `varchar` | ✅ 实测 | | `text` | `text` | `text` | ✅ 实测 | | `jsonb` / `json` | `text` | `text` | ✅ 实测(DMS 无原生 JSON,**存 text 后不可用 search 检索内部**) | | `bigint` / `integer` / `smallint` | `integer` | `int_num` | ✅ 实测 | | `numeric` / `double precision` | `double` | `float_num` | ✅ 实测 | | `boolean` | `boolean` | `boolean` | ✅ 实测 | | (epoch 秒换算后) | `timestamp` | `date_time` | ✅ 实测 | | — | `check` / `mcheck` | 单/多选 | 📄 未用到,未验证 | | — | `type` / `mtype` | 类别 | 📄 未用到,未验证 | | — | `file` | `files` | 📄 未用到,未验证 | | — | `geometry` | 点/线/面 | 📄 未用到,未验证 | > `customType`/`defaultValue` 传空字符串 `""`。 ### 5.3 传输方式:必须用 form body OpenAPI 把 `addModel`/`addColumn` 的参数都声明为 `query`,但: | 方式 | 结果 | |------|------| | query string | 41 字段的 `fieldList` → **HTTP 400**(URL 过长) | | form body(`-d`) | ✅ 正常 | **结论:大 payload 一律用 form body,忽略 OpenAPI 的 `in: query`。** ### 5.4 错误码触发条件(实测) | code | 实测触发条件 | |------|--------------| | `-1` 参数错误 | `addModel` / `addColumn` **缺 `authorId`/`authorName`** | | `205` 模型不存在 | `addColumn` 时模型还没建 | | `214` 数据错误 | ① `frontType` 填了 `"content"` 而非 param 名
② 缺 `index`
③ fieldList 的 JSON 带空格(非紧凑) | | HTTP 400 | `fieldList` 放进了 query string | ### 5.5 建栏目顺序 ``` PUT /model/addModel 建基础模型 (type=1) ↓ POST /model/getModelByName 拿 modelId(幂等检查也用它) ↓ PUT /column/addColumn 建内容栏目 (type=1),DMS 自动克隆模型 → type=2 + c_ 前缀 ``` **不可颠倒** —— `addColumn` 要求模型已存在。 --- ## 6. 命令速查 ```bash export DMS_HOST=121.43.55.7:10081 export DMS_TOKEN='...' # 接口规范(排障第一站) curl -s "http://${DMS_HOST}/dms/v3/api-docs" -H "token: ${DMS_TOKEN}" # 栏目树 curl -s -X POST "http://${DMS_HOST}/dms/column/getColumnList" -H "token: ${DMS_TOKEN}" # 模型 curl -s "http://${DMS_HOST}/dms/model/getModelById?modelId=2030" -H "token: ${DMS_TOKEN}" curl -s "http://${DMS_HOST}/dms/model/getModelListByPage?type=2" -H "token: ${DMS_TOKEN}" # 基础字段库(frontType 的取值来源) curl -s -X POST "http://${DMS_HOST}/dms/param/selectAll" -H "token: ${DMS_TOKEN}" # 核对行数(C1 校验) curl -s -X POST "http://${DMS_HOST}/dms/content/selectContentList" \ -H "token: ${DMS_TOKEN}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "columnId=1885&page=0&pageSize=1" ``` --- ## 7. 相关文档 | 文档 | 内容 | |------|------| | [`dms_openapi.json`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/harness/artifacts/dms_openapi.json) | 服务端 OpenAPI 全量(82 端点),**最权威** | | [`SKILL.md`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/2.%20DMS-Manage/cursor-skill-dms/SKILL.md) | DMS 领域模型与常用流程 | | [`apifox-dms.md`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/2.%20DMS-Manage/cursor-skill-dms/apifox-dms.md) | Apifox 干净版 | | [`DMS_MAPPING.md`](DMS_MAPPING.md) | 本项目表→栏目映射 + 踩坑记录 | | [`RUNBOOK.md`](F:/yysk/AI_zhaoshang/DMS_Data_Migration/harness/docs/RUNBOOK.md) | 操作手册 | --- ## 8. 浏览服务器文件目录:`POST /file/getFiles` ✅ **用途**:列服务器上某个目录的内容。查「后端把文件存哪了」时用它 —— **这些路径不是 URL,用浏览器/curl 直接访问一律 404**(实测)。 ### 8.1 参数只有一个:`path`(form body) ```bash curl -s -X POST "http://121.43.55.7:10081/dms/file/getFiles" -H "token: ${DMS_TOKEN}" --data-urlencode "path=../../../../data/dms/dms_upload/yszs" ``` > ⚠️ **`path` 是相对 DMS 进程工作目录(Tomcat 的 bin)的**,不是文件系统绝对路径。 > 传绝对路径(`/data/...`)不会报错,但会返回「当前文件不是文件夹,无子文件」—— > 因为那是相对拼接后并不存在的路径。**这是最容易踩的一点。** ### 8.2 相对路径换算(实测) | 想要的位置 | 写成 | |---|---| | Tomcat bin(工作目录) | `.` | | Tomcat 根(有 `webapps*`、`conf`、`logs`、`uploads`…) | `..` | | `/usr/local` | `../..` | | `/`(根) | `../../../..` | | `/data` | `../../../../data` | | **`/data/dms/dms_upload`** | `../../../../data/dms/dms_upload` | | `/data/dms/dms_upload/yszs` | `../../../../data/dms/dms_upload/yszs` | ### 8.3 返回形态 ```json {"code":200,"message":"成功!","content":["../webapps_dms","../logs", ...]} ``` | 情况 | 返回 | |---|---| | 目录**有**内容 | `content` 是数组,元素是「带前缀的相对路径」 | | 目录**为空** | **没有 `content` 字段**(只有 `code`/`message`)—— 别把「字段缺失」当成出错 | | 传的是**文件**路径 | `content: ["当前文件不是文件夹,无子文件"]`(说明它只能列目录,**读不了文件内容**) | | 参数名写错(`dir`/`directory`/`filePath`…) | HTTP 500 | ### 8.4 实测到的目录布局(2026-09-17) ``` /data/dms/ ├── dms_upload/ ← DMS 上传根目录 │ ├── photo/ 7027 项 │ ├── file/ 6324 项 │ ├── video/ 3 项 │ ├── other/ 1 项 │ ├── yszs/ **空**(营商助手专用,新建未写入) │ └── *.json 7 个 uuid 命名的 json └── down_files/ ``` > 📌 结论:`/data/dms/dms_upload/yszs` 是**DMS 服务器上的真实目录**, > 目前**没有数据**;它既不是 URL,也不能通过 `getFiles` 读文件内容(只能列目录)。