# 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` 读文件内容(只能列目录)。