feat: DocSystem (MxsDoc) 文件操作 skill

从公司 DocSystem / MxsDoc V2.02.71 逆向出 HTTP 接口,封装为零依赖 Python CLI,
让 AI 可以完全通过命令行操作文档系统的文件系统。

功能:
- 浏览:ls / tree / repos / auth
- 读写:mkdir(-p) / touch / upload / download / cat
- 变更:rename / rm / mv / cp
- 检索:search(文件名 + 正文)

要点:
- 不指定仓库时默认操作「研发管理配置库」(id=2);首个位置参数以 / 开头时自动判定为路径
- 凭证不入库,从 ~/.docsys/config.json 或环境变量读取
- 会话 cookie 缓存于 ~/.docsys/session.json,过期自动重登
This commit is contained in:
Cheney
2026-10-20 15:50:57 +08:00
commit 8ae1694b6d
4 changed files with 1431 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
# DocSystem(MxsDoc)HTTP 接口文档
目标系统:MxsDoc / DocSystem V2.02.71
地址:`http://172.1.4.31:8100/DocSystem`
前端入口:`/DocSystem/web/index.html` → `projects.html` → `project.html?vid=<仓库ID>`
本文档由前端 JS 逆向 + 逐接口实测得出,用于支撑 `scripts/docsys.py`。
---
## 通用约定
- 全部接口为 `POST`,表单编码 `application/x-www-form-urlencoded; charset=UTF-8`
(仅 `uploadDoc.do` 为 `multipart/form-data`)。
- 必须带请求头 `X-Requested-With: XMLHttpRequest`。
- 会话靠 Cookie 维持:`JSESSIONID`、`dstoken`(= base64(密码))、`dsuser`(= 用户名)。
`rememberMe=1` 时服务端下发 `dstoken`/`dsuser` 用于自动登录。
- 响应统一 JSON:`{"status":"ok"|"fail","data":...,"msgInfo":"...","msgData":...}`。
少数接口(如 `getDocContent.do`)返回纯文本。
- 节点类型:`type=1` 文件,`type=2` 目录。
- 时间字段为毫秒时间戳。
## 鉴权
### POST `User/login.do`
| 参数 | 说明 |
|---|---|
| `userName` | 邮箱或手机号 |
| `pwd` | 密码经 base64 编码后的字符串(标准 base64,非 URL-safe) |
| `rememberMe` | `1` 记住登录,`0` 不记住 |
成功返回 `data` 为用户对象(`id`/`name`/`realName`/`role`/…)。
### POST `User/logout.do`
无参数。
### POST `User/getLoginUser.do`
无参数,返回当前登录用户。可用于探测会话是否有效。
---
## 仓库
### POST `Repos/getReposList.do`
无参数。返回 `data` 为仓库数组:
`{id, name, info, type, verCtrl, path, owner, ...}`
实测可见仓库:
| id | 名称 | 说明 |
|---|---|---|
| 1 | 意见与反馈 | 当前账号可新增/编辑,不可删除/下载 |
| **2** | **研发管理配置库** | **CLI 默认仓库**(`DEFAULT_REPO_NAME`),基本只读,部分目录可下载 |
| 5 | 常用工具 | 只读 |
| 6 | 临时仓库 | 无访问权限 |
> **约定**:`scripts/docsys.py` 中若省略仓库参数,默认操作 id=2「研发管理配置库」。
> 判定规则:第一个位置参数以 `/` 或 `./` 开头则视为路径(仓库取默认),否则视为仓库 id/名称。
### POST `Repos/getDocSysConfig.do`
无参数,返回系统配置(如 `defaultReposStorePath`)。
---
## 目录列举
### POST `Repos/getSubDocList.do`
| 参数 | 说明 |
|---|---|
| `vid` | 仓库 id |
| `docId` | 目录 docId(**实测服务端不使用**,仅随请求透传) |
| `path` | 目录路径,仓库根为 `""`,其余以 `/` 结尾 |
| `name` | 目录名。服务端取 `path + name` 作为目标目录 |
| `sort` | 排序方式,`0` 名称 |
| `needLockState` | `1` |
| `shareId` | 可传空 |
**关键结论**:目标目录 = `path + name`。传 `path="a/"`、`name=""` 与 `path=""`、`name="a"`
等价;但传 `path="a/"` 且 `name="a"` 会变成 `a/a` 从而返回空。
返回 `data` 为节点数组,节点字段:
`docId, pid, path, name, type, level, size, checkSum, createTime, latestEditTime,
latestEditorName, creatorName, localRootPath, reposPath, ...`
其中 `path` 是**节点所在目录**的路径,节点完整路径 = `path + name`。
---
## 权限
### POST `Repos/getUserDocAuth.do`
| 参数 | 说明 |
|---|---|
| `docId` | 目录 docId,仓库根为 `0` |
| `reposId` | 仓库 id |
| `path` | 目录自身路径 |
| `name` | 目录名 |
返回 `data`:`{access, addEn, editEn, deleteEn, downloadEn, isAdmin, uploadSize, ...}`
实测(账号 `qian.huang@sunyard.com`,仓库 1):
```
access=1 addEn=1 editEn=1 deleteEn=0 downloadEn=0 isAdmin=0
```
注意:**rename / move 在服务端按"删除 + 新增"鉴权**,因此即使 `addEn=1`,
`deleteEn=0` 时重命名和移动同样会被拒绝,报错为「您没有该目录的删除权限」。
### 权限是逐目录授予的,不是仓库级
同一个仓库里不同目录的权限可以完全不同。实测:
| 仓库 / 目录 | access | addEn | editEn | deleteEn | downloadEn |
|---|---|---|---|---|---|
| 2 研发管理配置库 `/`(根) | 1 | 0 | 0 | 0 | 0 |
| 2 `/Sunyard/Docs/开发流程` | 1 | 0 | 0 | 0 | **1** |
| 2 `/Sunyard/Dev/产品/系统研发/工具` | 1 | **1** | **1** | **1** | **1** |
| 1 意见与反馈 `/`(根) | 1 | 1 | 1 | 0 | 0 |
所以**不能凭仓库名推断权限**,写操作前务必先查 `getUserDocAuth`。
---
## 写操作
### POST `Doc/addDoc.do`(新建文件 / 目录)
| 参数 | 说明 |
|---|---|
| `reposId` | 仓库 id |
| `type` | `1` 文件,`2` 目录 |
| `pid` | 父目录 docId,仓库根为 `0` |
| `path` | **父目录完整路径 + `/`**,仓库根为 `""` |
| `name` | 新节点名 |
| `content` | 文本文件内容;建目录传空 |
返回 `data` 为新节点,`msgData` 为 `isNewNode`。
> ⚠️ **实测:`content` 被服务端忽略。** 即使传了内容,建出来也是 0 字节空文件
> (响应 `size=0`,随后 `getDocContent` 读不到任何内容)。
> 要创建带内容的文件必须改用 `Doc/uploadDoc.do`(CLI 的 `touch` 已自动走这条路)。
### POST `Doc/renameDoc.do`
| 参数 | 说明 |
|---|---|
| `reposId` | 仓库 id |
| `docId` | 节点 docId |
| `pid` | 父目录 docId |
| `type` | 节点类型 |
| `level` | 节点层级 |
| `path` | 节点**自身所在目录**路径 |
| `name` | 原名 |
| `dstName` | 新名 |
### POST `Doc/deleteDoc.do`
| 参数 | 说明 |
|---|---|
| `reposId` | 仓库 id |
| `docId` | 节点 docId |
| `pid` | 父目录 docId |
| `path` | 节点自身所在目录路径 |
| `name` | 节点名 |
删除目录时为递归删除。
### POST `Doc/moveDoc.do`
| 参数 | 说明 |
|---|---|
| `reposId` | 仓库 id |
| `docId` | 节点 docId |
| `type` | 节点类型 |
| `srcLevel` | 源层级 |
| `dstLevel` | 目标层级(父目录 level + 1;移到仓库根为 `0`) |
| `srcPid` | 源父目录 docId |
| `dstPid` | 目标父目录 docId,仓库根为 `0` |
| `srcPath` | 源目录路径 |
| `srcName` | 源名 |
| `dstPath` | **目标父目录完整路径 + `/`**,仓库根为 `""` |
| `dstName` | 目标名(可与源名不同,即改名 + 移动) |
### POST `Doc/copyDoc.do`
参数同 `moveDoc`。
---
## 上传 / 下载
### POST `Doc/uploadDoc.do`(multipart/form-data)
| 字段 | 说明 |
|---|---|
| `reposId` | 仓库 id |
| `path` | **目标目录完整路径 + `/`** |
| `name` | 远端文件名 |
| `size` | 文件字节数 |
| `checkSum` | 文件 MD5 |
| `uploadFile` | 文件内容(文件域) |
| `commitMsg` | 提交说明,可空 |
| `dirPath` | 文件夹上传标记,可空 |
大文件走分片:`chunkNum` / `cutSize` / `chunkIndex` / `chunkSize` / `chunkHash` /
`combineDisabled=1`,分片全部传完再调 `Doc/combineChunks.do`。
### POST `Doc/downloadDocPrepare.do`
| 参数 | 说明 |
|---|---|
| `reposId`, `docId`, `pid`, `path`, `name` | 目标节点 |
| `downloadType` | `1` 单文件 |
| `downloadList` | 可空 |
返回 `data`:`{path, name, targetPath, targetName}`,`msgData` 为 `deleteFlag`。
若 `msgData == 5` 表示目录正在打包,用 `data.id` 轮询
`Doc/queryDownloadPrepareTask.do`(参数 `taskId`),每 2 秒一次,直到 `msgData == 6`。
### GET `Doc/downloadDoc.do?...`
查询串:`vid, path, name, targetPath, targetName, deleteFlag, encryptEn=1`(值需 URL 编码)。
> 实测:直接调 `Doc/getDocFileLink.do` 拿到的链接指向 `tmp/History/rdata/<commitId>/...`
> 历史目录,该文件通常不存在,会返回 500。**必须走 `downloadDocPrepare` 流程。**
---
## 读取 / 搜索
### POST `Doc/searchDoc.do`
| 参数 | 说明 |
|---|---|
| `searchWord` | 关键词 |
| `sort` | `0` |
| `reposId` | 仓库 id |
| `path` | 限定目录范围,全库传 `""` |
返回 `data` 为命中项,`hitType`:`1` 文件名命中,`3` 正文命中;
`content` 为正文的 base64 编码;`dataEx` 为命中总数。
### POST `Doc/getDocContent.do`
| 参数 | 说明 |
|---|---|
| `reposId`, `docId`, `pid`, `path`, `name` | 目标文件 |
返回**纯文本**,响应以 `ok` 开头,后面才是文件正文,解析时须去掉前两个字符。
### POST `Doc/getDoc.do`
| 参数 | 说明 |
|---|---|
| `reposId`, `docId`, `pid`, `path`, `name` | 目标节点 |
| `docType` | `0` 不读取正文和备注 |
返回节点元信息。
---
## 已探明但未使用的接口
- `Doc/lockDoc.do` / `Doc/unlockDoc.do` — 文件锁定
- `Doc/executeDoc.do`、`Doc/refreshDoc.do`
- `Doc/checkChunkUploaded.do`、`Doc/combineChunks.do`、`Doc/getMaxThreadCount.do` — 分片上传
- `Doc/getDocFileLink.do`、`Doc/getZipDocFileLink.do` — 预览/下载链接(下载场景不可靠,见上)
- `Bussiness/getDocOfficeLink.do`、`Bussiness/getDocCadLink.do` — 在线预览
- `Bussiness/addDocShare.do`、`Doc/getDocShare.do` — 分享
- `Doc/querySystemLog.do` — 操作日志
- `Repos/getReposInitMenu.do` — 目录树初始化菜单