OpenAPI 第三方开发者手册
OpenAPI 第三方开发者手册
适用人群:接入 GodoCloud 平台的第三方系统开发者
1. 架构总览
① AppKey + 签名 → POST /openapi/token → access_token(应用凭证)
② access_token + 用户名密码 → POST /openapi/user/login → user_token(用户JWT)
③ user_token → Authorization: Bearer → 调用 /api/v1/<业务原路径>💡
user_token不能直接打到站内/user/*路由,必须通过 OpenAPI 业务网关前缀/api/v1/透传。 例如要列文件,业务原路径是/user/files/read,第三方实际调用:GET /api/v1/user/files/read?path=/网关会按顺序完成:user_token 校验 → 应用状态 / IP 白名单 / 限流 / scope 校验 → 重写路径 → 调用业务 handler,第三方不需要关心站内中间件细节。
2. 快速开始(5 步对接)
① 向 GodoCloud 管理员申请 AppKey / AppSecret
② POST /openapi/token → 拿到 access_token
③ POST /openapi/user/login → 用用户名密码换取 user_token
④ Authorization: Bearer user_token → 调用 /api/v1/<业务原路径>
⑤ POST /openapi/user/token/refresh → token 即将过期前续期3. 签名算法(HMAC-SHA256)
3.1 算法步骤
- 收集业务参数(当前只有
app_key),排除sign、timestamp、nonce。 - 按 key 字典序排序,以
key=value用&拼接,不做 URL encode。 - 末尾追加
×tamp=<秒>&nonce=<随机串>。 sign = HEX( HMAC_SHA256(signString, AppSecret) )(小写 hex)。
3.2 示例
待签名串: app_key=ak_abc123×tamp=1717150000&nonce=8f3a7c9e
sign = hmac_sha256(上述串, AppSecret) → 64位小写hex3.3 各语言签名函数
JavaScript / Node.js
const crypto = require('crypto')
function buildSign(params, timestamp, nonce, secret) {
const keys = Object.keys(params).filter(k => k && k !== 'sign').sort()
let raw = keys.map(k => `${k}=${params[k]}`).join('&')
if (raw) raw += '&'
raw += `timestamp=${timestamp}&nonce=${nonce}`
return crypto.createHmac('sha256', secret).update(raw).digest('hex')
}Go
func buildSign(params map[string]string, ts, nonce, secret string) string {
keys := make([]string, 0)
for k := range params { if k != "" && k != "sign" { keys = append(keys, k) } }
sort.Strings(keys)
var b strings.Builder
for _, k := range keys {
if b.Len() > 0 { b.WriteByte('&') }
b.WriteString(k + "=" + params[k])
}
if b.Len() > 0 { b.WriteByte('&') }
b.WriteString("timestamp=" + ts + "&nonce=" + nonce)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(b.String()))
return hex.EncodeToString(h.Sum(nil))
}Python
import hmac, hashlib
def build_sign(params, ts, nonce, secret):
keys = sorted(k for k in params if k and k != 'sign')
raw = '&'.join(f'{k}={params[k]}' for k in keys)
if raw: raw += '&'
raw += f'timestamp={ts}&nonce={nonce}'
return hmac.new(secret.encode(), raw.encode(), hashlib.sha256).hexdigest()3.4 校验规则
| 字段 | 规则 |
|---|---|
timestamp | Unix 秒;与服务器偏差 ≤ 300 秒 |
nonce | 任意随机串;5 分钟内不可重复 |
sign | 64 位小写 hex |
3.5 调试签名
POST /openapi/sign/verify(参数与 /openapi/token 一致),返回服务端计算的签名串供比对:
{
"code": 0,
"data": {
"sign_str": "app_key=ak_abc×tamp=1717150000&nonce=xxx",
"expected_sign": "服务端计算的sign",
"received_sign": "你提交的sign",
"match": true
}
}4. OpenAPI 网关接口
接口总览
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | /openapi/ping | 无 | 连通性测试 |
| POST | /openapi/sign/verify | 签名 | 调试签名 |
| POST | /openapi/token | 签名 | 获取 access_token |
| POST | /openapi/user/login | access_token | 换取 user_token |
| POST | /openapi/user/token/refresh | access_token + user_token | 续期 user_token |
| GET | /openapi/user/info | access_token + user_token | 获取用户信息 |
4.1 POST /openapi/token — 获取 access_token
请求(form-urlencoded 或 query):
| 参数 | 必填 | 说明 |
|---|---|---|
app_key | ✅ | 管理员分配 |
timestamp | ✅ | Unix 秒 |
nonce | ✅ | 随机串 |
sign | ✅ | HMAC-SHA256 签名 |
响应:
{
"code": 0, "msg": "success",
"data": {
"access_token": "3a2b9c8e...",
"expires_in": 7200,
"expires_at": 1717157200,
"scopes": "*",
"token_type": "Bearer"
}
}4.2 POST /openapi/user/login — 换取 user_token
Header:
| 字段 | 说明 |
|---|---|
X-App-Key | AppKey |
X-Access-Token | access_token |
Body (JSON):
{
"username": "alice",
"password": "sha256后的密码或明文",
"plain": true
}plain=true:password 传原始明文(仅 IP 白名单内推荐)plain=false(默认):password 传sha256(原始密码)的 hex
响应:
{
"code": 0, "data": {
"user_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"user": { "id": 100, "username": "alice", "nickname": "Alice", "email": "", "phone": "", "avatar": "", "dept_id": 3, "role_id": 2 }
}
}4.3 POST /openapi/user/token/refresh — 续期
Header:X-App-Key + X-Access-Token + Authorization: Bearer <旧 user_token>
{ "code": 0, "data": { "user_token": "新的JWT", "token_type": "Bearer" } }4.4 GET /openapi/user/info — 用户信息
Header:同 4.3。返回用户详细资料(含角色、部门、岗位等)。
4.5 GET /openapi/ping — 连通性
{ "code": 0, "data": { "server_time": 1717150000, "version": "1.0" } }5. 业务接口(user_token 可调用)
拿到 user_token 后,统一在业务原路径前加 /api/v1/ 即可。 GodoCloud 内部任何 NeedAuth=1 的 /user/*、/workflow/*、/store/* 等都能透传调用。
5.1 调用方式
业务原路径例如 /user/files/read?path=/,第三方应调用:
GET /api/v1/user/files/read?path=/
Authorization: Bearer <user_token>支持的 token 传递方式(任选其一):
| 方式 | 示例 |
|---|---|
| Authorization(推荐) | Authorization: Bearer <user_token> |
| Header(旧版) | token: <user_token> |
| URL Query | ?token=<user_token> |
⚠️ 网关禁止透传以下前缀(防越权 / 防递归):
/openapi/、/api/v1/、/admin/、/install/。 命中将返回40301 scope forbidden。
5.2 业务接口完整清单
下表 Path 列是业务原路径,第三方实际调用时统一加
/api/v1前缀。 例:/user/files/read→GET /api/v1/user/files/read
📁 文件 / 网盘(/user/files/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/files/desktop | 获取桌面文件 |
| GET | /user/files/read | 读取目录 |
| GET | /user/files/stat | 获取文件信息 |
| POST | /user/files/chmod | 修改文件权限 |
| GET | /user/files/exists | 判断文件是否存在 |
| GET | /user/files/readfile | 读取文件内容(带密) |
| GET | /user/files/unlink | 删除文件 |
| GET | /user/files/pwd | 加密文件 |
| GET | /user/files/unpwd | 解密文件 |
| GET | /user/files/clear | 清空目录 |
| GET | /user/files/rename | 文件重命名 |
| POST | /user/files/mkdir | 创建目录 |
| GET | /user/files/rmdir | 删除目录 |
| GET | /user/files/restore | 恢复文件 |
| GET | /user/files/copyfile | 复制文件 |
| GET | /user/files/favorite | 收藏文件 |
| POST | /user/files/writefile | 写文件 |
| POST | /user/files/appendfile | 追加文件 |
| GET | /user/files/search | 搜索文件 |
| GET | /user/files/zip | 压缩文件 |
| GET | /user/files/unzip | 解压文件 |
| POST | /user/files/share | 共享文件 |
| GET | /user/files/sharelist | 共享文件列表 |
| GET | /user/files/knowledge | 将文件夹加入知识库 |
| POST | /user/files/ask | 询问知识库 |
| GET | /user/files/avatarlist | 获取用户头像列表 |
| GET | /user/files/saveavatar | 保存用户头像 |
🤝 协同编辑(/user/files/collaboration/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/files/collaboration/editusers | 获取编辑用户列表 |
| GET | /user/files/collaboration/timeline | 获取编辑时间线 |
| GET | /user/files/collaboration/sharers | 获取共享用户列表 |
| POST | /user/files/collaboration/recover | 从历史记录恢复文件 |
📤 文件上传(/user/upload*)
| Method | Path | 说明 |
|---|---|---|
| POST | /user/upload | 单文件上传 |
| POST | /user/multiupload | 多文件上传 |
| POST | /user/verifyupload | 校验文件上传 |
📝 表单(/user/form*、/user/editdata)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/forms | 获取表单列表 |
| GET | /user/formfields | 获取表单字段列表 |
| GET | /user/datalist | 获取表单数据列表 |
| POST | /user/editdata | 编辑用户数据 |
🔄 工作流(/workflow/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /workflow/list | 获取工作流列表 |
| GET | /workflow/addbefore | 工作流添加表单前 |
| POST | /workflow/adddata | 工作流添加数据 |
| GET | /workflow/getdata | 工作流获取数据 |
| GET | /workflow/datalist | 工作流数据列表 |
| GET | /workflow/myauditdatalist | 我审核的数据列表 |
| GET | /workflow/formdata | 获取前端表格 |
| GET | /workflow/getview | 获取流程图 |
| POST | /workflow/getusersbydeptids | 根据部门获取用户 |
| GET | /workflow/mychecklist | 需要审核的工作流列表 |
| GET | /workflow/getcheckdata | 审核人单条数据 |
| POST | /workflow/checkflowdata | 审核工作流节点 |
| GET | /workflow/getcopytomelist | 发送给我的抄送 |
| GET | /workflow/getcopyfrommelist | 我发送的抄送 |
| POST | /workflow/resubmitflowformdata | 重新编辑工作流审批 |
| GET | /workflow/getflowcheckcomments | 获取审核意见 |
| GET | /workflow/getFlowRelation | 获取流程类型和父子关系 |
| GET | /workflow/getParentFlowChildren | 获取父流程的子流程 |
| GET | /workflow/getWorkFlowListByType | 按类型获取流程列表 |
| GET | /workflow/exportdata | 导出申请数据 |
👤 用户 / 组织(/user/user*、/user/dept*、/user/role*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/list | 按部门或角色获取用户列表 |
| POST | /user/searchuser | 搜索用户 |
| GET | /user/sharelist | 分享用户列表 |
| GET | /user/depts | 按PID获取部门列表 |
| GET | /user/deptsall | 获取全部部门列表 |
| GET | /user/roles | 获取角色列表 |
👤 个人资料(/user/profile*、/user/savepwd)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/getprofile | 获取个人资料 |
| POST | /user/updateprofile | 更新个人资料 |
| POST | /user/savepwd | 修改密码 |
📖 词条 / 知识库(/user/entry*、/user/tag*、/user/category*)
| Method | Path | 说明 |
|---|---|---|
| POST | /user/entry/create | 创建词条 |
| GET | /user/entry/list | 获取词条列表 |
| GET | /user/entry/detail | 获取词条详情 |
| GET | /user/entry/category | 获取词条分类列表 |
| POST | /user/entry/view | 增加词条浏览量 |
| GET | /user/entry/top | 获取置顶词条 |
| GET | /user/entry/recommended | 获取推荐词条 |
| GET | /user/entry/hot | 获取热门词条 |
| GET | /user/tag/list | 获取标签列表 |
| GET | /user/category/list | 获取词条分类列表 |
| GET | /user/category/entry/list | 获取分类下词条列表 |
📊 图表(/user/charts/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/charts/list | 图表列表 |
| GET | /user/charts/getdata | 获取图表数据 |
📰 通知(/user/news/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/news/list | 查看通知列表 |
📖 字典(/user/dict/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/dict/cache/cate | 按分类编码获取字典缓存 |
| GET | /user/dict/cache/field | 按字段编码获取字典缓存 |
| GET | /user/dict/cate/tree | 获取字典分类树 |
| GET | /user/dict/cate/fields | 按分类ID获取字段列表 |
🌍 区域(/user/areas/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /user/areas/list | 获取区域列表(三级联动) |
| GET | /user/areas/tree | 获取区域树形结构 |
| GET | /user/areas/all | 获取全部区域(扁平映射) |
🏪 应用商店(/store/*)
| Method | Path | 说明 |
|---|---|---|
| GET | /store/storelist | 应用商店列表 |
🔗 钉钉空间
| Method | Path | 说明 |
|---|---|---|
| GET | /user/ding/space | 钉钉空间文件列表 |
6. 错误码
| 错误码 | 说明 | 处理建议 |
|---|---|---|
| 0 | 成功 | — |
| 40001 | 参数错误 | 检查必填参数 |
| 40101 | 应用不存在 | 检查 app_key |
| 40102 | 应用已禁用 | 联系管理员启用 |
| 40103 | IP 不在白名单 | 联系管理员加白 |
| 40110 | 时间戳过期 | 同步服务器时间 |
| 40111 | nonce 重复 | 更换随机串 |
| 40112 | 签名错误 | 参照第 3 节排查 |
| 40113 | token 无效 | 重新获取 |
| 40114 | token 过期 | 重新获取 |
| 40301 | scope 无权限 / 透传路径被禁 | 联系管理员授权;或检查是否调用了 /openapi/ /admin/ /install/ 等禁止透传的路径 |
| 40302 | 用户已禁用 | 联系管理员 |
| 40401 | 用户不存在 | 检查用户名 |
| 40402 | 密码错误 | 检查密码 |
| 42901 | 请求频率超限 | 降低频率或联系管理员提额 |
| 50001 | 服务器内部错误 | 联系管理员查看日志 |
7. 完整对接示例
7.1 Node.js 示例
const crypto = require('crypto')
const axios = require('axios')
const BASE = 'https://your-godocloud.com'
const APP_KEY = 'ak_xxxxxx'
const APP_SECRET = 'sk_xxxxxx'
// ① 签名函数
function buildSign(params, ts, nonce) {
const keys = Object.keys(params).filter(k => k && k !== 'sign').sort()
let raw = keys.map(k => `${k}=${params[k]}`).join('&')
if (raw) raw += '&'
raw += `timestamp=${ts}&nonce=${nonce}`
return crypto.createHmac('sha256', APP_SECRET).update(raw).digest('hex')
}
async function main() {
// ② 获取 access_token
const ts = Math.floor(Date.now() / 1000).toString()
const nonce = crypto.randomBytes(8).toString('hex')
const sign = buildSign({ app_key: APP_KEY }, ts, nonce)
const tokenRes = await axios.post(`${BASE}/openapi/token`, null, {
params: { app_key: APP_KEY, timestamp: ts, nonce, sign }
})
const accessToken = tokenRes.data.data.access_token
// ③ 用用户名密码换取 user_token
const loginRes = await axios.post(`${BASE}/openapi/user/login`,
{ username: 'alice', password: '原始密码', plain: true },
{ headers: { 'X-App-Key': APP_KEY, 'X-Access-Token': accessToken } }
)
const userToken = loginRes.data.data.user_token
// ④ 调用业务接口(业务原路径前面统一加 /api/v1)
const files = await axios.get(`${BASE}/api/v1/user/files/read`, {
params: { path: '/' },
headers: { Authorization: `Bearer ${userToken}` }
})
console.log('文件列表:', files.data)
// ⑤ 调用工作流接口
const workflows = await axios.get(`${BASE}/api/v1/workflow/list`, {
headers: { Authorization: `Bearer ${userToken}` }
})
console.log('工作流列表:', workflows.data)
}
main().catch(console.error)7.2 Python 示例
import hmac, hashlib, time, random, requests
BASE = 'https://your-godocloud.com'
APP_KEY = 'ak_xxxxxx'
APP_SECRET = 'sk_xxxxxx'
def build_sign(params, ts, nonce):
keys = sorted(k for k in params if k and k != 'sign')
raw = '&'.join(f'{k}={params[k]}' for k in keys)
if raw: raw += '&'
raw += f'timestamp={ts}&nonce={nonce}'
return hmac.new(APP_SECRET.encode(), raw.encode(), hashlib.sha256).hexdigest()
# ① 获取 access_token
ts = str(int(time.time()))
nonce = ''.join(random.choices('abcdef0123456789', k=16))
sign = build_sign({'app_key': APP_KEY}, ts, nonce)
token_res = requests.post(f'{BASE}/openapi/token', params={
'app_key': APP_KEY, 'timestamp': ts, 'nonce': nonce, 'sign': sign
})
access_token = token_res.json()['data']['access_token']
# ② 换取 user_token
login_res = requests.post(f'{BASE}/openapi/user/login', json={
'username': 'alice', 'password': '原始密码', 'plain': True
}, headers={'X-App-Key': APP_KEY, 'X-Access-Token': access_token})
user_token = login_res.json()['data']['user_token']
# ③ 调用业务接口(业务原路径前面统一加 /api/v1)
headers = {'Authorization': f'Bearer {user_token}'}
files = requests.get(f'{BASE}/api/v1/user/files/read', params={'path': '/'}, headers=headers)
print('文件列表:', files.json())
# 调用工作流
wf = requests.get(f'{BASE}/api/v1/workflow/list', headers=headers)
print('工作流:', wf.json())7.3 Go 示例
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
const (
base = "https://your-godocloud.com"
appKey = "ak_xxxxxx"
appSecret = "sk_xxxxxx"
)
func buildSign(params map[string]string, ts, nonce string) string {
keys := make([]string, 0)
for k := range params { if k != "" && k != "sign" { keys = append(keys, k) } }
sort.Strings(keys)
var b strings.Builder
for _, k := range keys {
if b.Len() > 0 { b.WriteByte('&') }
b.WriteString(k + "=" + params[k])
}
if b.Len() > 0 { b.WriteByte('&') }
b.WriteString("timestamp=" + ts + "&nonce=" + nonce)
h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(b.String()))
return hex.EncodeToString(h.Sum(nil))
}
func main() {
// ① 获取 access_token
ts := fmt.Sprintf("%d", time.Now().Unix())
nonceBuf := make([]byte, 8)
rand.Read(nonceBuf)
nonce := hex.EncodeToString(nonceBuf)
sign := buildSign(map[string]string{"app_key": appKey}, ts, nonce)
resp, _ := http.PostForm(base+"/openapi/token", url.Values{
"app_key": {appKey}, "timestamp": {ts}, "nonce": {nonce}, "sign": {sign},
})
var tokenResp map[string]interface{}
json.NewDecoder(resp.Body).Decode(&tokenResp)
accessToken := tokenResp["data"].(map[string]interface{})["access_token"].(string)
// ② 换取 user_token(省略 JSON body 构造,参考 net/http 标准库)
fmt.Println("access_token:", accessToken)
// 接下来参照 Python/Node.js 示例的流程继续即可
}8. 注意事项
- 时间同步:确保你的服务器时间与 GodoCloud 服务器偏差 < 5 分钟。
- nonce 唯一性:建议使用 UUID 或随机 hex(≥ 16 字节)。
- 密码安全:生产环境推荐
plain=false,客户端先做sha256(密码)再传输。 - HTTPS:所有请求必须走 HTTPS,避免凭证泄露。
- token 存储:access_token 和 user_token 都应安全存储,不要暴露在前端 URL 或日志中。
- 续期策略:user_token 快过期前调用
/openapi/user/token/refresh,无需用户重新输入密码。 - 错误重试:遇到 42901(限流)应退避重试;40113/40114 需重新获取 token。
- 业务接口参数:具体参数请通过 GodoCloud 管理后台的"接口文档"或联系管理员获取。
9. 常见问题
Q1:为什么需要两次 token? A:access_token 证明第三方应用身份;user_token 证明用户身份。两者职责不同,安全隔离。
Q2:user_token 能调用管理员接口吗? A:不能。user_token 只允许通过 /api/v1/ 网关访问业务接口;/admin/*、/install/* 等前缀被网关明确拒绝。
Q2.1:为什么不能直接 Authorization: Bearer <user_token> 打到 /user/files/read? A:那是站内 Web 路由,会被站内 JWT 中间件拦截(session/cookie 流程),且无法走应用层校验(IP 白名单、scope、限流)。 所有外部调用必须走 /api/v1/ 网关,由 OpenAPI 模块统一鉴权后转发,业务侧不需要任何改造。
Q3:用户修改密码后 user_token 还能用吗? A:在 token 自然过期前仍可使用;过期后需用新密码重新 login。
Q4:如何批量操作多个用户? A:每个用户单独调用 /openapi/user/login 获取各自的 user_token,然后分别调用业务接口。
Q5:签名调试不通过怎么办? A:调用 /openapi/sign/verify,对比 sign_str 和 expected_sign 与你本地计算是否一致。