Skip to content

7. 已知限制与坑

本章按严重程度排序。每条写清「现象 / 原因 / 规避方式」。带「实测」标记的均已在本手册的基线设备上真实复现。


7.0 先读这三条(否则会白做)

如果你只读本章的一部分,请先读这三条——它们会让基于错误前提开发出来的功能静默失效

#一句话后果详见
1/v1/calcModel/gethistorybyid 返回伪造的随机数据任何基于它的历史分析、报表、趋势判断都毫无意义,而且接口返回成功、不报错7.32
2code == 0 不代表成功code:0msg:"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.goFilterBefore 实现,失败时走的是 ctx.Redirect(401, "/")直接输出 HTML 重定向体,完全没有经过 JSON 响应封装。

规避

  1. 先判断 HTTP 状态码401 / 302 直接进入「重新登录」分支,不要解析响应体。
  2. 客户端若统一 json.Unmarshal 响应体,会把「未登录」误报成「响应格式错误」,掩盖真实原因。
  3. /v1/isLogin/ 失败返回 302(不是 401),/v1/logout/ 未登录时直接 500——三者行为都不同,不要用同一套判断。

7.2 【严重】code == 0 不代表成功

现象(实测):查询一个不存在的采集服务配置:

bash
GET /v1/das/conf/99999
json
{"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} 中,textTypeispassword / password 的配置项,其 value 不是明文,而是 base64(AES(明文))

原因:后端在返回前对密码类字段做了加密(driver.goutils.AesEncrypt + base64.StdEncoding)。

规避

  • 只读展示:先 base64 解码,再用同一 AES 密钥解密。
  • 回写:必须重新 base64(AES(明文)) 后上送;把读到的密文原样回写虽然「看起来能跑」,但一旦密钥/实现变化就会把密码字段写坏。
  • 【警告】AES 密钥硬编码在二进制里utils/aes.goAesKey)。这意味着该加密不提供真正的机密性,只能防肩窥。不要把它当作安全边界。

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}exportemptymultimportimport/local采集服务 ID(dasId)
clean / forceDelete / template完全被忽略(传什么都一样)

规避

  • 要按服务查测点,用 GET /v1/point?SN={服务名}SN精确匹配),不要拼路径。
  • 要按测点查,先想清楚你手上的是 UID(实时库点号,即点表 ID 列)还是 ID(关系库自增主键)——路径上要传的是 UID
  • 实测对照(同一台设备,共 4 个测点):
请求实测结果
GET /v1/point?SN=jencount:4,返回 4 条
GET /v1/point?SN=mqttcount:0
GET /v1/point/36count:0(36 是 dasId,不是 UID)
GET /v1/point/1026返回 UID=1026 那一条

7.8 【高】code:0 + 空数据的其他变体(把失败当成功)

除 7.2 外,还有一类「错误信息塞进 datacode/msg 正常」的实现,例如驱动列表查询失败时 code 仍为 0msg 仍为 "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.RequireLoginLogintrue 时,来自 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/driveroffsetlimit 与直觉相反,且 offset0 时会被后端强制改成 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

后果:由于路由只定义了 :idname 分支永远走不到,属于残留代码。用服务名去调 /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 在监听80108088 均为 closed。

规避

  • 以真机实测为准,不要照抄配置文件推断端口。
  • 上线前自己扫一遍端口(见 2.1)。
  • 若你的环境确实开了 HTTPS,记得 curl 要带 -k(自签证书)或导入 ca

7.19 【低】源码中的版本号与设备实际版本不一致

现象:源码 consts/consts.goVERSION = "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 【低】导入模板只认固定工作表名与固定文件名

现象(源码确认):

  • 文件扩展名必须是 .xlsxfilepath.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/drivername / 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/upointSN / ED / AS 过滤字符串直接拼进 SQL(只有 PN 走了转义)
GET /v1/point/pnPUT /v1/point/pnDELETE /v1/point/pnsnLIKE 模糊匹配 + 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.ForceDeletecontrollers/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/pnpn / pns 参数,在 SQL 里比的字段是 gn(全局名 W3.<节点>.<PN>),不是点名。

后果:传点名 G02 会「查不到 / 删成功但什么都没删」,而接口仍可能返回成功。

规避:这三个接口传的必须是 W3.XXX.G02 形式的全局名。另外这两个接口在「查不到记录」时会在 points[0]数组越界 panic(HTTP 500),且 sn 传空会因空指针 panic。


7.30 【提示】部分接口的参数名大小写不一致

现象(源码确认):同一产品里参数命名风格混用,且大小写敏感

接口参数名
/v1/point/v1/upoint 列表全大写:SNPNEDIDHOST
/v1/upoint/pn全小写:snpn
/v1/point/pn全小写:snpn
/v1/system/user/changPwdpldPwd拼写就是 pld,不是 old)、NewPwd(大写 N)
/v1/system/service/protect/PascalCase:NameValidPath
/v1/system/dualActive/setParametercamelCase JSON:roleforeignIPnativeIP

规避逐接口照文档传参,不要凭"同族接口应该一样"去猜。传错大小写通常不报错,而是参数取到零值、条件静默失效。


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.inilocale_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.goGetArchive(),而这个函数的第一行就是

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 写错的问题。

规避

  1. 绝对不要在生产集成中使用 /v1/calcModel/gethistorybyid
  2. 需要真实历史数据,请用 /v1/opconsole/gethistorybyid/v1/opconsole/gethistorybyids(实测返回真实归档数据,但注意它们没有条数上限且采样被写死为 1 秒 1 点,见 4.7.4)。
  3. 若要控制采样方式与时间窗口,应走实时库直连(rtnn 8200)或 openPlant SDK,而不是这两个 HTTP 接口。
  4. 在做接口验收时,用「传一个不存在的 ID,看是否仍返回数据」来识别此类假接口——这是一个通用且有效的判别手法。

【警告】这条与本手册 4.7.2 是同一个问题,因影响面大,在两处都做了标注。