Appearance
2. 接入准备
2.1 服务地址与协议
| 项 | 值 | 说明 |
|---|---|---|
| 服务地址 | http://<设备地址>:8085 | 端口来自 conf/app.conf 的 httpport = 8085 |
| 协议 | HTTP | 实测真机仅 8085 在监听 |
| 路径前缀 | /v1 | 所有接口均以该前缀开头 |
| 静态资源 / 前端页面 | GET / | 与 API 同源,前端是同一进程提供的页面 |
| CORS | 全放开 | 见 main.go 的 cors.Allow:AllowAllOrigins=true、AllowCredentials=true,且 ExposeHeaders 含 Set-Cookie |
| 未登录的响应 | HTTP 401 + HTML | 返回体是 <a href="/">Unauthorized</a>,不是 JSON,详见 2.3 |
【警告】不要想当然认为有 HTTPS。仓库
conf/app.conf里确实写了EnableHTTPS = true/HttpsPort = 8010,也开了 beego 管理端AdminAddr = "localhost"/AdminPort = 8088(EnableAdmin = true),但实测真机上8010与8088均未监听,只有8085是开的。不同版本 / 不同交付配置可能不一样,上线前请自行确认实际端口,不要照抄配置文件。
端口探测(只读):
bash
for p in 8085 8010 8088; do
(echo >/dev/tcp/<设备地址>/$p) >/dev/null 2>&1 && echo "$p open" || echo "$p closed"
done2.2 请求 / 响应公共约定
响应包装结构:DASS2 绝大多数接口由基类 BaseController 的两个方法输出,结构是固定的四字段:
| 字段 | 类型 | 说明 |
|---|---|---|
code | int | 0=成功;-1=业务失败;1=部分查询类接口的「无数据」错误;其它值见 6-错误码 |
msg | string | 成功恒为 "ok";失败恒为 "error" |
count | int | 记录数 / 计数。含义随接口变化,不总是"列表长度" |
data | 任意 | 业务数据。失败时的真实错误文案放在这里,不在 msg 里 |
json
{ "code": 0, "count": 2, "data": [ ], "msg": "ok" }源码(controllers/base.go):
go
// AjaxList → {"code","msg","count","data"}
func (self *BaseController) AjaxList(msg interface{}, msgCode int, count int64, data interface{})
// AjaxMsg → {"code","msg"}
func (self *BaseController) AjaxMsg(msg interface{}, msgno int)【警告】
msg里永远只有"ok"或"error",真正的错误原因在data里。 二次开发必须从data取错误文案,只打印msg会得到一句没用的error。
三个必须知道的坑:
坑 1:code == 0 不代表成功。 部分接口在失败时把错误码传了 0,于是出现 code:0 与 msg:"error" 并存。实测:
bash
# 查询一个不存在的采集服务配置
GET /v1/das/conf/99999json
{"code":0,"count":0,"data":"DAS服务配置错误\u003cQuerySeter\u003e no row found","msg":"error"}→ 判定成功的正确姿势是 code == 0 && msg == "ok",或者干脆以 msg == "ok" 为准。
坑 2:错误码不统一。 同样是失败,不同接口给出的 code 不同:
| 场景 | 实测响应 |
|---|---|
| 密码错误 | {"code":-1,"count":0,"data":"账号或密码错误","msg":"error"} |
| 非 POST 调登录 | {"code":-1,"count":0,"data":"只允许POST方法","msg":"error"} |
| 服务不存在(停止服务) | {"code":-1,"count":1,"data":"服务不存在\u003cQuerySeter\u003e no row found","msg":"error"} |
| 服务配置不存在 | {"code":0,"count":0,"data":"DAS服务配置错误…","msg":"error"} |
| 未登录(受保护接口) | HTTP 401 + <a href="/">Unauthorized</a> |
未登录调 /v1/isLogin/ | HTTP 302 → / |
未登录调 /v1/logout/ | HTTP 500(详情见 7-已知限制与坑) |
坑 3:返回值就是字符串,不是对象。 很多失败响应中 data 是一个纯文本字符串而非对象,例如 "data":"DAS服务配置错误..."。客户端若强类型反序列化成 struct 会直接失败。
Content-Type 约定:
| 场景 | 约定 |
|---|---|
| 默认 | application/json; charset=utf-8 |
| 登录 | application/x-www-form-urlencoded(表单) |
| 建服务 / 改配置 | application/x-www-form-urlencoded,同名字段可重复(表单字段即驱动配置项集合) |
| 文件上传(测点导入、上传点导入) | multipart/form-data |
| 文件下载(测点导出、模板下载、日志下载) | application/octet-stream,模板为 xlsx(实测响应体是 ZIP 头 PK) |
【注意】没有统一的 HTTP 状态码语义。业务失败时 HTTP 状态码通常仍是
200;只有鉴权失败(401/302)和个别异常(500)会不同。必须解析响应体,不能只看 HTTP 状态码。
2.3 鉴权
2.3.1 鉴权是怎么实现的
DASS2 的鉴权不在 main.go,而是在 models/timer/timer.go 里注册的一个全局过滤器:
go
beego.InsertFilter("/*", beego.BeforeRouter, filter.FilterBefore) // models/timer/timer.go:78过滤器本体在 models/filter/filter.go。它的判定顺序如下(照源码直译):
| 步骤 | 条件 | 结果 |
|---|---|---|
| 1 | 配置 Base.RequireLoginLogin == true 且 请求来自 127.0.0.1 / localhost | 直接放行(免鉴权) |
| 2 | URL 包含 login | 放行 |
| 3 | URL 等于 / | 放行 |
| 4 | URL 包含 socket.io | 放行 |
| 5 | URL 包含 logout | 放行 |
| 6 | URL 不包含 v1 | 放行 |
| 7 | Session 中没有 user | 401(isLogin 则 302) |
| 8 | 请求里没有以「用户名」为名的 Cookie,或该 Cookie 值不是 a|b|c 三段 | 401(isLogin 则 302) |
| 9 | Cookie 第三段(过期时间戳)小于当前时间 | 401(isLogin 则 302) |
| 10 | 以上全部通过 | 放行,并刷新 Cookie 的过期时间为「当前 + 24 小时」(滑动续期) |
2.3.2 登录
| 项 | 值 |
|---|---|
| 方法与路径 | POST /v1/login(路由注册为 /v1/login/,beego 自动兼容无尾斜杠) |
| 鉴权 | 免认证(URL 含 login,命中步骤 2) |
| Content-Type | application/x-www-form-urlencoded |
| 请求字段 | username、password(md5(明文) 的小写 hex,32 位) |
bash
# 计算密码 MD5(macOS)
PWD_MD5=$(md5 -q -s '<管理员明文密码>')
# 计算密码 MD5(Linux)
PWD_MD5=$(printf '%s' '<管理员明文密码>' | md5sum | cut -d' ' -f1)
curl -s -X POST "http://<设备地址>:8085/v1/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin" \
-d "password=$PWD_MD5"【警告】密码必须是
md5(明文)的小写 hex。传明文会返回账号或密码错误——而且这个报错和「密码真的错了」无法区分,极易误判。
实测成功响应(已截断 User.Menus):
json
{
"code": 0,
"count": 0,
"data": {
"User": {
"Id": 37,
"LoginName": "admin",
"RealName": "admin",
"Password": "",
"Role": "1",
"Platform": "arm",
"Model": "IA-2230",
"LastLogin": 1504115965,
"LastIp": "192.168.50.70",
"Menus": [ { "Id": 1, "AuthName": "设备状态", "EnglishName": "deviceState", "Auths": [ ] } ],
"Status": 1,
"UpdateTime": 1504115965
},
"IsFirst": false
},
"msg": "ok"
}| 字段 | 类型 | 说明 |
|---|---|---|
data.User | object | 登录用户对象(注意 Password 被清空) |
data.User.Menus | array | 该用户的菜单树,可用来反推产品的功能分区 |
data.IsFirst | bool | 是否为首次启动(InitFirstBoot() 的返回值);为 true 表示设备刚初始化 |
count | int | 登录接口恒为 0,不要拿它当成功标志 |
登录同时下发 3 个 Cookie(实测响应头):
http
Set-Cookie: beegosessionID=3df6f87c5276c43e6f8274d159830df5; Path=/; HttpOnly
Set-Cookie: lang=zh-CN; Expires=Mon, 17 Sep 2085 21:13:32 UTC; Max-Age=2147483647; Path=/
Set-Cookie: admin=37|3afddb6d2b26d2b7657c4bd601892d67|1504202365; Expires=Thu, 31 Aug 2017 17:59:25 UTC; Max-Age=86400; Path=/| Cookie | 作用 | 必需 |
|---|---|---|
beegosessionID | beego 服务端 Session 的凭据,对应过滤器步骤 7 的 session["user"] | 必需 |
admin | 以用户名为名的业务 Cookie,值 = 用户ID|authkey|过期时间戳 | 必需 |
lang | 界面语言,zh-CN / en-US | 可选 |
authkey 的构造(controllers/default.go):
go
authkey := libs.Md5([]byte(self.getClientIp() + "|" + user.Password + user.Salt))→ 即 md5(客户端IP + "|" + 密码哈希 + 盐)。注意:它绑定了登录时的客户端 IP。
2.3.3 两个 Cookie 缺一不可(实测)
| 测试 | 请求携带 | 结果 |
|---|---|---|
| A | 只有 admin=37|fake|99999999999 | 401 |
| B | 只有 beegosessionID=<有效会话> | 401 |
| C | 两者同时携带 | 200 OK |
→ 二次开发必须把登录响应的全部 Cookie 存下来并在后续请求中原样带上,只带其中一个必然 401。
bash
# 用 cookie jar 是最省事的做法
curl -s -c /tmp/dass2.jar ...登录...
curl -s -b /tmp/dass2.jar "http://<设备地址>:8085/v1/das"【警告】不要依赖 Cookie 过期时间自动管理。过滤器会在每次成功请求后把
adminCookie 的过期时间滑动续期 24 小时,所以只要持续调用就不会掉线;但会话本身(beegosessionID)的有效期由 beego 控制。正确做法是:捕获 401 → 重新登录一次 → 重放原请求,而不是定时重登或无限重试。
2.3.4 本机免鉴权(重要,可省掉整个登录流程)
若配置项 Base.RequireLoginLogin 为 true,则来自 127.0.0.1 / localhost 的请求完全跳过鉴权(过滤器步骤 1)。
- 配置文件键名:
conf/dasserver.conf的require.local.login,注释写作「本地无鉴权访问」;代码里对应字段却叫RequireLoginLogin(global/conf.go,默认false)。 - 实测:该设备
GET /v1/system/conf返回的Base.RequireLoginLogin为true。
json
{ "Base": { "RequireLoginLogin": true, "BoxCode": "default_0011", "MaxLengthServiceName": 11 } }【注意】如果你的程序与 DASS2 跑在同一台设备上(例如设备侧的采集程序、脚本或系统服务),大概率可以走
http://127.0.0.1:8085/v1/...免登录直接调用,实现最简单也最稳。
【警告】反过来看,这也是一个安全口子:命中该配置时,设备上任何本地进程都能无凭据调用全部/v1接口,且包含重建服务、清空测点、重启设备等破坏性操作。对外交付时建议评估是否关闭。
上表结论来自源码逻辑 + 实测配置值;由于本次没有该设备本机的执行权限,未在设备本机做环回调用的端到端实测,请在目标环境自行确认一次。
2.3.5 鉴权失败长什么样(务必按 HTML 处理)
FilterBefore 用的是 ctx.Redirect(401, "/"),所以:
bash
$ curl -i "http://<设备地址>:8085/v1/das"
HTTP/1.1 401 Unauthorized
<a href="/">Unauthorized</a>.| 情况 | HTTP | 响应体 |
|---|---|---|
| 未登录 / Cookie 过期,调普通接口 | 401 | <a href="/">Unauthorized</a>.(HTML,非 JSON) |
未登录调 /v1/isLogin/ | 302 | 重定向到 / |
未登录调 /v1/logout/ | 500 | 空(服务端 panic,见 7-已知限制与坑) |
【警告】客户端不能对 401 直接
json.Unmarshal——那会得到一个 HTML 解析错误,把「未登录」误报成「响应格式错误」。先看 HTTP 状态码,401/302 直接走重登逻辑。
2.3.6 免鉴权路径清单(源码结论)
以下路径不需要登录(命中过滤器前 6 条步骤):
/ (前端页面)
/v1/login /v1/login/ (含 "login")
/v1/logout /v1/logout/ (含 "logout")
/v1/socket.io/... (含 "socket.io")
任何不含 "v1" 的路径【警告】这里是子串匹配而非精确匹配:任何 URL 中只要含有
login子串就会被放行。这属于实现缺陷而非有意设计,二次开发不要利用它绕过鉴权。另外/v1/isLogin/不在放行名单里("isLogin"含的是大写L,匹配不到小写login),所以它需要鉴权、失败时给 302。
2.4 dasId 从哪来
{id}(下文统称 dasId)是本手册出现频率最高的参数,也是最容易被误解的一个。
| 项 | 结论 |
|---|---|
| 真实语义 | 采集服务在关系库 das_service 表的自增主键 |
| 不是什么 | 不是测点 ID、不是驱动 ID、不是设备序列号、不是端口 |
| 怎么获取 | GET /v1/das,取 data[].id(配合 data[].name 一起记) |
| 怎么传 | 绝大多数接口是 URL 路径参数,形如 /v1/point/import/{dasId} |
| 写死会失效 | 自增主键随建删服务变化,不要硬编码,每次先查列表 |
实测 GET /v1/das:
json
{
"code": 0,
"count": 2,
"data": [
{ "id": 36, "name": "jen", "type": "GB_Demo_Jenkins", "driverType": "das",
"port": 5480, "desc": "s", "host": "", "isLink": "true",
"nodeName": "W3.JEN", "status": "running", "total": 0, "timeout": 0 },
{ "id": 35, "name": "mqtt", "type": "OP2_MQTT_JC", "driverType": "upload",
"port": 9486, "desc": "m", "host": "", "isLink": "true",
"nodeName": "W3.MQTT", "status": "stop", "total": 0, "timeout": 0 }
],
"msg": "ok"
}| 字段 | 类型 | 说明 |
|---|---|---|
id | int | dasId,后续接口要用的就是这个 |
name | string | 服务名,全局唯一,创建后用于 /v1/das/conf/third、/v1/das/boot/{name} 等按名寻址的接口 |
type | string | 驱动名(如 GB_Demo_Jenkins),注意字段名叫 type 但存的是驱动名 |
driverType | string | 驱动类型 / 用途:das=采集、upload=上传 |
port | int | 该采集服务的本地监听端口(由系统自动分配,不传) |
nodeName | string | 实时库节点名,形如 W3.JEN(大写) |
status | string | running / stop / error |
isLink | string | 字符串 "true"/"false",不是布尔:true = 测点自动写入实时库 |
desc | string | 服务描述(创建时必填) |
total / timeout | int | 通道计数 / 超时;服务未运行时可能不返回 |
【注意】
/v1/das的count是服务总数;status来自内存态DasServiceStatus而非数据库,所以服务停了但进程还在时,这里可能仍显示running——需要以/v1/das/deviceLink/{id}的真实链路状态为准。
2.5 调用前的自检清单
| # | 检查项 | 通过标准 |
|---|---|---|
| 1 | 网络与端口 | 8085 可连(curl -i http://<设备地址>:8085/ 能返回页面) |
| 2 | 登录成功 | code == 0 && msg == "ok",且拿到 beegosessionID + <用户名> 两个 Cookie |
| 3 | 鉴权生效 | 带上两个 Cookie 调 GET /v1/das 返回 200 而不是 401 |
| 4 | 目标采集服务存在 | GET /v1/das 的 data[].id 里有你要的 dasId,且确认 status |
| 5 | 目标驱动存在 | GET /v1/driver 能看到目标驱动,且 driverType 与用途匹配(采集用 das,上传用 upload) |
| 6 | 驱动配置字段已知 | GET /v1/driver/conf/{driverId} 能拿到字段清单(key / textType / textData),建服务前必须先拿这个 |
| 7 | 实时库在线 | GET /v1/das/deviceLink/{dasId} 返回 data: true;否则测点导入/历史查询会异常 |
| 8 | 授权余量充足 | GET /v1/mqtt/baseInfo 的 licensePoint 与 userdPoint 留有余量 |
| 9 | 破坏性操作已确认 | 已读 7-已知限制与坑的「破坏性接口清单」,确认不会误删数据 / 误重启设备 |
帮助中心