Appearance
7. 已知限制与坑
本章按严重程度排序。每条写清「现象 / 原因 / 规避方式」。带「实测」标记的均已在本手册的基线设备上真实复现。
7.0 先读这三条(否则会白做)
如果你只读本章的一部分,请先读这三条——它们会让基于错误前提开发出来的功能静默失效:
| # | 坑 | 一句话后果 | 详见 |
|---|---|---|---|
| 1 | /v1/calcModel/gethistorybyid 返回伪造的随机数据 | 任何基于它的历史分析、报表、趋势判断都毫无意义,而且接口返回成功、不报错 | 7.32 |
| 2 | code == 0 不代表成功 | code:0 与 msg:"error" 会并存;只判 code 会把失败当成功 | 7.2 |
| 3 | /v1/point/{id} 的 {id} 是测点 UID,不是 dasId | 「查某服务的测点」写成 /v1/point/{dasId} 会静默返回空列表 | 7.7 |
另外两条也建议先记住:鉴权失败返回的是 HTML 而不是 JSON(7.1),点表导入成功时 count 恒为 0(7.9)。
7.1 【严重】鉴权失败返回的是 HTML,不是 JSON
现象:未登录或 Cookie 过期时,/v1 接口返回:
http
HTTP/1.1 401 Unauthorized
<a href="/">Unauthorized</a>.原因:鉴权由 models/filter/filter.go 的 FilterBefore 实现,失败时走的是 ctx.Redirect(401, "/"),直接输出 HTML 重定向体,完全没有经过 JSON 响应封装。
规避:
- 先判断 HTTP 状态码,
401/302直接进入「重新登录」分支,不要解析响应体。 - 客户端若统一
json.Unmarshal响应体,会把「未登录」误报成「响应格式错误」,掩盖真实原因。 /v1/isLogin/失败返回 302(不是 401),/v1/logout/未登录时直接 500——三者行为都不同,不要用同一套判断。
7.2 【严重】code == 0 不代表成功
现象(实测):查询一个不存在的采集服务配置:
bash
GET /v1/das/conf/99999json
{"code":0,"count":0,"data":"DAS服务配置错误\u003cQuerySeter\u003e no row found","msg":"error"}原因:部分控制器调用 AjaxList(MessageError, 0, 0, ...) 时把错误码参数写成了 0,于是成功码与失败码撞车。这是后端实现不一致,不是个例。
规避:
- 判定成功用
code == 0 && msg == "ok",或干脆只认msg == "ok"。 - 不要写
if resp.Code == 0 { 成功 }。
7.3 【严重】msg 里没有有效信息,真正原因在 data
现象:失败时 msg 恒为 "error",客户端只打印 msg 会得到一句毫无信息量的输出。
原因:AjaxList(msg, code, count, data) 的 msg 参数在设计上只传 consts.MessageOk("ok") 或 consts.MessageError("error"),真实文案塞在 data 里。
规避:始终从 data 读错误原因。注意 data 的类型不稳定:
| 形态 | 出现场景 |
|---|---|
| 字符串 | 大多数错误,如 "服务不存在"、"账号或密码错误" |
| 对象数组 | 点表导入的逐行错误,元素形如 {"Flag":"...","PN":"..."} |
| 对象 | 成功时的业务数据 |
| 数组 | 成功时的列表 |
| 布尔 / 数字 | 个别接口,如 /v1/das/deviceLink/{id} 返回 true/false |
→ 反序列化时建议把 data 声明为「任意类型」,或先按接口逐个适配,不要全局强类型。
7.4 【严重】密码字段是密文,回写时容易写坏
现象:GET /v1/driver/conf/{id} 与 GET /v1/das/conf/{id} 中,textType 为 ispassword / password 的配置项,其 value 不是明文,而是 base64(AES(明文))。
原因:后端在返回前对密码类字段做了加密(driver.go 中 utils.AesEncrypt + base64.StdEncoding)。
规避:
- 只读展示:先
base64解码,再用同一 AES 密钥解密。 - 回写:必须重新
base64(AES(明文))后上送;把读到的密文原样回写虽然「看起来能跑」,但一旦密钥/实现变化就会把密码字段写坏。 - 【警告】AES 密钥硬编码在二进制里(
utils/aes.go的AesKey)。这意味着该加密不提供真正的机密性,只能防肩窥。不要把它当作安全边界。
7.5 【严重】写操作接口注册成 *:方法,用 GET 也会真的执行
现象(源码确认):以下路由用 beego 的 *:Method 形式注册,意味着 GET / POST / PUT / DELETE 任意方法都会触发同一个写动作:
/v1/point/import/{id} /v1/point/export/{id} /v1/point/empty/{id}
/v1/point/clean/{id} /v1/point/forceDelete/{id}
/v1/point/multimport/{id} /v1/upoint/import/{id} /v1/upoint/empty/{id}
/v1/das/stop/{id} /v1/das/boot/{id} /v1/das/reboot/{id}
/v1/point/insert /v1/point/import/local/{id}原因:路由表用了 "*:Import" 这类写法(见 routers/router.go),而非绑定具体方法。
规避:
- 脚本里一律用 POST,并把 URL 视作「动作」而非「资源」。
- 【警告】这意味着浏览器预取、爬虫、健康检查、URL 扫描器只要 GET 到这些地址,就会真的清空测点或重启服务。生产环境建议在网关层限制方法。
7.6 【严重】破坏性接口清单(务必逐条确认)
| 接口 | 后果 | 是否可逆 |
|---|---|---|
POST /v1/point/empty/{dasId} | 清空该服务全部采集测点(isLink=true 时连实时库的点一起删) | 否 |
POST /v1/upoint/empty/{dasId} | 清空该服务全部上传点 | 否 |
POST /v1/point/clean/{任意值} | 清理游离/垃圾点数据。路径参数被忽略,是全局操作 | 否 |
POST /v1/point/forceDelete/{任意值} | 实为空实现(什么都不删,见 7.26) | — |
DELETE /v1/point/{测点UID列表} | 删除测点(逗号分隔的 UID,不是 dasId) | 否 |
DELETE /v1/das/{dasId} | 删除采集服务 | 否 |
DELETE /v1/driver/{ids} | 删除驱动,并连带删除该驱动的配置模板 | 否 |
GET /v1/system/log/empty/ | 清空全部操作审计日志(DELETE FROM log) | 否 |
GET /v1/system/log/{id} | 删除指定审计日志 | 否 |
GET /v1/system/service/log/{name}?fileName=… | 下载即删除该文件,且 fileName 可路径穿越读任意文件 | 否 |
GET /v1/system/service/status?name=<svc>&status=stop | 停止系统服务,连守护一起关 | 是(若守护被关则可能起不来) |
DELETE /v1/system/service/{name} | 删除系统服务的守护记录(服务不再被拉起) | 否 |
POST /v1/system/user/resetPwd/{id} | 弱口令重置:admin→sis、magus→openplant | 否 |
GET /v1/system/backup/recover?filePath= | 整库覆盖关系库,恢复后必须重启 | 否 |
POST /v1/system/mirror/geneRepFile | 生成镜像配置,变更时会 kill 实时库进程 | 是(靠守护拉起) |
POST /v1/system/reset/ | 恢复出厂设置 | 否 |
GET /v1/system/reboot/ | 重启整机(3 秒后执行,全部服务中断) | 是(但会中断) |
GET /v1/system/serviceBoot/ | 退出 dasserver 进程(能否自动回来取决于守护) | 是(见 7.27) |
POST /v1/das/reboot/{dasId} | 重启采集服务 | 是(但会中断) |
POST /v1/das/stop/{dasId} | 停止采集服务(*:方法,GET 也会触发) | 是 |
【警告】
DELETE /v1/driver/{ids}有个隐蔽后果:它会同时删除example_conf表里该驱动的全部配置行。也就是说删掉驱动后,即使重新放置文件,服务也无法凭原有模板重建——必须重新上传驱动包。而且它不检查驱动是否正在被服务使用:删掉后服务进程仍在跑,但下次重启就会因找不到可执行文件而启不来。
【警告】/v1/system/service/status停服务时,magusdog 会同时关闭该服务的守护(否则会被立即拉起)。若你停掉dasserver自身,本接口随之不可用——等于远程把自己关掉。停服务前务必确认目标服务的Valid(守护开关)状态。
【建议】执行任何破坏性操作前,先GET /v1/point/export/{dasId}与POST /v1/system/backup各做一份备份。
7.7 【高】/v1/point/{id} 的 {id} 是测点 UID,不是 dasId
现象:想「查某个采集服务的测点」,于是把 dasId 拼进路径:GET /v1/point/36,得到:
json
{"code":0,"count":0,"data":[],"msg":"ok"}成功但空——看起来像「这个服务没有测点」,实际是参数用错了。
原因:GET /v1/point/{id} 的 {id} 是测点 UID(实时库点号),代码里 strconv.Atoi(id) 后直接作为 uid 查询条件。传一个碰巧不存在的 UID,后端就返回空列表,不报错。
同一个 /v1/point/ 前缀下,路径参数含义并不统一:
| 接口 | {id} 的真实含义 |
|---|---|
GET /v1/point/{id} | 测点 UID |
DELETE /v1/point/{id} | 逗号分隔的测点 UID 列表 |
POST /v1/point/import/{id}、export、empty、multimport、import/local | 采集服务 ID(dasId) |
clean / forceDelete / template | 完全被忽略(传什么都一样) |
规避:
- 要按服务查测点,用
GET /v1/point?SN={服务名}(SN为精确匹配),不要拼路径。 - 要按测点查,先想清楚你手上的是
UID(实时库点号,即点表ID列)还是ID(关系库自增主键)——路径上要传的是UID。 - 实测对照(同一台设备,共 4 个测点):
| 请求 | 实测结果 |
|---|---|
GET /v1/point?SN=jen | count:4,返回 4 条 |
GET /v1/point?SN=mqtt | count:0 |
GET /v1/point/36 | count:0(36 是 dasId,不是 UID) |
GET /v1/point/1026 | 返回 UID=1026 那一条 |
7.8 【高】code:0 + 空数据的其他变体(把失败当成功)
除 7.2 外,还有一类「错误信息塞进 data 但 code/msg 正常」的实现,例如驱动列表查询失败时 code 仍为 0、msg 仍为 "ok",错误串出现在 data。
规避:对列表类接口,除了看 msg,还要校验 data 的结构是否符合预期(data 是数组还是意外变成了字符串)。发现 data 类型异常时按失败处理。
7.9 【高】导入成功时 count 恒为 0,拿不到真实条数
现象(实测):POST /v1/point/import/{dasId} 成功响应是:
json
{"code":0,"count":0,"data":[],"msg":"ok"}原因:源码里成功分支调用的是 AjaxList(MessageOk, 0, 0, make([]map[string]interface{}, 0)),第二个 0 是错误码,第三个 0 才是 count——成功时 count 被写死为 0。
【警告】原始接口资料里曾把
count描述为「导入的测点数量」,这是错的。实测成功恒为 0,失败时才携带「本次解析出的测点总数」。
规避:需要真实条数就导入前后各查一次 GET /v1/point?SN=<服务名> 的 count 做差(注意是 SN 查询,不是 /v1/point/{dasId},见 7.7)。
7.10 【高】导入是同步阻塞的,且部分失败不回滚
现象:点表很大时,/v1/point/import/{dasId} 会长时间不返回。
原因:整个导入(解析 xlsx → 比对 → 写关系库 → 写实时库 → 通知采集服务)在同一个 HTTP 请求内同步完成。
规避:
- 客户端超时设到 ≥300 秒。
- 【警告】不要因为超时就自动重试——重试会再触发一次导入。
- 大批量请拆包:后端单次插入上限
MaxInsertNumber = 10000。 - 【警告】部分行成功、部分行失败时,已成功的行不会回滚,只返回失败行清单。所以「导入失败」之后设备上可能已经多了一批点。要做整体幂等,只能靠「按 PN 比对」而不是「靠事务」。
7.11 【高】配置暴露敏感信息
现象(实测):GET /v1/system/conf 返回完整的设备配置,其中包含:
json
"HostOP": {"IP":"127.0.0.1","Port":"8200","UserName":"sis","Password":"***","EncodePassword":"***"}原因:该接口把 global.Conf 整体序列化返回,未做字段脱敏。
规避:
- 【警告】任何持有登录凭据的调用方都能读到实时库口令。请把该接口的访问权限当作「等同于设备 root 权限」来管理。
- 不要把该接口的响应贴到工单、聊天记录或公开文档里。
7.12 【中】本机免鉴权后门
现象:当配置项 Base.RequireLoginLogin 为 true 时,来自 127.0.0.1 / localhost 的请求完全跳过鉴权(实测该设备此配置为 true)。
原因:FilterBefore 的第一步就是这个判断。
两面性:
- ✅ 利好:与 DASS2 同机运行的程序(设备侧脚本、系统服务)可以直接调
/v1而无需登录,最省事也最稳。 - ⚠️ 风险:设备上任何本地进程(包括被入侵的低权限进程)都能无凭据调用全部接口,含清空测点、重启设备等破坏性操作。
规避:对外交付时评估是否关闭该配置;若必须开启,请确保设备上不存在不可信的本机进程。
【注意】该结论来自源码逻辑 + 实测配置值,本次未在设备本机做环回调用的端到端验证(无该设备的 shell 权限)。请在目标环境自行确认一次。
7.13 【中】鉴权路径匹配用的是「子串包含」
现象(源码确认):过滤器判定免登录用的是 strings.Index(urlPath, "login") != -1 这类子串匹配,而不是精确路径匹配。
后果:
- 任何URL 中含有
login子串的路径都会被放行。 /v1/isLogin/反而不在白名单里(因为"isLogin"含的是大写L,匹配不到小写login),需要鉴权、失败时返回 302。- 任何不含
v1子串的路径也会被放行。
规避:这是实现缺陷,不要在二次开发中利用它绕过鉴权;但排障时要知道「为什么这个请求没被拦」。
7.14 【中】Cookie 校验不验证签名,且会滑动续期
现象(源码确认):FilterBefore 对用户 Cookie 的校验只做了三件事——段数为 3、第三段是可解析的整数、第三段大于当前时间。中间的 authkey 段(split[1])从未被校验。
后果:
- 单看 Cookie,伪造
admin=1|x|<未来时间>就能过这一关。 - 但仍然需要有效的
beegosessionID(Session 里有user对象),所以整体并未被完全击穿。
滑动续期:每次请求成功后,过滤器会把 Cookie 的过期时间重置为「当前 + 24 小时」。
规避:客户端不要按固定 24 小时定时重登(会浪费且可能并发),正确做法是捕获 401 后重登一次。
7.15 【中】列表分页参数 offset / limit 语义反了
现象(源码确认):GET /v1/driver 的 offset 与 limit 与直觉相反,且 offset 传 0 时会被后端强制改成 20。
原因:后端把二者拼成了 LIMIT <limit>,<offset>(即 SQL 的「跳过 offset 条、取 limit 条」被写反),同时 offset == 0 时有个默认值兜底逻辑。
后果:GET /v1/driver 不带参数时返回 0 行;也无法用它取第 1 页。
规避:显式传参,例如 ?limit=20&offset=20;分页前先做一次真实对照验证。不同接口的分页行为不统一,请以各接口自身说明为准。
7.16 【中】非数字路径参数被静默当成 0
现象(源码确认):多处 strconv.Atoi(id) 的错误被直接丢弃,于是 /v1/driver/abc 等价于 /v1/driver/0(即查全部)。
规避:客户端自己保证 ID 是数字;不要指望服务端对非法 ID 报错。
7.17 【中】启动服务的路由参数与代码读取的键名不一致
现象(源码确认):路由注册的是 /v1/das/boot/?:id,但 Boot() 方法先读取 :name,取不到才回落到 :id。
后果:由于路由只定义了 :id,name 分支永远走不到,属于残留代码。用服务名去调 /v1/das/boot/{name} 会失败。
规避:启动服务只用 dasId(数字)。「按名寻址」只有 /v1/das/conf/third?name=xxx 这类接口才支持。
7.18 【中】HTTPS 与管理端口「配置里有、真机没开」
现象(实测):仓库 conf/app.conf 中配置了:
EnableHTTPS = true
HttpsPort = 8010
AdminAddr = "localhost"
AdminPort = 8088
EnableAdmin = true但实测该设备只有 8085 在监听,8010 与 8088 均为 closed。
规避:
- 以真机实测为准,不要照抄配置文件推断端口。
- 上线前自己扫一遍端口(见 2.1)。
- 若你的环境确实开了 HTTPS,记得
curl要带-k(自签证书)或导入ca。
7.19 【低】源码中的版本号与设备实际版本不一致
现象:源码 consts/consts.go 中 VERSION = "3.5.2.20251205",而实测设备上报的版本是 2.5.9.20241216。
后果:本手册的字段/行为结论基于设备实测 2.5.9.20241216;源码 HEAD 可能已包含更新的改动。以设备实测为准。
规避:任何接口行为差异,优先在目标设备上实测复现,再回头核对源码分支。
7.20 【低】源码缺陷:REDATA2 常量被定义成 "REDATA1"
现象(源码确认):consts/point.go 中:
go
ReData1 = "ReData1"
REDATA1 = "REDATA1"
ReData2 = "ReData2"
REDATA2 = "REDATA1" // ← 应为 "REDATA2"影响评估:当前代码库中没有任何地方引用 consts.REDATA2(点表导入用的是大小写混合的 ReData2),因此当前无实际影响。但它是一个埋着的雷:一旦有人使用全大写常量做列映射,ReData2 会被写成 ReData1 的值。
规避:填写点表的保留字段时,用模板原样给出的列名(REDATA1),不要自行改大小写。
7.21 【低】双机热备模式下的操作限制
现象(源码确认):当设备运行在双机热备(dual active)模式时:
| 角色 | 被禁止的操作 | 报错文案 |
|---|---|---|
master(主机) | 停止采集服务、手动重启服务 | 主机禁止停止服务 |
slave(备机) | 启动采集服务 | 备机禁止启动服务 |
规避:自动化脚本在双机热备环境下会收到「操作被拒绝」的失败响应,这不是 bug。脚本应先查询双机热备状态再决定是否执行启停。
7.22 【低】导入模板只认固定工作表名与固定文件名
现象(源码确认):
- 文件扩展名必须是
.xlsx(filepath.Ext(fh.Filename) != ".xlsx"直接失败)。不支持.xls、不支持.csv,尽管表单字段名和部分错误文案里带csv字样。 - 只读取名为
Sheet1的工作表(consts.Sheet1 = "Sheet1")。工作表改名会导致读不到数据。
规避:直接用 POST /v1/point/template/{dasId} 下载的模板,不要另存改名、不要改 sheet 名。
7.23 【低】导出/模板接口的文件格式在 point 与 upoint 之间不一致
现象(实测):
| 接口 | 实际格式 |
|---|---|
POST /v1/point/template/{dasId} | xlsx(响应体以 PK 开头) |
POST /v1/upoint/template/{dasId} | CSV(纯文本,8 列) |
规避:不要用同一套解析逻辑处理两者,也不要假设「模板接口都返回 xlsx」。
7.24 【提示】SQL 注入与解压路径穿越(安全审计参考)
以下为源码审阅发现,尚未验证可利用性,仅作为安全评估线索:
| 位置 | 问题 |
|---|---|
GET /v1/driver 的 name / type / desc 过滤 | 字符串直接拼接进 SQL 的 LIKE 子句,未参数化 → 潜在 SQL 注入 |
POST /v1/driver(驱动包上传) | 解压时未对 tar 内路径做 filepath.Clean 校验 → 潜在路径穿越 |
POST /v1/driver 的文件名校验 | 用的是 strings.Contains(name, ".tar.gz") → .tar.gz.exe 这类文件名可以通过 |
POST /v1/driver 的处理顺序 | 先解压落盘,后做业务校验 → 校验失败时恶意文件已留在磁盘上,只回滚数据库 |
规避:二次开发不要依赖服务端做输入校验;驱动包上传接口应当只对可信来源开放。同一类问题还出现在:
| 位置 | 问题 |
|---|---|
GET /v1/point、/v1/upoint 的 SN / ED / AS 过滤 | 字符串直接拼进 SQL(只有 PN 走了转义) |
GET /v1/point/pn、PUT /v1/point/pn、DELETE /v1/point/pn | sn 用 LIKE 模糊匹配 + pn 直接拼接 |
GET /v1/system/log/ | Type / Level / Module / Desc / 起止时间全部拼接 |
DELETE /v1/system/user/{id} | id 拼接进 IN (...) |
GET /v1/system/service/log/{name} | fileName 未做路径约束,且读到后删除 → 可读并删除任意文件 |
7.26 【中】/v1/point/forceDelete/{id} 是空实现,什么都不删却返回成功
现象(源码确认):POST /v1/point/forceDelete/{id} 返回:
json
{"code":0,"count":1,"data":"脏数据清除成功","msg":"ok"}原因:PointController.ForceDelete(controllers/data/point.go:2002)没有读取路径参数 id,方法体里只做了三件无副作用的事:通知 MQTT 刷新、上报云日志、写操作日志。它不删除任何数据。
后果:二次开发者若指望它「强制删除测点」,会静默失败(还返回成功),并误以为数据已删除。而它的返回文案是「脏数据清除成功」、日志文案是「清洗测点成功」,与接口名完全不符。
规避:删除测点请用 DELETE /v1/point/{测点UID列表};清理垃圾数据请用 /v1/point/clean(注意它忽略 id,是全局操作)。
7.27 【中】重启类接口的响应不是合法 JSON,且 serviceBoot 可能再也起不来
现象(源码确认):GET /v1/system/reboot/ 与 GET /v1/system/serviceBoot/ 的响应体是两段 JSON 串接:
{"code":0,"msg":"ok","count":1,"data":"三秒后系统将重新"}{"code":0,"msg":"ok","count":1,"data":"重启成功"}原因:方法先 ServeJSON 写一次,末尾又调 AjaxList 再写一次,beego 的 Output.Body 会追加写入。
后果:严格的 JSON 客户端(如 axios 默认的 transformResponse)会解析失败,取不到 code。「调用成功」不能靠解析响应体判断,应改为轮询设备可达性。
两个接口的差异(务必分清):
/v1/system/reboot/ | /v1/system/serviceBoot/ | |
|---|---|---|
| 粒度 | 重启整机(执行 reboot 命令) | 只退出 dasserver 进程(os.Exit(0),不执行系统命令) |
| 影响面 | 全部服务、采集、网络会话中断数分钟 | 只有 dasserver(HTTP 8085)短暂不可用 |
| 依赖 | root 权限 | 依赖外部把进程重新拉起:magusdog 或 systemd |
| 风险 | 需要现场或远程带外恢复 | ⚠️ 若 dasserver 未注册到 magusdog 守护(或守护被关),进程退出后永远不会回来 ——等于远程关机 |
【警告】调用
serviceBoot前,必须确认 magusdog 在运行且 dasserver 的守护开关(Valid)为 true。设备上真实存在 dasserver 未受守护的情况(见 2.3.4 与 4.8)。
7.28 【中】官方上传点模板是 CSV,但上传点导入只吃 xlsx
现象(实测):POST /v1/upoint/template/{id} 下载到的是 CSV 文本(8 列):
csv
ID,AN,AD,SR,ED,FK,FB,EVENT
1046,g02,1.3.0,INT16,22,1,0,0但 POST /v1/upoint/import/{dasId} 只接受 .xlsx(扩展名大小写敏感),且要求工作表名必须是 Sheet1。
后果:拿官方模板直接去导入,必然失败,报 文件格式不是xlsx。
规避:
- 上传点导入请自建 xlsx 模板,工作表命名
Sheet1。 - 对照:
POST /v1/point/template/{任意值}下发的是真正的 xlsx(34 列),采集点链路没有这个矛盾。 - 上传点导入的列白名单与采集点不同:
ID是必需列;GN与全大写REDATA2会被判为「非法的字段」;表头大小写敏感(ID除外)。
7.29 【中】/v1/upoint/pn 系列的 pn / pns 其实是「全局名 GN」
现象(源码确认):GET|PUT|DELETE /v1/upoint/pn 的 pn / pns 参数,在 SQL 里比的字段是 gn(全局名 W3.<节点>.<PN>),不是点名。
后果:传点名 G02 会「查不到 / 删成功但什么都没删」,而接口仍可能返回成功。
规避:这三个接口传的必须是 W3.XXX.G02 形式的全局名。另外这两个接口在「查不到记录」时会在 points[0] 处数组越界 panic(HTTP 500),且 sn 传空会因空指针 panic。
7.30 【提示】部分接口的参数名大小写不一致
现象(源码确认):同一产品里参数命名风格混用,且大小写敏感:
| 接口 | 参数名 |
|---|---|
/v1/point、/v1/upoint 列表 | 全大写:SN、PN、ED、ID、HOST |
/v1/upoint/pn | 全小写:sn、pn |
/v1/point/pn | 全小写:sn、pn |
/v1/system/user/changPwd | pldPwd(拼写就是 pld,不是 old)、NewPwd(大写 N) |
/v1/system/service/protect/ | PascalCase:Name、Valid、Path |
/v1/system/dualActive/setParameter | camelCase JSON:role、foreignIP、nativeIP |
规避:逐接口照文档传参,不要凭"同族接口应该一样"去猜。传错大小写通常不报错,而是参数取到零值、条件静默失效。
7.31 【提示】部分接口的 data 里返回的是未翻译的 i18n key
现象(源码确认):某些 data 里出现的不是中文,而是大写的常量名,例如:
{"code":0,"count":1,"data":"OPERATE_SUCCESS","msg":"ok"}
{"code":-1,"count":1,"data":"POINT_SR_TYPE_MATCH_FAILED","msg":"error"}原因:这些 key 在 conf/locale_zh-CN.ini 与 locale_en-US.ini 里都没有定义,i18n.Tr 找不到时原样返回 key。
规避:客户端做错误映射时,要为这类「全大写 + 下划线」的字符串预留兜底分支,不要直接展示给最终用户。已确认缺失的 key 至少有:
| key | 出现场景 |
|---|---|
OPERATE_SUCCESS / OPERATE_FAILED / SELECT_SUCCESS | /v1/system/service/status、/v1/system/service/protect/ 等 |
POINT_SR_TYPE_MATCH_FAILED | 测点/上传点的 SR 不在驱动白名单内 |
ERROR_MESSAGE_KEYWORD_REPEAT | /v1/upoint 新增上传点时的部分失败分支 |
7.25 【提示】不受本手册覆盖的接口
以下分组未纳入本手册,但存在于 DASS2 中。如果你的需求涉及它们,请单独评估:
| 分组 | 前缀 | 说明 |
|---|---|---|
| 网络管理 | 无 /v1 前缀的 controllers/network/* | 网口、无线、NAT、防火墙、路由、串口 |
| 系统诊断 / 协议调试 | /v1/system/debug/* | 建连接、发报文、Ping、串口调试 |
| 实时通信 | /v1/socket.io/* | 前端实时推送与 WebSSH |
| 系统升级 | /v1/system/update/ | OTA |
| 数据分析任务 | /v1/system/data/analysis/* | 分析作业增删改查 |
| 缓存镜像(部分) | /v1/system/mirror/* | 镜像文件的增删改查与预览 |
【注意】这些接口同样受 7.1 ~ 7.6 的通用坑影响(鉴权返回 HTML、
code语义不一致、*:方法触发写操作等)。
7.32 【严重】/v1/calcModel/gethistorybyid 返回的是伪造的随机数据
现象(实测):调用历史数据接口,无论传什么测点 ID,返回的点名永远是 TEST_POINT_0005:
bash
GET /v1/calcModel/gethistorybyid?begin=1504116000&end=1504116600&interval=60&ID=[[1026]]
GET /v1/calcModel/gethistorybyid?begin=1600000000&end=1600000600&interval=60&ID=[[999999]]两次请求(ID 一个是真实存在的 1026,一个是根本不存在的 999999)返回:
json
{"code":0,"count":1,"data":[{"PN":"TEST_POINT_0005","GN":"W3.DATAPOINT.TEST_POINT_0005",
"EstimatedCurve":[{"AV":90,"TM":1504116600},{"AV":80,"TM":1504116540}, ...],
"ResidualCurve":[{"AV":0.033713963604995456,...}, ...],
"RealValueCurve":[ ... ],
"AlarmStatusCurve":[ ... ]}]}PN / GN 完全相同,曲线值也是同一组硬编码常量,只有残差曲线里的随机抖动值不同。
原因(源码确认):该接口最终走到 models/opconsole/openPlant.go 的 GetArchive(),而这个函数的第一行就是:
go
func GetArchive(host, begin, end, interval, idStr string) (interface{}, int64, string, error) {
return Temp(begin, end, idStr, interval) // ← 直接返回,下面 30 行真实实现全是死代码
...
}Temp() 的实现是造数据:
go
realTimeValue = []float64{40.0267, 50, 60, 70, 80, 90} // 硬编码的曲线
avAndtms = append(avAndtms, AVAndTM{
AV: realTimeValue[num] + float64(rand.Float64()*10-5), // 加随机抖动
TM: int64(endInt - i*60),
})
...
archiveData.PN = "TEST_POINT_0005" // 写死的点名
archiveData.GN = "W3.DATAPOINT.TEST_POINT_0005"也就是说:它不读实时库、不读归档、与传进来的 ID 毫无关系。
后果:
- 接口返回
code:0(成功),不会报任何错——调用方无法从响应判断这是假数据。 - 任何基于它做的历史趋势、报表、异常检测、模型训练全部是无意义的。
- 传入不存在的测点 ID 也会「成功」返回数据,掩盖了 ID 写错的问题。
规避:
- 绝对不要在生产集成中使用
/v1/calcModel/gethistorybyid。 - 需要真实历史数据,请用
/v1/opconsole/gethistorybyid或/v1/opconsole/gethistorybyids(实测返回真实归档数据,但注意它们没有条数上限且采样被写死为 1 秒 1 点,见 4.7.4)。 - 若要控制采样方式与时间窗口,应走实时库直连(rtnn
8200)或 openPlant SDK,而不是这两个 HTTP 接口。 - 在做接口验收时,用「传一个不存在的 ID,看是否仍返回数据」来识别此类假接口——这是一个通用且有效的判别手法。
【警告】这条与本手册 4.7.2 是同一个问题,因影响面大,在两处都做了标注。
帮助中心