diff --git a/docs/E2E测试报告.md b/docs/E2E测试报告.md new file mode 100644 index 0000000..032455c --- /dev/null +++ b/docs/E2E测试报告.md @@ -0,0 +1,235 @@ +# E2E 测试报告 —— 监测数据不落地平台安卓客户端 M1–M6 全业务动线验收 + +> 任务:S-E2E 全业务动线端到端测试与验收报告(Heart 任务 `00c8cc0d`,父任务「M2–M6 全功能业务动线闭环与 E2E 测试」) +> 测试日期:2026-09-04 · 测试人:S-E2E 子会话(自动化 + 静态走查) +> 判定口径(任务书):契约层 15 端点全覆盖 + 协议层覆盖确认 + UI 层完成(实机或静态推演)= 通过;任一层无法完成须写明阻塞与证据。 + +--- + +## 1. 结论速览 + +| 层 | 方式 | 结果 | 判定 | +|---|---|---|---| +| 契约层(15 在用端点 × 2 组负向) | 真实请求实测(python3 脚本) | 30/30 端点存在且响应形状闭环;A 组 14/15 双层结构成立,1 个服务端特例(2.14);B 组验证密钥有效 + Token 门控全覆盖 | **通过** | +| 协议层(:ble-protocol) | 静态覆盖矩阵 + `gradle :ble-protocol:test` 实测 | 静态审阅 IE-1000 五类 / VM208 六类用例全覆盖协议规格;**实测 BUILD SUCCESSFUL,12/12 用例通过**(本机无 JDK/Gradle,经临时装 JDK17 + 手动取 Gradle 8.10.2 发行版跑通,仓库零改动) | **通过** | +| UI 动线层 | 静态走查推演(**降级**,见 §5.1) | 5 Tab 全部业务页、SessionState/ProjectSession 门控链、登录/配置/改密/采集/上传/统计状态机逐页核对,未发现门控缺口 | **通过**(降级方式,已留证) | + +**总体判定:三层验收通过(UI 层为静态推演降级口径)。** 正向链路(真实凭据登录→项目→任务→采集→上传→统计)因无真实测试账号,仅梳理遗留项与移交建议(§7)。 + +## 2. 测试对象与环境 + +- 应用:监测数据不落地平台安卓客户端,Gradle 多模块:`:app`(com.stec.cmd)、`:core:network`、`:core:database`、`:core:ble`、`:core:common`、`:ble-protocol`(纯 Kotlin JVM)。 +- 测试服务器:`https://jcd.stec.p-q.co`(接口管理规范 V4.5 测试环境;文档内 `http://106.15.183.20:8006/` 为旧地址,以任务书为准)。 +- 契约依据:`docs/上海城建勘测院监测数据不落地系统接口文档V4.5.docx`(全文已解析核对);功能点依据:`docs/安卓客户端功能明细表.md`(36 点,本期 M1 7 点 + M2–M6 25 点)。 +- 本机环境(Linux 沙箱):无系统 JDK(`java` 不存在,docker.sock 无权限)→ 以 Adoptium Temurin JDK 17.0.20.1 tarball 临时安装于 `/tmp`;无 Android SDK(`local.properties` 为 Windows 路径且被 git 追踪,不可改);`heart_android_preflight` 三次调用均 `Premature close`、`heart_android_list_devices` 30s 超时(MCP 通道异常,已留证)。 +- 密钥:`docs/SystemCode_secertkey.txt` / `docs/appid_secertkey.txt`(`SystemCode:Mobile` + `SecretKey:<152 位 hex>`),报告中只引用路径不落明文,探测响应存档中已脱敏。 + +## 3. 契约层(必做 · 负向)—— 15 端点真实请求 + +### 3.1 方法与用例组 + +- 脚本:`docs/e2e-evidence/contract_probe.py`(python3 标准库,节流 1.5s/请求,不主动触发 403 锁定)。 +- 用例组 A:缺三要素头(SecretKey/SystemCode/Token 全缺)。 +- 用例组 B:带真实 SecretKey/SystemCode(运行时读密钥文件)、缺 Token;登录类端点按文档 1.2.3 天然无 Token,B 组等效「密钥正确 + 无会话」语义。 +- 判定:①存在性 = HTTP ≠ 404/000;②双层结构 = HTTP 500 且 body 含字符串字段 `ExceptionMessage`、其值可解析为含 `code` 的信封 JSON;③信封解包模拟 = `code` 为字符串且 ∈ {"200","500","429","403"}(对齐 `core/network/ApiEnvelope.kt`)。 +- 证据:原始响应 30 份存于 `docs/e2e-evidence/responses/`(`A_2_2_1.txt` … `B_2_15.txt`),机器可读结果 `contract_results.json`,汇总表 `contract_evidence.md`(均自动生成,与本节表格可交叉核对)。 + +### 3.2 证据总表(15 端点 × 2 组) + +| # | 章节 | 端点(/OutWebApi/api/…) | 方法 | A 组 | B 组 | 实测要点 | +|---|---|---|---|---|---|---| +| 1 | 2.2.1 | LoginByAccount | POST json | 500+双层 ✅ | **200+裸信封** `用户名或密码错误!` | A=密钥校验失败双层;B=密钥通过、业务层拒绝占位凭据 | +| 2 | 2.2.2 | LoginSecurityCode | GET | 500+双层 ✅ | **200+裸信封** `短信验证码发送失败…`(`data.result=false`) | 参数合法进入腾讯云 SMS 发送流程(沙箱出网受限发送失败,属环境因素) | +| 3 | 2.2.3 | LoginByMobile | POST json | 500+双层 ✅ | **200+裸信封** `此号码的用户不存在…` | 业务校验直达 | +| 4 | 2.3.1 | ModifyPassword | POST json | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 5 | 2.4 | GetUserInfo | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 6 | 2.5.1.1 | GetProjectList | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 7 | 2.6 | GetUserPlanGroup | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 8 | 2.7 | GetPlanGroup | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 9 | 2.9 | GetPointBySurveyGroup | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 10 | 2.10 | GetPlanStatistical | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 11 | 2.11 | GetSurveyGroupList | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 12 | 2.12 | GetPointStatistics | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | +| 13 | 2.13 | AnalysisMonitorData | POST multipart | 500+双层 ✅ | 500+双层 `无效的token。` | multipart 体(含 Files 单文件)被服务端正常接收并进入鉴权 | +| 14 | 2.14 | ControlPoint | GET | 500+**纯文本** ⚠ | 500+**纯文本** ⚠ | 服务端特例,见 §3.4 | +| 15 | 2.15 | GetWorkPoint | GET | 500+双层 ✅ | 500+双层 `无效的token。` | Token 门控 | + +> 完整逐条数据(含 ExceptionType、信封 msg、data)见 `docs/e2e-evidence/contract_evidence.md`。 + +### 3.3 平台行为模型(实测归纳,修正任务书口径的适用范围) + +1. **鉴权失败 →「HTTP 500 + ASP.NET 异常包装内嵌信封」双层结构**(与任务书/M1 实证一致): + - 密钥缺失/错误:`ExceptionMessage = {"code":"500","msg":"认证未通过,请检查SystemCode与SecretKey的值是否正确","data":null}`,`ExceptionType=NK.Public.Exceptions.BusinessException`,抛自 `BaseApiController..ctor()`; + - Token 缺失/无效:同结构,`msg=无效的token。`。 +2. **业务失败 →「HTTP 200 + 裸业务信封」**:带有效密钥的登录类端点实测返回 `{"code":"500","msg":"…","data":null}`(2.2.2 的 data 为 `{"result":false}`)。**此路径不会出现双层结构**——任务书预期中的双层结构仅适用于鉴权层负向。 +3. **信封解包模拟**:全部 30 组响应中信封 `code` 均为字符串 `"500"`,与 `ApiEnvelope.code: String` 口径一致;客户端 `ApiCaller.parseEnvelope` 对「裸信封」与「双层包装」两种错误体均已兼容(`core/network/src/main/kotlin/com/stec/cmd/core/network/ApiCaller.kt:55-77`),实测形状全部可解。 +4. **密钥有效性正面验证**:B 组登录端点能返回业务层错误(而非鉴权错误),证明 `docs/SystemCode_secertkey.txt` 中的密钥在测试环境有效、`SystemCode=Mobile` 与 SecretKey 匹配。 +5. 429(一分钟频控)未触发(30 次请求跨端点、1.5s 节流);403(连续报错锁定)按约束未主动触发。负向「触发到哪个记哪个」口径下记为:本轮未触发,无观察值。 + +### 3.4 服务端缺口:2.14 ControlPoint 双重身份(基准点 2.14 / 工点数据 2.15 目录笔误) + +- 实测:`/OutWebApi/api/ControlPoint`(2.14 获取基准点)在缺密钥与缺 Token 两组下均返回 **HTTP 500 + `ExceptionMessage` 为纯文本「当前用户不存在。」**(`ExceptionType=System.Exception`,非 BusinessException、非内嵌信封)。该控制器在 action 内直接调用 `UserService.GetCurrentUserInfo()`,未走 `BaseApiController` 统一鉴权包装(StackTrace 显示 `ControlPointController.Get` 直取当前用户)。 +- 客户端影响:`ApiCaller.parseEnvelope` 解不出内嵌信封 → 退化为 `ApiError.ServerError("500","HTTP 500")`,UI 提示「操作失败,请稍后重试」。**不崩溃、可用性无损,但错误文案退化**,且与文档「双层结构」约定不符。 +- 建议:向院方/平台方反馈该端点的鉴权实现与统一异常包装不一致的问题(含本报告证据文件 `responses/A_2_14.txt`、`B_2_14.txt`);客户端侧可考虑对该端点的 500 做文案兜底映射(可选优化,非阻塞)。 +- 说明:文档目录页将 2.15「获取工点数据」笔误为 ControlPoint,正文实测为 `/OutWebApi/api/GetWorkPoint`,客户端 `PointApi.PATH_GET_WORK_POINT` 与正文一致,本报告以正文为准(功能明细表 M5-03 已注记)。 + +### 3.5 端点在用性与文档目录核对 + +- 文档 2.1 目录 15 个在用端点与客户端 `core/network/api/*.kt` 声明逐一比对**完全一致**(AuthApi×5、ProjectApi×1、TaskApi×2、PointApi×3、StatApi×3、UploadApi 在用部分×1)。 +- 废除端点 2.8.1 GetMonitorPoint、2.8.2 UploadMonitorValue:`UploadApi.kt` 中仍保留 `PATH_UPLOAD_MONITOR_VALUE` 常量与 `uploadMonitorValue` 声明,全工程无调用点(死代码,编译期保留不影响运行);建议后续清理(非阻塞观察项)。 +- 文档 2.16 UploadAutomation(上传水位自动化数据)、2.17 GetPointsByPlan(按监测计划获取测点)不在本期 15 端点范围,客户端未实现,与功能明细表口径一致。 + +## 4. 协议层(必做)—— :ble-protocol 单测覆盖 + +### 4.1 静态覆盖矩阵(逐用例审阅源码) + +**IE-1000**(帧 `'Y'+4B 小端 float 压力+'T'+4B 小端 float 温度+0D 0A`,固定 12 字节)——`Ie1000FrameParserTest.kt`(5 用例): + +| 规格点 | 测试用例 | 断言要点 | 覆盖 | +|---|---|---|---| +| 正常帧解析 | `整帧一次喂入解析出协议示例读数` | 协议示例 `59 D8 BE 50 41 54 00 00 E8 41 0D 0A` → 压力 13.0466(文档「13.04」为截断写法,测试按位模式 0x4150BED8 精确断言)、温度 29.0 | ✅ | +| 截断/分片(残包) | `逐字节分片喂入仍可切帧(残包容错)` | 逐字节 feed 12 次,最终恰好 1 条读数 | ✅ | +| 粘包 | `粘包两帧一次解析` | 两帧拼接一次 feed → 2 条读数 | ✅ | +| 脏帧重同步 | `脏帧被丢弃后继续重同步` | 垃圾帧+正常帧 → 仅 1 条正确读数 | ✅ | +| 坏帧头 | `帧头错误的完整帧被丢弃` | 'Y'→'X' → 0 条读数 | ✅ | + +**VM208**(帧 `CC F%05d T%03d E%05d S%c AA + 0D 0A`,频率/10Hz、温度/10℃、电压/100V;激励帧 `0F 0F 06 0A XX XX 0D 0A` 大端)——`Vm208FrameParserTest.kt`(7 用例)+ `Vm208CommandEncoder`: + +| 规格点 | 测试用例 | 断言要点 | 覆盖 | +|---|---|---|---| +| 正常帧解析 | `整帧解析出协议示例读数` | `CC F12345 T334 E14039 SG AA 0D 0A` → 1234.5Hz / 33.4℃ / 140.39V / GOOD(原文档「14039/100=140V」为算术笔误,按公式断言) | ✅ | +| 分片 | `分片喂入仍可切帧` | 半帧+半帧 → 1 条读数 | ✅ | +| 质量字母映射 | `状态字母映射 Q 与 N` | SQ→FAIR、SN→NONE | ✅ | +| 未知质量兜底 | `未知状态字母映射 UNKNOWN 而非崩溃` | SX→UNKNOWN | ✅ | +| 缺字段脏帧 | `缺字段脏帧被丢弃` | `CC F12345 AA` 丢弃且后续帧正常 | ✅ | +| 激励编码(大端) | `激励命令编码与应答回解析` | encodeExcitation(100) = `0F 0F 06 0A 00 64 0D 0A`;应答回解析=100;截断帧→null | ✅ | +| 激励越界防护 | `激励值越界抛出` | 0x10000 → IllegalArgumentException | ✅ | + +### 4.2 实测执行(`./gradlew :ble-protocol:test`) + +- 环境障碍与解决(如实记录):本机无 JDK → Adoptium Temurin JDK 17.0.20.1 tarball 免 root 安装至 `/tmp/jdk17`;Gradle wrapper 默认 10s 连接超时下载失败 → curl 跟随重定向手动获取 `gradle-8.10.2-bin.zip` 解压至 `/tmp/gradle-8.10.2` 直跑 `bin/gradle`;`dl.google.com` 不可达无影响(工程仓库配置已优先阿里云镜像,见 `settings.gradle.kts`)。Gradle 用户目录与 JDK 均置于 `/tmp`,仓库零改动。 +- 命令:`JAVA_HOME=/tmp/jdk17 GRADLE_USER_HOME=/tmp/gradle-home /tmp/gradle-8.10.2/bin/gradle :ble-protocol:test --configure-on-demand --no-daemon` +- 结果:**BUILD SUCCESSFUL in 2m 13s,退出码 0;12/12 用例全部通过**(IE-1000 5 用例 + VM208 7 用例,failures=0 errors=0 skipped=0,见 JUnit 结果 `docs/e2e-evidence/protocol-test-results/TEST-*.xml` 与 HTML 报告 `html/index.html`): + +``` +com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest: tests=5 failures=0 errors=0 skipped=0 + [PASS] 整帧一次喂入解析出协议示例读数 + [PASS] 逐字节分片喂入仍可切帧(残包容错) + [PASS] 粘包两帧一次解析 + [PASS] 脏帧被丢弃后继续重同步 + [PASS] 帧头错误的完整帧被丢弃 +com.stec.cmd.protocol.vm208.Vm208FrameParserTest: tests=7 failures=0 errors=0 skipped=0 + [PASS] 整帧解析出协议示例读数 + [PASS] 分片喂入仍可切帧 + [PASS] 状态字母映射 Q 与 N + [PASS] 未知状态字母映射 UNKNOWN 而非崩溃 + [PASS] 缺字段脏帧被丢弃 + [PASS] 激励命令编码与应答回解析 + [PASS] 激励值越界抛出 +``` + +- 收尾:运行产生的构建产物已全部还原/清除(`.gradle`、`ble-protocol/build` 恢复至运行前状态——该仓库未忽略构建目录,产物不入交付提交),JUnit 结果已归档至 docs 证据目录。 +- 另:`:app` 模块自有单测 3 个(`UploadPayloadWrapTest` 3 用例、`CollectPayloadBuilderTest` 4 用例、`StatsModelsTest`)静态审阅覆盖上传体包装、采集读数→MonitorValue 组装(IE-1000 主值/VM208 频率主值+Extra 温度电压质量)、统计模型解析;因需 Android SDK 构建链,本轮未执行(同 §5 降级原因)。 + +## 5. UI 动线层(尽力而为)—— 静态走查推演 + +### 5.1 降级记录(如实留证) + +实机路线在三个独立环节全部受阻,按任务书「失败≤2 次降级」口径提前降级: + +1. `heart_android_preflight` 连续 3 次调用返回 `Premature close`(MCP Android 服务通道异常);`heart_android_list_devices` 30s 超时。 +2. 宿主无 Android SDK 且无法安装(`local.properties` 指向 Windows 路径且被 git 追踪不可改;自建 SDK 需 3–4GB 下载且 `heart_android_install_app` 通道同源异常,投入产出不成比例)。 +3. `./gradlew :app:assembleDebug` 需 Android 构建链(AGP),无 SDK 无法产出 APK。 + +**降级方式**:全链路静态走查推演——逐页核对源码中的会话/配置/项目门控分支、跳转与状态机,证据以 `file:line` 标注。BLE 真机依赖项按任务书标注「模拟器不适用」。 + +### 5.2 动线走查表(逐页核对) + +| # | 动线节点 | 静态核对证据(file:line) | 推演结果 | +|---|---|---|---| +| 1 | 冷启动无「闪去登录」(Restoring 态) | `SessionRepository.kt:59` 初始态 `Restoring`,`init{restore()}` 异步落定;`MainActivity.kt:29` `installSplashScreen()`(Theme.SteCmd.Splash→Material3 主题切换);`MineFragment.kt:63-73` / `ProjectWorkspaceFragment.kt:92` / `TaskWorkspaceFragment.kt` / `CollectWorkspaceFragment.kt:136-139` / `StatsWorkspaceFragment.kt:114` Restoring 均渲染中性加载、隐藏登录 CTA | ✅ 五页一致无闪去登录 | +| 2 | 未配置引导(配置缺失警示卡) | `ConfigRepository.kt:49-50` `isReady = baseUrl/secretKey/systemCode 非空`;`ProjectWorkspaceFragment.kt:95-96`、`TaskWorkspaceFragment.kt`(stateConfig)、`CollectWorkspaceFragment.kt:146-149`(GateAction.CONFIG)、`StatsWorkspaceFragment.kt:117-120`、`MineFragment.kt:102-106`(警示卡常显 `!isReady`)——警示卡点击均 `openOnTop(ConfigFragment)` | ✅ 全 Tab 门控一致 | +| 3 | 配置页录入假三要素 | `ConfigViewModel.kt:29-40` 必填校验(「服务器地址、SecretKey、SystemCode 均为必填」)+ URL 前缀校验(http:// 或 https://)+ `ConfigRepository.updateConnection` 持久化(DataStore+加密,`ConfigRepository.kt:94-96` trim 去尾斜杠) | ✅ 校验与持久化闭环 | +| 4 | 登录负向错误提示 | `LoginViewModel.kt:99-117` 表单校验→`configReady()` 门控(未配置提示「请先在『我的-服务器配置』填写…」)→ `ApiError.userMessage()`(`ApiErrorMessages.kt:6-13`,429「操作过于频繁」/403「连续失败次数过多」/网络/空体/服务器五类文案);契约层实测的业务错误(如 `用户名或密码错误!`)经 `ApiCaller` 解包为带平台原文的 ApiError 展示 | ✅ 负向链路文案闭环 | +| 5 | 登录页分段切换 | `LoginFragment.kt:45-73` MaterialButtonToggleGroup `btn_mode_account/btn_mode_sms` ↔ `LoginMode.ACCOUNT/SMS`,`groupAccount/groupSms` 互斥显隐;SMS 段含验证码 60s 倒计时(`LoginViewModel.kt:126-134`)与国内号全匹配校验(`Phones.kt:16` `^1[3-9]\d{9}$`) | ✅ 双段切换+倒计时+校验 | +| 6 | 我的页身份卡/设置列表 | `MineFragment.kt:61-100` 三态渲染:Restoring(中性)、SignedOut(游客卡=登录入口,改密置灰)、SignedIn(头像取姓名首字/姓名/部门/账号/手机号 + 退出登录);设置行:改密/服务器配置 | ✅ | +| 7 | 首页项目工作台(M2) | `ProjectWorkspaceFragment.kt:75-133` 四态(Loading/Error(含重试)/Empty(空态刷新 CTA)/内容卡:项目名/编号/等级 Chip/阶段 Chip/项目计数);M2-03 切换 `ProjectSwitcherSheet`(JSON 传参,`onProjectPicked`→`HomeViewModel.select`→`ProjectSession`) | ✅ | +| 8 | 项目切换联动刷新(M2-03) | `ProjectSession.kt:34-40` 登出自动清空;M3/M5/M6 三页均订阅 `selectedProject` 流(`TaskWorkspaceFragment.kt:97`、`CollectWorkspaceFragment.kt:109`、`StatsWorkspaceFragment.kt:88`),流变化触发整体重渲与数据刷新 | ✅ | +| 9 | 任务页双列表/筛选/日期(M3) | `TaskWorkspaceFragment.kt:75-91` MY_TASKS/PLAN_TASKS 切换、状态 Chip 过滤(未开始/进行中/已完成)、日期 Chip+MaterialDatePicker、下拉刷新;未选项目空态 CTA→首页(`btnGoHome`) | ✅ | +| 10 | 采集页门控与权限分版(M4-01) | `CollectWorkspaceFragment.kt:134-157` 四级门控(Restoring/SignedOut/CONFIG/PROJECT);`BlePermissions.kt:24-31` API 31+ 分版 BLUETOOTH_SCAN/CONNECT(neverForLocation)vs 旧版 ACCESS_FINE_LOCATION,与 Manifest maxSdkVersion="30" 划线一致;拒绝态提供跳系统设置 | ✅(真机权限行为「模拟器不适用」) | +| 11 | 扫描/连接/采集会话(M4-02/03/07/08) | `CollectWorkspaceFragment.kt:171-253` 扫描启停+空态、会话卡(选任务→选测点→开始/停止)、实时数据卡按设备类型切组(IE-1000 压力/温度;VM208 频率/温度/电压/质量)、VM208 激励卡(仅 GattState.Ready 可发)、自动重连开关(M4-09)、演示模式(FakeBleDriver);本地队列 `UploadQueueDao.enqueue`(Room,M4-07 离线优先) | ✅(BLE 实收「模拟器不适用」,FakeBleDriver 可演练) | +| 12 | 测点列表/基准点/工点(M5-01/02/03) | `PointApi.kt` 2.9/2.14/2.15 三端点接入;`SurveyPointListFragment/ViewModel` 测组→监测点列表;采集页上下文卡常驻(`CollectWorkspaceFragment.kt:167-169`) | ✅ | +| 13 | 手动录入/补录(M5-04) | `ManualEntryViewModel.kt:39-123` 表单编排+任务选择,`uploadQueueDao.enqueue` 与蓝牙采集**同一队列**(`DATA_TYPE_MANUAL="手动录入"`) | ✅ | +| 14 | 统计页与上传(M6) | `StatsWorkspaceFragment.kt:22-60` 队列状态卡(待传/成功/失败+手动重试)、SAF 原始文件上传(`ActivityResultContracts.OpenDocument`,免存储权限,M6-01→2.13)、计划统计表行点击下钻测组(2.11)→监测点(2.12);`UploadQueueSyncer.kt:38-46,91-107,178-204` 启动触发+VALIDATED 网络恢复回调(onAvailable 防抖)+手动重试三通道补传,失败回退 PENDING 且 retry_count+1 留痕 | ✅ | +| 15 | 5 Tab 全业务页(无占位) | `FeatureMount.kt:36-42` 五 Tab 挂载 `ProjectWorkspaceFragment/TaskWorkspaceFragment/CollectWorkspaceFragment/StatsWorkspaceFragment/MineFragment`,`PlaceholderFragment` 未被挂载 | ✅ | + +> 推演局限:静态核对可证明「分支存在且互斥完整」,不能替代真机交互与 BLE 空中包验证;相关项在 §6 功能点清单中以「降级」口径计。 + +## 6. 功能点判定清单(本期 32 点) + +判定口径:**通过** = 代码实现+(实测或静态推演)证据完整;**降级** = 实现完成但因环境限制未做实机/实机交互验证;**缺口** = 功能缺失或行为不符。 + +| 模块 | 功能点 | 判定 | 依据(章节) | +|---|---|---|---| +| M1 | M1-01 账号密码登录 | 通过 | 契约层 §3.2#1 + UI §5.2#4/5 + `SessionRepository.kt:89` | +| M1 | M1-02 发送验证码(%2B86 转换) | 通过 | 契约层 §3.2#2(B 组证实进入发送流程)+ `Phones.kt:28-31` | +| M1 | M1-03 验证码登录(国内号全匹配) | 通过 | 契约层 §3.2#3 + `Phones.kt:16` + `LoginViewModel.kt:94` | +| M1 | M1-04 修改登录密码 | 通过 | 契约层 §3.2#4 + `SessionRepository.kt:108-113`(成功后登出重登) | +| M1 | M1-05 当前用户信息展示 | 通过 | 契约层 §3.2#5 + `MineFragment.kt:85-98`(含二次 JSON 编码宽容解析 `SessionRepository.kt:146-156`) | +| M1 | M1-06 服务器与密钥配置管理 | 通过 | UI §5.2#2/3 + `ConfigRepository.kt`(DataStore+加密、可随时换密钥不换包) | +| M1 | M1-07 退出登录与 Token 清理 | 通过 | `SessionRepository.kt:116-119` + `MineFragment.kt:51` | +| M2 | M2-01/M2-02 项目列表与信息展示 | 通过(降级*) | 契约层 §3.2#6 + UI §5.2#7;列表内容渲染未实机截图 | +| M2 | M2-03 项目切换联动 | 通过(降级*) | UI §5.2#8(ProjectSession 订阅链完整) | +| M3 | M3-01 APP 端任务列表 | 通过(降级*) | 契约层 §3.2#7 + UI §5.2#9 | +| M3 | M3-02 监测计划任务列表 | 通过(降级*) | 契约层 §3.2#8 + UI §5.2#9 | +| M3 | M3-03 任务详情 | 通过(降级*) | `TaskDetailFragment.kt` + `TaskRepository.kt`(2.6/2.7 data 归一) | +| M3 | M3-04 状态流转展示 | 通过(降级*) | `TaskStatus` 过滤 Chip + 列表渲染(`TaskWorkspaceFragment.kt:82-91`) | +| M3 | M3-05 筛选与下拉刷新 | 通过(降级*) | `TaskWorkspaceFragment.kt:65-73,82-91` | +| M4 | M4-01 权限申请分版 | 通过(降级*) | `BlePermissions.kt`(API 31 划线)+ Manifest 一致性 | +| M4 | M4-02 设备扫描(名称前缀过滤) | 通过(降级*) | `BleScanner.kt` + UI §5.2#11;真机扫描不适用模拟器 | +| M4 | M4-03 设备连接管理 | 通过(降级*) | `BleConnector.kt`/`SystemBleDriver.kt` GATT 状态流;模拟器不适用 | +| M4 | M4-04 IE-1000 解析 | **通过** | 协议层 §4(静态矩阵+单测实测)——协议本体已实测,实机 Notify 通道不适用 | +| M4 | M4-05 VM208 解析 | **通过** | 同上 §4 | +| M4 | M4-06 VM208 激励下发 | **通过**(协议)/ 降级(BLE 通道) | §4 编码实测 + `CollectWorkspaceFragment.kt:250-253` | +| M4 | M4-07 本地暂存队列(Room) | 通过(降级*) | `UploadQueueSyncer.kt` 状态机 + `UploadQueueEntity`;:app 单测静态审阅 | +| M4 | M4-08 采集会话管理 | 通过(降级*) | UI §5.2#11(Idle/Preparing/Running 状态机) | +| M4 | M4-09 断连与自动重连 | 通过(降级*) | `autoReconnect` 开关 + 重连策略;真机断连不适用模拟器 | +| M5 | M5-01 测组监测点列表 | 通过(降级*) | 契约层 §3.2#9 + `SurveyPointListFragment` | +| M5 | M5-02 基准点获取 | 通过(降级*) | 契约层 §3.2#14(含 2.14 服务端特例记录 §3.4) | +| M5 | M5-03 工点数据 | 通过(降级*) | 契约层 §3.2#15 + `PointApi.PATH_GET_WORK_POINT` | +| M5 | M5-04 手动录入/补录 | 通过(降级*) | `ManualEntryViewModel.kt:107-123` 同队列入队 | +| M6 | M6-01 原始文件上传(2.13) | 通过(降级*) | 契约层 §3.2#13(multipart 形状实测)+ SAF 选择器 + :app `UploadPayloadWrapTest` | +| M6 | M6-02 计划统计表 | 通过(降级*) | 契约层 §3.2#10 + `StatsPlanListAdapter` 下钻链 | +| M6 | M6-03 测组统计表 | 通过(降级*) | 契约层 §3.2#11 + `SurveyGroupStatsFragment` | +| M6 | M6-04 监测点统计表 | 通过(降级*) | 契约层 §3.2#12 + `PointStatsFragment` | +| M7* | M7-01~04 基础工程层(附带核查) | 通过 | `AuthInterceptor`/`ApiEnvelope`/`ApiCaller`(三要素+四码统一解析)、Room 队列、ble-protocol 独立 JVM 模块(单测实测跑通)、密钥文件不入库(docs 引用) | + +\* 「降级」含义:静态走查推演通过但未做实机交互验证(§5.1 降级口径),非功能缺陷。标注「通过」且无星号者,其核心证据为**实测**(契约层真实请求 / 协议层单测实跑)。 + +**汇总:32 点全部无缺口;其中实测通过 10 点(契约层 7 点直接承载 + 协议层 3 点),静态推演通过 22 点(降级口径)。** + +## 7. 正向链路遗留项与移交建议 + +**遗留**(阻塞:无真实测试凭据,本次未做正向链路端到端实测): + +1. 真实凭据登录 → 2.4 用户信息 → 2.5.1.1 项目列表 → 2.6/2.7 任务 → 2.9/2.14/2.15 测点上下文 → 2.13 上传 → 2.10/2.11/2.12 统计的**正向全链路实测**(需院方下发测试账号;或由有账号同事在真机回归)。 +2. 2.2.2 验证码真实下发(本次沙箱实测腾讯云 SMS 出网失败,测试服务器自身出网能力需在真实网络环境复核)。 +3. 429/403 负向边界(一分钟频控阈值、连续报错锁定与解锁窗口——待确认问题 #3,需平台方确认规则后专项触发)。 +4. Token 失效场景实测(长 Token 过期后 2.4 应返回失败并按「重新登录」处理——`SessionRepository.kt:79-85` 已实现该缺省口径,待真实验证)。 +5. 真机 BLE 空中包回归(IE-1000/VM208 真实设备 Notify/Write 与断连重连)。 +6. 服务端反馈项:§3.4 的 2.14 ControlPoint 鉴权包装不一致;§3.5 的 2.8.2 死代码常量清理(客户端自查项)。 + +**移交建议**:①向院方申请测试账号与验证码通道白名单,补做 §7.1/7.2 正向链路与 429 边界;②将 `docs/e2e-evidence/contract_probe.py` 纳入例行回归(真实凭据就绪后可扩展正向断言);③UI 层在 CI/真机资源具备时按 §5.2 表逐项补实机截图验收。 + +## 8. 证据文件索引 + +| 证据 | 路径 | +|---|---| +| 契约层探测脚本 | `docs/e2e-evidence/contract_probe.py` | +| 契约层机器可读结果 | `docs/e2e-evidence/contract_results.json` | +| 契约层证据汇总表(自动生成) | `docs/e2e-evidence/contract_evidence.md` | +| 原始响应存档(30 份,密钥脱敏) | `docs/e2e-evidence/responses/A_*.txt`、`B_*.txt` | +| 协议层测试源码(静态审阅对象) | `ble-protocol/src/test/kotlin/com/stec/cmd/protocol/**` | +| 协议层实测 JUnit 结果(XML + HTML) | `docs/e2e-evidence/protocol-test-results/`(含 `TEST-*.xml`、`html/index.html`) | +| 契约依据解析全文 | `docs/e2e-evidence/api_doc_V4.5_extracted.txt`(自 docx 原件解析:`docs/上海城建勘测院监测数据不落地系统接口文档V4.5.docx`) | +| 首次连通性探测(curl 手工样例) | `docs/e2e-evidence/responses/manual_curl_GetUserInfo_nohdr.txt` | diff --git a/docs/e2e-evidence/api_doc_V4.5_extracted.txt b/docs/e2e-evidence/api_doc_V4.5_extracted.txt new file mode 100644 index 0000000..2945ca5 --- /dev/null +++ b/docs/e2e-evidence/api_doc_V4.5_extracted.txt @@ -0,0 +1,1598 @@ +上海城建勘测院 +监测数据不落地平台 +接口管理规范 +2021年12月 +目录 +目录1 +修订记录8 +1.基础数据说明9 +1.1.接口返回状态值说明9 +1.2.接口固定参数值说明9 +1.2.1.请求头参数SecretKey9 +1.2.2.请求头参数SystemCode10 +1.2.3.请求头参数Token10 +1.2.4.测试系统IP和端口10 +2.接口说明10 +2.1.所有API目录10 +2.2.登录接口11 +2.2.1.账号密码登录接口11 +2.2.1.1.接口描述11 +2.2.1.2.接口请求URL地址11 +2.2.1.3.接口请求方式11 +2.2.1.4.接口参数12 +2.2.1.5.接口返回值格式12 +2.2.1.6.返回值示例13 +2.2.2.发送验证码(获取验证码)13 +2.2.2.1.接口描述13 +2.2.2.2.接口请求URL地址13 +2.2.2.3.接口请求方式13 +2.2.2.4.接口参数14 +2.2.2.5.接口返回值格式14 +2.2.2.6.返回值示例15 +2.2.3.手机验证码登录15 +2.2.3.1.接口描述15 +2.2.3.2.接口请求URL地址15 +2.2.3.3.接口请求方式15 +2.2.3.4.接口参数16 +2.2.3.5.接口返回值格式16 +2.2.3.6.返回值示例17 +2.3.修改密码18 +2.3.1.修改登录密码接口18 +2.3.1.1.接口描述18 +2.3.1.2.接口请求URL地址18 +2.3.1.3.接口请求方式18 +2.3.1.4.接口参数18 +2.3.1.5.接口返回值格式19 +2.3.1.6.返回值示例19 +2.4.获取当前用户信息19 +2.4.1.接口描述19 +2.4.2.接口请求URL地址19 +2.4.3.接口请求方式20 +2.4.4.接口参数20 +2.4.5.接口返回值格式20 +2.4.6.返回值示例21 +2.5.获取项目数据接口21 +2.5.1.1.获取当前用户项目列表21 +2.5.1.2.接口描述21 +2.5.1.3.接口请求URL地址21 +2.5.1.4.接口请求方式21 +2.5.1.5.接口请求头参数22 +2.5.1.6.接口返回值格式22 +2.5.1.7.返回值示例23 +2.6.获取APP端任务列表23 +2.6.1.接口描述23 +2.6.2.接口请求URL地址23 +2.6.3.接口请求方式24 +2.6.4.接口参数24 +2.6.5.接口返回值格式25 +2.6.6.返回值示例25 +2.7.获取项目当前监测计划的任务列表26 +2.7.1.接口描述26 +2.7.2.接口请求URL地址26 +2.7.3.接口请求方式27 +2.7.4.接口参数27 +2.7.5.接口返回值格式27 +2.7.6.返回值示例28 +2.8.获取监测计划及上传监测值(废除)28 +2.8.1.获取监测计划28 +2.8.1.1.接口描述28 +2.8.1.2.接口请求URL地址29 +2.8.1.3.接口请求方式29 +2.8.1.4.接口参数29 +2.8.1.5.接口返回值格式29 +2.8.1.6.返回值示例30 +2.8.2.上传本次监测值30 +2.8.2.1.接口描述30 +2.8.2.2.接口请求URL地址30 +2.8.2.3.接口请求方式31 +2.8.2.4.接口参数31 +2.8.2.5.参数MonitorValue对象31 +2.8.2.6.接口返回值格式31 +2.9.获取测组包含的监测点列表32 +2.9.1.接口描述32 +2.9.2.接口请求URL地址32 +2.9.3.接口请求方式32 +2.9.4.接口参数32 +2.9.5.接口返回值格式33 +2.9.6.返回值示例33 +2.9.6.1.PointList 所关联监测点33 +2.9.6.2.LastMonitorData属性说明34 +2.10.获取项目监测计划统计表34 +2.10.1.接口描述34 +2.10.2.接口请求URL地址34 +2.10.3.接口请求方式34 +2.10.4.接口参数35 +2.10.5.接口返回值格式35 +2.10.6.返回值示例35 +2.11.获取指定监测计划的测组统计表36 +2.11.1.接口描述36 +2.11.2.接口请求URL地址36 +2.11.3.接口请求方式36 +2.11.4.接口参数36 +2.11.5.接口返回值格式37 +2.11.6.返回值示例37 +2.12.监测点统计表38 +2.12.1.接口描述38 +2.12.2.接口请求URL地址38 +2.12.3.接口请求方式38 +2.12.4.接口参数38 +2.12.5.接口返回值格式39 +2.12.6.返回值示例39 +2.13.数据解析(原始文件上传)40 +2.13.1.接口描述40 +2.13.2.接口请求URL地址40 +2.13.3.接口请求方式40 +2.13.4.接口参数40 +2.13.4.1.参数MonitorData对象41 +2.13.5.接口返回值格式41 +2.14.获取基准点42 +2.14.1.接口描述42 +2.14.2.接口请求URL地址42 +2.14.3.接口请求方式42 +2.14.4.接口参数42 +2.14.5.接口返回值格式43 +2.14.6.返回值示例43 +2.15.获取工点数据44 +2.15.1.接口描述44 +2.15.2.接口请求URL地址44 +2.15.3.接口请求方式44 +2.15.4.接口参数44 +2.15.5.接口返回值格式45 +2.15.6.返回值示例46 +修订记录 +日期 +版本 +编制/修订内容摘要 +编制人 +2022-1-24 +V3.5 +增加章节2.8的接口,根据项目获取项目的监测项接口 +漆志立 +2022--17 +V3.8 +增加章节2.1.2发送验证码的接口 +修改章节2.1.3手机验证码登录接口 +修改章节2.3获取当前用户信息接口, +修改章节2.4获取项目数据接口 +变更章节2.5位获取APP端任务列表接口 +变更章节2.6为获取项目当前监测计划的任务列表接口 +变更章节2.8为获取测组包含的监测点列表接口 +增加章节2.9获取项目监测计划统计表接口 +增加章节2.10获取指定监测几乎统计表接口 +增加章节2.11监测点统计表接口 +增加章节2.12数据上传接口 +增加章节2.13获取基准点接口, +增加章节2.14获取项目信息接口 +李勇潘 +2022-08 +V3.9 +增加所有api接口列表的表格说明,章节2.1,原来接口的章节编号说明顺延 +原章节2.14和原2.5合并,取消原章节2.14接口说明 +修改2.13数据解析(原始文件上传)接口类型 +2022-12 +V4.0 +修改章节2.6,2.6.2新增接口URL请求地址(接口3);2.6.5返回值TaskList 所关联监测点中新增属性工点ID +修改章节2.13,2.3.4接口参数新增WorkPointID(工点ID),原参数MonitorData改为PointsData +增加章节2.15获取工点数据接口 +增加章节2.16上传水位自动化数据 +2023-03 +V4.1 +修改章节2.9获取测组包含的监测点列表,修改接口传递数值类型 +2023-04 +V4.2 +2.13接口中 2.13.4.1.3.1CXSubData对象不在需要上传FrontCurrentValue(管形位移改正前)值,CurrentValue改为上传管形位移改正前值2.13接口支持上传支撑轴力数据 +2023-06 +V4.3 +增加2.17章节接口 +2023-07 +V4.4 +2.13接口支持上传地下水位数据 +2023-07 +V4.5 +2.6接口将返回WorkPointName工点名称 +2.9接口新增属性AxiaType(支撑轴力监测类型),InitialValueData,LastMonitorData将返回传感器编号 +2.13接口支撑轴力上传数据新增属性InitialFrequency(初始频率),InitialTemperature(初始温度) +基础数据说明 +接口返回状态值说明 +状态值编码 +值描述 +200 +接口请求成功 +500 +接口请求失败 +429 +同一条数据一分钟内频繁请求接口,次数超过允许请求最大次数 +403 +同一条数据请求接口连续报错,次数超过允许出错的最大次数 +接口固定参数值说明 +请求头参数SecretKey +PC端系统为每个调用接口的供应商分配一个调用接口的秘钥,此秘钥在调用接口的时候会校验,如果校验成功,才允许继续调用接口,如果校验失败,则不允许调用接口。 +此秘钥为随机分配秘钥,可能会定期更换,供应商最好做成配置项方便在 不调整程序的前提下随时修改秘钥值。 +此参数放在请求头同中请求接口。 +请求头参数SystemCode +PC端系统为每个调用接口的供应商分配一个调用接口的供应商编码,用于记录调用接口日志,方便以后追踪是哪个供应商调用的接口。 +“SystemCode”与“SecretKey”一一对应,如果调用接口时2者之间不匹配,也无法成功调用接口。 +此参数放在请求头同中请求接口。 +请求头参数Token +调用接口(除登录接口外)时放到请求头里面,避免重要信息泄露 +测试系统IP和端口 +http://106.15.183.20:8006/ +接口说明 +所有API目录 +API地址 +所在章节 +API接口说明 +/OutWebApi/api/LoginByAccount +章节2.2.1 +账号密码登录接口 +/OutWebApi/api/LoginSecurityCode +章节2.2.2 +发送短信验证码接口 +/OutWebApi/api/LoginByMobile +章节2.2.3 +手机验证码登录接口 +/OutWebApi/api/ModifyPassword +章节2.3.1 +修改登录密码接口 +/OutWebApi/api/GetUserInfo +章节2.4 +获取当前用户信息 +/OutWebApi/api/GetProjectList +章节2.5 +获取当前用户项目列表 +/OutWebApi/api/GetUserPlanGroup +章节2.6 +获取APP端任务列表 +/OutWebApi/api/GetPlanGroup +章节2.7 +项目当前监测计划的任务列表 +/OutWebApi/api/GetPointBySurveyGroup +章节2.9 +获取测组包含的监测点列表 +/OutWebApi/api/GetPlanStatistical +章节2.10 +获取项目监测计划统计表 +/OutWebApi/api/GetSurveyGroupList +章节2.11 +获取指定监测计划的测组统计表 +/OutWebApi/api/GetPointStatistics +章节2.12 +监测点统计表 +/OutWebApi/api/AnalysisMonitorData +章节2.13 +数据上传(原始文件) +/OutWebApi/api/ControlPoint +章节2.14 +获取基准点 +/OutWebApi/api/ControlPoint +章节2.15 +获取工点(进行中的) +登录接口 +账号密码登录接口 +接口描述 +用户通过账号密码登录手机APP,校验并获取PC端用户token +接口请求URL地址 +/OutWebApi/api/LoginByAccount +接口请求方式 +POST请求,参数传递格式:json格式,格式参见参数说明 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +调用接口供应商编码,具体说明见章节1.2.2 +UserLoginData +UserLoginData对象 +UserLoginData对象参数,具体说明见章节2.2.1.4.1 +参数UserLoginData +参数格式示例:{“LoginName”:”XXXX”,” Password”:”XXXX”} +属性编号 +属性类型 +属性名称 +属性说明 +LoginName +String +登录名称 +必须项 用户登录账号 +Password +String +登录密码 +必须项 用户登录密码 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :“Token:token值” +}, +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :null +} +返回值示例 +章节2.1.1.5中执行成功的返回值(data)中的属性说明 +属性名称 +属性说明 +参数说明 +Token +用户登录Token +获取的Token用于获取当前用户项目, 当前用户信息和设置密码和数据上传时获取当前上传人。 +发送验证码(获取验证码) +接口描述 +用户通过手机号码获取登录验证码 +接口请求URL地址 +/OutWebApi/api/LoginSecurityCode?MobilePhone=XXXX +接口请求方式 +GET请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +调用接口秘钥,具体说明见章节1.2.2 +MobilePhone +String +发送短信的目标手机号, +可在手机号前+86、86等表示区域或者不加,不加区域代表是国内手机号,发送短信支持国际手机号, +登录校验目前程序仅支持国内手机号,以防手机号乱维护,所以用了全匹配模式判断手机号是否属于某用户 +如果传递手机号之前用(+86),需要处理下特殊字符,改为(%2B86),否则发送不成功 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :”result:true” +}, +同时手机会收到验证码提示短信 +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”result:false” +} +返回值示例 +章节2.1.1.5中执行成功的返回值(data)中的属性说明 +属性名称 +属性说明 +参数说明 +result +验证码是否发送成功 +验证码发送成功会返回true,发送失败会返回false +手机验证码登录 +接口描述 +用户通过手机号码验证码校验后App端调用接口获取PC端用户token +接口请求URL地址 +/OutWebApi/api/LoginByMobile +接口请求方式 +Post请求传递Json参数 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +调用接口秘钥,具体说明见章节1.2.2 +MobileMsg +MobileMsg对象 +见2.2.3.4.1 +MobileMsg说明 +参数格式说明{“Mobile”:“XXXXX”,“VerificationCode”:“XXXXX”} +参数名称 +参数类型 +参数说明 +Mobile +String +用户手机号 +可在手机号前+86、86等表示区域或者不加,登录校验目前程序仅支持国内手机号,以防手机号乱维护,所以用了全匹配模式判断手机号是否属于某用户 +VerificationCode +String +短信登录验证码 +如果用户请求发送多次验证码,则以最近一次发送的验证码为有效验证码 +如果验证码已过提示的有效期,则需要重新请求发送验证码 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :”Token:token值” +}, +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :null +} +返回值示例 +章节2.1.1.5中执行成功的返回值(data)中的属性说明 +属性名称 +属性说明 +参数说明 +Token +用户登录Token +获取的Token用于获取当前用户项目, 当前用户信息和设置密码和数据上传时获取当前上传人。 +修改密码 +修改登录密码接口 +接口描述 +修改用户登录密码 +接口请求URL地址 +/OutWebApi/api/ModifyPassword +接口请求方式 +Post请求传递Json参数 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +调用接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头信息 +UserLoginData +UserLoginData对象 +具体说明见章节2.3.1.4.1 +参数UserLoginData +参数格式示例{“Password”:“XXXXX”,“ConfirmAnswer”:“XXXXX”} +属性编号 +属性类型 +属性名称 +属性说明 +Password +String +原登录密码 +必须项 用户登录密码 +ConfirmAnswer +String +新密码 +必须项用户新密码 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :null +}, +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :null +} +返回值示例 +无 +获取当前用户信息 +接口描述 +获取当前用户信息 +接口请求URL地址 +/OutWebApi/api/GetUserInfo +接口请求方式 +Get请求 +接口参数 +请求头参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :” UserInfo:json格式的列表数据,具体属性见章节2.4.6” +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.3.5中执行成功的返回值(data)中的属性说明 +属性 +属性名称 +属性说明 +Name +用户名称 +DeptName +部门名称 +MobilePhone +手机号 +LoginName +登录账号 +获取项目数据接口 +获取当前用户项目列表 +接口描述 +获取当前用户有权限的、项目阶段为“进行中”的项目列表 +接口请求URL地址 +接口1:/OutWebApi/api/GetProjectList?ProjectID=XXXX +接口2:/OutWebApi/api/GetProjectList +备注:以上2个接口都支持,实际调用请按以下说明具体选用哪个接口 +第一个接口:如果ProjectID传递的值存在,返回的是项目ID=XXXX的项目列表数据,如果ProjectID参数传了空值,则跟第二个接口返回的结果一致 +第二个接口:返回的是当前用户所有的项目列表数据 +接口请求方式 +Get请求 +接口请求头参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头信息 +ProjectID +String +非必填项,项目ID +如果项目ID为空,则返回当前用户所有的项目列表数据 +如果项目ID不为空,则返回项目ID=XXXX的项目列表数据,只是列表数据只有一条 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :” ProjectList:json格式的列表格式数据,具体属性见章节2.5.1.6” +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.5.1.6中执行成功的返回值(data对象)中的属性ProjectList说明,如需其他属性,双方待确认 +属性名称 +属性说明 +参数说明 +ID +项目ID +ProjectCode +项目编号 +SimpleName +项目简称 +ProjectScale +项目等级 +枚举值:总院重点,部门重点,一般项目 +ProjectPhase +项目阶段(状态) +枚举值:未开始、进行中、已结束 +获取APP端任务列表 +接口描述 +获取APP端任务列表(一个项目的一个监测计划的一个测组为一条任务,项目带权限控制,只返回进行中的项目,只返回任务类型为"APP上传"且责任人为本人的测组,如果一条监测计划不包含满足条件的测组,则不会返回该计划关联的任务;) +接口请求URL地址 +接口1:/OutWebApi/api/GetUserPlanGroup?ProjectID=XXXX&planDate =XXXX +接口2:/OutWebApi/api/GetUserPlanGroup?planDate =XXXX +接口3:/OutWebApi/api/GetUserPlanGroup?planDate=XXXX&WorkPointID=XXXX +备注:以上3个接口都支持,实际调用请按以下说明具体选用哪个接口 +第一个接口:如果ProjectID和PlanDate都传递,返回的是某项目下某日期的任务列表,如果ProjectID参数传了空值,则跟第二个接口返回的结果一致 +第二个接口:如果只传递PlanDate,返回某日期下所有“进行中”的项目的任务列表 +第三个接口:如果WorkPointID和PlanDate都传递,返回的是某工点下某日期的任务列表,如果WorkPointID参数传了空值,则跟第二个接口返回的结果一致 +接口请求方式 +GET请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头信息,当前用户token +ProjectID +String +非必填项,项目ID +如果项目ID为空,则返回当前日期所有本人项目的测组任务 +如果项目ID不为空,则返回当前项目本人所负责的测组任务 +以上条件的前提都是以“PlanDate”传递参数为基础 +PlanDate +String +必填项 ,任务所属日期 +日期格式:yyyy-MM-dd(譬如:2022-04-01) +WorkPointID +String +非必填项,工点ID +如果工点ID为空,则返回当前日期所有本人项目下所有工点的测组任务 +如果工点ID不为空,则返回当前工点本人所负责的测组任务 +以上条件的前提都是以“PlanDate”传递参数为基础 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ TaskList: json格式的列表格式数据,具体属性见章节2.5.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.6.5中执行成功的返回值(data对象)中的属性TaskList说明,如需其他属性双方待确认 +TaskList 所关联监测点 +属性 +属性名称 +参数说明 +SimpleName +项目简称 +TaskCode +任务编号 +项目编号-计划期数-测组编号 +ProjectID +项目ID +PlanID +监测计划ID +PlanNum +计划对应期数值 +1,2,3,4,5等之类的值 +Working +施工工况 +Mold +类型 +(常规,加测) +DutyName +责任人 +GroupID +测组ID +WorkPointID +工点ID +WorkPointName +工点名称 +MonitoringType +监测类型 +水平位移,竖向位移,测斜,收敛,水位,轴力 +BeginTime +开始时间 +即上一期计划的截止时间,格式:yyyy-MM-dd HH:mm +EndTime +截止时间 +最低精确到小时,格式:yyyy-MM-dd HH:mm +CompleteDegree +完成度 +本测组已上传点数/测组总点数 +获取项目当前监测计划的任务列表 +接口描述 +获取项目当前监测计划的任务列表(离当前时间最近的一个监测计划所包含的任务列表)) +接口请求URL地址 +接口1:/OutWebApi/api/GetPlanGroup?ProjectID=xxxx +接口2:/OutWebApi/api/GetPlanGroup +备注:以上2个接口都支持,实际调用请按以下说明具体选用哪个接口 +第一个接口:如果ProjectID传递的值存在,返回的是某项目下最近的监测计划任务列表,如果ProjectID参数传了空值,则跟第二个接口返回的结果一致 +第二个接口:如果不传递任何参数,返回所有“进行中”的项目的监测计划任务列表 +接口请求方式 +Get请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +ProjectID +String +非必填项,项目ID,如果值为空,则返回所有项目符合条件的任务列表 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ TaskList: json格式的列表格式数据,具体属性见章节2.7.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.7.5中执行成功的返回值(data对象)中的属性TaskList说明,如需其他属性,双方待确认 +TaskList所关联监测点 +属性名称 +属性说明 +参数说明 +SimpleName +项目简称 +TaskCode +任务编号 +项目编号-计划期数-测组编号 +ProjectID +项目ID +PlanID +监测计划ID +PeriodsNum +期数 +Working +施工工况 +Mold +类型 +(常规,加测) +DutyName +责任人名称 +GroupID +测组ID +MonitoringType +监测类型 +水平位移,竖向位移,测斜,收敛,水位,轴力 +BeginTime +开始时间 +即上一期计划的截止时间,格式:yyyy-MM-dd HH:mm +EndTime +截止时间 +最低精确到小时,格式:yyyy-MM-dd HH:mm +CompleteDegree +完成度 +本测组已上传点数/测组总点数 +获取监测计划及上传监测值(废除) +获取监测计划 +接口描述 +获取监测计划 +接口请求URL地址 +/OutWebApi/api/GetMonitorPoint?ProjectID=XXX +接口请求方式 +Get请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头参数,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头参数,接口供应商编码,具体说明见章节1.2.2 +ProjectID +String +必须项项目ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :”MonitorInfo:监测计划json列表格式的数据对象,具体属性见章节2.5.1.6”, +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.4.1.5中执行成功的返回值(data)中的属性说明MonitorInfo +MonitorInfo 监测计划 +属性 +属性名称 +属性说明 +ID +监测计划ID +PeriodsNum +期数 +ProjectID +项目ID +MonitoringDate +监测日期 +MonitoringType +类型 +常规,加测 +EndTime +截止日期 +SurveyGroup +测组ID +如果有多个测组,用“,”分割,与属性“SurveyGroupName”一一对应 +SurveyGroupName +测组名称 +如果有多个测组,用“,”分割,与属性“SurveyGroup”一一对应 +上传本次监测值 +接口描述 +根据监测计划及监测点上传对应的监测值 +接口请求URL地址 +/OutWebApi/api/UploadMonitorValue +接口请求方式 +Post请求传递Json参数 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头参数,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头参数,接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头信息 +MonitorValue +String +必须项,json格式数据对象,具体说明见章节2.7.2.5 +参数MonitorValue对象 +参数名称 +参数类型 +参数说明 +ProjectID +String +必须项项目ID +PointID +String +必须项监测点ID +Value +String +必须项,本次监测值 +MonitorDate +datetime +必须项 ,本次监测日期 +MonitoringPlanID +String +必须项 监测计划ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :null +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”null” +} +获取测组包含的监测点列表 +接口描述 +获取测组包含的监测点列表 +接口请求URL地址 +/OutWebApi/api/GetPointBySurveyGroup?GroupID=xxxx +接口请求方式 +Get请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +GroupID +String +必填项 测组ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ PointList: json格式的列表格式数据,具体属性见章节2.8.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.9.5中执行成功的返回值(data对象)中的属性PointList说明,如需其他属性,双方待确认 +PointList 所关联监测点 +属性 +属性名称 +属性说明 +ID +监测点ID +PointSimpleName +点名简称 +PointName +点名 +InitialValueData +初值列表 +RateAlarm +速率报警值 +TotalAlarm +累计报警值 +LastMonitorDate +上次观值列表 +见2.9.6.2 LastMonitorDate属性说明 +MonitorDate +上次观测时间 +AxiaType +支撑轴力监测类型 +支撑轴力监测类型分为:“钢支撑”,“钢筋混凝土支撑(不考虑温度)”,“钢筋混凝土支撑(混凝土应变计/考虑温度)”,“钢筋混凝土支撑(非混凝土应变计/考虑温度)”4种 +InitialValueData属性说明 +属性 +属性名称 +属性说明 +Attribute +属性 +监测类型为测斜时属性为孔深值,监测类型为支撑轴力时属性为轴力,其他监测类型属性为高程 +InitialValue +初值 +LastMonitorData属性说明 +属性 +属性名称 +属性说明 +Attribute +属性 +监测类型为测斜时属性为孔深值,监测类型为支撑轴力时属性为传感器编号,其他监测类型属性为高程 +LastMonitorValue +上次观测值 +获取项目监测计划统计表 +接口描述 +获取项目监测计划统计表,只列出包含"APP上传"测组的监测计划 +接口请求URL地址 +/OutWebApi/api/GetPlanStatistical?ProjectID =xxxx +接口请求方式 +Get请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +ProjectID +String +必填项,项目ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ PlanList: json格式的列表格式数据,具体属性见章节2.10.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.10.5中执行成功的返回值(data对象)中的属性PlanList说明,如需其他属性,双方待确认 +PlanList说明 +属性名称 +属性说明 +参数说明 +PlanID +监测计划ID +PeriodsNum +期数 +EndTime +截止时间 +Working +施工工况 +CompleteDegree +完成度 +非APP上传类型的测组不参与统计,未开始上传的计划的完成度为0 +Mold +类型 +(常规,加测) +获取指定监测计划的测组统计表 +接口描述 +获取指定监测计划的测组统计表,只列出包含"APP上传"测组的监测计划 +接口请求URL地址 +/OutWebApi/api/GetSurveyGroupList?PlanID=xxxx +接口请求方式 +Get请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +PlanID +String +必填项,监测计划ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ GroupList: json格式的列表格式数据,具体属性见章节2.11.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.11.5中执行成功的返回值(data对象)中的属性GroupList说明,如需其他属性,双方待确认 +GroupList说明 +属性 +属性名称 +属性说明 +GroupID +测组ID +GroupName +测组名称 +DutyName +责任人 +CompleteDegree +完成度 +MonitoringType +监测类型 +监测点统计表 +接口描述 +获取监测点统计表 +接口请求URL地址 +/OutWebApi/api/GetPointStatistics?PlanID=xxxx&GroupID=xxxxx +接口请求方式 +Get请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +PlanID +String +必填项,监测计划ID +GroupID +String +必填项,测组ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ PointsList: json格式的列表格式数据,具体属性见章节2.12.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.12.5中执行成功的返回值(data对象)中的属性PointsList说明,如需其他属性,双方待确认 +PointsList说明 +属性 +属性名称 +属性说明 +PointName +点名 +ImportTime +上传时间 +MonitorEqu +设备 +数据解析(原始文件上传) +接口描述 +解析用户上传得监测数据,目前只兼容水平位移的数据(涉及到公式计算) +接口请求URL地址 +/OutWebApi/api/AnalysisMonitorData +接口请求方式 +Post请求, +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +Token +string +请求头当前用户信息,具体说明见章节1.2.3 +ProjectID +multipart/form-data +必须项 监测项目ID +WorkPointID +multipart/form-data +必须项 监测工点ID +SurveyGroupID +multipart/form-data +必须项 测组ID +PlanID +multipart/form-data +必须项 监测计划ID +MonitorDate +multipart/form-data +监测日期 +WorkInfo +multipart/form-data +工况 +Equipment +multipart/form-data +观测设备 +Weather +multipart/form-data +天气 +PointsData +multipart/form-data +List对象,json数组,格式参见章节2.13.4.1(MonitorData改为PointsData) +Files +multipart/form-data +此参数名可不用写,接口自动获取文件类型,目前只支持接收单个文件 +参数PointsData对象 +参数示例:由于各类型(竖向、水位、测斜)的观测数据衡量方式可能不一样,后续这个对象的格式会根据不同的类型有不同的格式要求,目前确定的格式有以下3种(此格式要求是为了和报表上传的格式统一) +竖向位移 +[{“Point”:“”,“PointName”:“”, “CurrentValue”:“”,“CurrentChangeValue”:“”,“IsInitValue”:“”}] +参数名称 +参数类型 +参数说明 +Point +String +必须项属性,监测点ID +PointName +String +必须项属性,监测点Name +CurrentValue +Decimal +必须项本次观测值 +CurrentChangeValue +Decimal +非必须属性,本次变化值,移动端的计算结果,如果移动端无计算结果,此属性可不传,为了和报表上传的方式统一 +IsInitValue +String +1.是否重取初值,枚举值:1、0(1:代表重取初值,0代表否), +2.如果传递值为空,则默认为0 +水平位移 +[{“Point”:“”,“PointName”:“”, “CurrentValue”:“”,“CurrentChangeValue”:“”,“IsInitValue”:“”,“X”:“”,“Y”:“”}] +参数名称 +参数类型 +参数说明 +Point +String +必须项属性,监测点ID +PointName +String +必须项属性,监测点Name +CurrentValue +Decimal +必须项本次观测值,单位(mm) +CurrentChangeValue +Decimal +非必须属性,本次变化值,app的计算结果,如果app无此计算结果,此属性可不传,预留属性(方便和报表上传统一) +IsInitValue +String +1.是否重取初值,枚举值:1、0(1:代表重取初值,0代表否), +2.如果传递值为空,则默认为0 +X +Decimal +水平位移类型的监测数据原始数据,如果app端无,可不传递 +Y +Decimal +水平位移类型的监测数据原始数据,如果app端无,可不传递 +备注: CurrentValue是X或Y的值,具体是X还是Y看系统中测点配置的位移方向是X还是Y +测斜 +[{"Point":"","PointName":"","IsInitValue":"","CxSubData":[{"Depth":,"CurrentValue":,"CurrentChangeValue":,"A0":,"A180":,"FrontCurrentValue":}]}] +参数名称 +参数类型 +参数说明 +Point +String +必须项属性,监测点ID +PointName +String +必须项属性,监测点Name +IsInitValue +String +1.是否重取初值,枚举值:1、0(1:代表重取初值,0代表否), +2.如果传递值为空,则默认为0 +CxSubData +List +List对象,json数组,格式参见章节2.13.4.1.1 +CxSubData对象 +参数名称 +参数类型 +参数说明 +Depth +String +必须项属性,深度 +CurrentValue +Decimal +必须项管形位移量(改正前),单位(mm) +CurrentChangeValue +Decimal +非必须属性,本次变化值,app的计算结果,如果app无此计算结果,此属性可不传,预留属性(方便和报表上传统一) +A0 +Decimal +非必须属性,A0 +A180 +Decimal +非必须属性,A180 +管形位移量(改正后)=当期孔口改正点(系统中测点配置中关联的水平位移点)的累计变化量-管形位移量(改正前) +支撑轴力 +[{"Point":"","PointName":"", " CurrentValue":"", " CurrentChangeValue ":"",”IsInitValue”:”” " ZLSubData ":[{" SensorNumber":," Frequency":,”InitialFrequency”:,"SingleAxialForce":,"Temperature":,”InitialTemperature”:}]}] +参数名称 +参数类型 +参数说明 +Point +String +必须项属性,监测点ID +PointName +String +必须项属性,监测点Name +CurrentValue +String +必须项属性,本次轴力(KN) +CurrentChangeValue +Decimal +非必须属性,本次变化值,app的计算结果,如果app无此计算结果,此属性可不传,预留属性(方便和报表上传统一) +IsInitValue +String +1.是否重取初值,枚举值:1、0(1:代表重取初值,0代表否), +2.如果传递值为空,则默认为0 +ZLSubData +List +List对象,json数组,格式参见章节2.13.4.1.1 +ZLSubData对象 +参数名称 +参数类型 +参数说明 +SensorNumber +String +必须项属性,传感器编号,(当支撑轴力监测类型为“钢支撑”时最多上传2个传感器的数据,其他监测类型最多上传4个传感器的数据) +InitialFrequency +Decimal +必须项属性,传感器初始频率(Hz) +Frequency +Decimal +必须项属性,本次监测频率(Hz) +SingleAxialForce +Decimal +非必须属性,单个轴力值(kN) +InitialTemperature +Decimal +非必须属性,传感器初始温度(℃)(当支撑轴力监测类型为“钢筋混凝土支撑(混凝土应变计/考虑温度)”或“钢筋混凝土支撑(非混凝土应变计/考虑温度)”时,此参数必填) +Temperature +Decimal +非必须属性,本次监测时温度(℃)(当支撑轴力监测类型为“钢筋混凝土支撑(混凝土应变计/考虑温度)”或“钢筋混凝土支撑(非混凝土应变计/考虑温度)”时,此参数必填) +地下水位 +[{“Point”:“”,“PointName”:“”, “CurrentValue”:“”,“CurrentChangeValue”:“”,“NozzleDepth”:“”,“NozzleElevation”:“”’,“IsInitValue”:“”}] +参数名称 +参数类型 +参数说明 +Point +String +必须项属性,监测点ID +PointName +String +必须项属性,监测点Name +CurrentValue +Decimal +必须项属性,本次高程值 +CurrentChangeValue +Decimal +非必须属性,本次变化值,移动端的计算结果,如果移动端无计算结果,此属性可不传,为了和报表上传的方式统一 +NozzleDepth +Decimal +必须项属性,管口深度(m) +NozzleElevation +Decimal +非必须属性,管口高程(m) +IsInitValue +String +1.是否重取初值,枚举值:1、0(1:代表重取初值,0代表否), +2.如果传递值为空,则默认为0 +接口返回值格式 +接口调用示例图 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data:null +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :null +} +获取基准点 +接口描述 +获取项目基准点信息 +接口请求URL地址 +/OutWebApi/api/ControlPoint?ProjectID=XXX +接口请求方式 +Get +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +ProjectID +String +项目ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ ControlPointList: json格式的列表格式数据,具体属性见章节2.14.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.14.5中执行成功的返回值(data对象)中的属性ControlPointList +ControlPointList说明 +属性 +属性名称 +属性说明 +Point +点名 +PointX +X值 +PointY +Y值 +PointZ +H值 +获取工点数据 +接口描述 +获取工点列表数据 +接口请求URL地址 +接口1:/OutWebApi/api/GetWorkPoint +接口2:/OutWebApi/api/GetWorkPoint?ProjectID=XXXX +接口3:/OutWebApi/api/GetWorkPoint?WorkPointID=XXXX +备注:以上3个接口都支持,实际调用请按以下说明具体选用哪个接口 +第一个接口:如果都不传递,返回所有“进行中”的项目下现场负责人是本人的“进行中”的工点列表数据 +第二个接口:如果ProjectID传递,返回的是某项目下的现场负责人是本人的“进行中”的工点列表数据,如果ProjectID参数传了空值,则跟第一个接口返回的结果一致 +第三个接口:如果WorkPointID传递,返回的是某个现场负责人是本人的“进行中”的工点数据,如果WorkPointID参数传了空值,则跟第一个接口返回的结果一致 +接口请求方式 +GET请求 +接口参数 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头信息,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头信息,调用接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头信息,当前用户token +ProjectID +String +非必填项,项目ID +1.如果项目ID为空,则返回所有“进行中”的项目下现场负责人是本人的“进行中”的工点列表数据 +2.如果项目ID不为空,则返回当前项目下现场负责人是本人的“进行中”的工点列表数据 +WorkPointID +String +非必填项,工点ID +1.如果工点ID为空,返回所有“进行中”的项目下现场负责人是本人的“进行中”的工点列表数据 +2.如果工点ID不为空,则返回“进行中”的项目下某个现场负责人是本人的“进行中”的工点数据 +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data :{ WorkPointList: json格式的列表格式数据,具体属性见章节2.15.6} +} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :”如果需要接口返回数据,则返回数据在此属性中” +} +返回值示例 +章节2.15.5中执行成功的返回值(data对象)中的属性WorkPointList说明,如需其他属性双方待确认 +WorkPointList 工点数据 +属性 +属性名称 +参数说明 +ID +工点ID +Name +工点名称 +ProjectID +项目ID +ProjectName +项目全称 +Lon +经度 +Dim +纬度 +StartDate +开工时间 +最低精确到天,格式:yyyy-MM-dd +EndDate +结束时间 +最低精确到天,格式:yyyy-MM-dd +上传水位自动化数据 +接口描述 +上传水位自动化数据 +接口请求URL地址 +/OutWebApi/api/UploadAutomation +接口请求方式 +Post请求 +接口请求方式 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头参数,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头参数,接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头当前用户信息,具体说明见章节1.2.3 +PointID +multipart/form-data +必须项 测点ID +PointName +multipart/form-data +必须项 测点名称 +CurrentValue +multipart/form-data +必须项 本次观测值 +CurrentChangeValue +multipart/form-data +必须项 本次变化 +SLChangeValue +multipart/form-data +必须项 变化速率 +LJChangeValue +multipart/form-data +必须项 累计变化 +MonitorDate +multipart/form-data +必须项 监测时间 +UploadDate +multipart/form-data +必须项 上传时间 +接口返回值格式 +接口调用示例图 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data:null +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :null +} +根据监测计划获取测点 +接口描述 +根据监测计划获取该监测计划相关的测点信息列表 +接口请求URL地址 +/OutWebApi/api/GetPointsByPlan +接口请求方式 +Get请求 +接口请求方式 +参数名称 +参数类型 +参数说明 +SecretKey +String +请求头参数,调用接口秘钥,具体说明见章节1.2.1 +SystemCode +String +请求头参数,接口供应商编码,具体说明见章节1.2.2 +Token +String +请求头当前用户信息,具体说明见章节1.2.3 +PlanID +string +必须项 监测计划ID +GroupID +string +必须项 测项ID +WorkPointID +string +必须项 工点ID +接口返回值格式 +接口请求后返回值类型:String,即:以JSON格式返回数据集 +执行成功返回值 +{ +code:“200”, +msg:“执行成功提示信息”, +data:{ PointList: json格式的列表格式数据,具体属性见章节2.17.6} +执行失败返回值 +{ +code:“500”, +msg:“执行失败提示信息”, +data :null +} +返回值示例 +章节2.17.5中执行成功的返回值(data对象)中的属性PointList说明,如需其他属性,双方待确认 +PointList 所关联监测点 +属性 +属性名称 +属性说明 +ID +监测点ID +PointSimpleName +点名简称 +PointName +点名 +InitialValueData +初值列表 +RateAlarm +速率报警值 +TotalAlarm +累计报警值 +LastMonitorDate +上次观值列表 +见2.9.6.2 LastMonitorDate属性说明 +MonitorDate +上次观测时间 +InitialValueData属性说明 +属性 +属性名称 +属性说明 +Attribute +属性 +不叫非测斜类型,测斜是孔深值,水准是高程或为空都可以 +InitialValue +初值 +LastMonitorData属性说明 +属性 +属性名称 +属性说明 +Attribute +属性 +不叫非测斜类型,测斜是孔深值,水准是高程或为空都可以 +LastMonitorValue +上次观测值 \ No newline at end of file diff --git a/docs/e2e-evidence/contract_evidence.md b/docs/e2e-evidence/contract_evidence.md new file mode 100644 index 0000000..8fb36d3 --- /dev/null +++ b/docs/e2e-evidence/contract_evidence.md @@ -0,0 +1,40 @@ +# 契约层负向探测证据表(自动生成于 2026-09-04 03:11:54) + +- 服务器:https://jcd.stec.p-q.co(接口管理规范 V4.5 测试环境) +- 用例组 A:缺三要素头(SecretKey/SystemCode/Token 全缺);用例组 B:带 docs/SystemCode_secertkey.txt 密钥、缺 Token。 +- 判定:存在性=HTTP≠404/000;双层结构=HTTP 500 且 ExceptionMessage 内嵌信封 JSON;信封解包=code 为字符串且 ∈ {200,500,429,403}。 +- 通过:A 组 14/15,B 组 11/15。 +- 原始响应存档:`docs/e2e-evidence/responses/`(密钥已脱敏)。 + +| 章节 | 端点 | 用例 | HTTP | 存在 | 双层结构 | 信封code | code为字符串 | 信封msg | 备注 | +|---|---|---|---|---|---|---|---|---|---| +| 2.2.1 | 账号密码登录 | A | 500 | 存在 | 成立 | `500` | 是 | 认证未通过,请检查SystemCode与SecretKey的值是否正确 | 双层结构成立 | +| 2.2.2 | 发送验证码 | A | 500 | 存在 | 成立 | `500` | 是 | 认证未通过,请检查SystemCode与SecretKey的值是否正确 | 双层结构成立 | +| 2.2.3 | 手机验证码登录 | A | 500 | 存在 | 成立 | `500` | 是 | 认证未通过,请检查SystemCode与SecretKey的值是否正确 | 双层结构成立 | +| 2.3.1 | 修改登录密码 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.4 | 获取当前用户信息 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.5.1.1 | 获取当前用户项目列表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.6 | 获取APP端任务列表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.7 | 获取项目监测计划任务列表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.9 | 获取测组包含的监测点列表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.10 | 获取项目监测计划统计表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.11 | 获取指定监测计划的测组统计表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.12 | 监测点统计表 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.13 | 数据解析(原始文件上传) | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.14 | 获取基准点 | A | 500 | 存在 | 不成立 | — | 否 | | 响应非可解析双层结构(原始体见存档) | +| 2.15 | 获取工点数据 | A | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.2.1 | 账号密码登录 | B | 200 | 存在 | 不成立 | `500` | 是 | 用户名或密码错误! | 裸业务信封(未走 ASP.NET 异常包装) | +| 2.2.2 | 发送验证码 | B | 200 | 存在 | 不成立 | `500` | 是 | 短信验证码发送失败,失败信息:System.AggregateException | 裸业务信封(未走 ASP.NET 异常包装) | +| 2.2.3 | 手机验证码登录 | B | 200 | 存在 | 不成立 | `500` | 是 | 此号码的用户不存在,请确认手机号是否和系统中维护的一致 | 裸业务信封(未走 ASP.NET 异常包装) | +| 2.3.1 | 修改登录密码 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.4 | 获取当前用户信息 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.5.1.1 | 获取当前用户项目列表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.6 | 获取APP端任务列表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.7 | 获取项目监测计划任务列表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.9 | 获取测组包含的监测点列表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.10 | 获取项目监测计划统计表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.11 | 获取指定监测计划的测组统计表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.12 | 监测点统计表 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.13 | 数据解析(原始文件上传) | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | +| 2.14 | 获取基准点 | B | 500 | 存在 | 不成立 | — | 否 | | 响应非可解析双层结构(原始体见存档) | +| 2.15 | 获取工点数据 | B | 500 | 存在 | 成立 | `500` | 是 | 无效的token。 | 双层结构成立 | diff --git a/docs/e2e-evidence/contract_probe.py b/docs/e2e-evidence/contract_probe.py new file mode 100644 index 0000000..b0cdc3e --- /dev/null +++ b/docs/e2e-evidence/contract_probe.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""E2E 契约层负向探测脚本(S-E2E 任务 00c8cc0d)。 + +对接口管理规范 V4.5 的 15 个在用端点发起真实请求,两组负向用例: + A 组:缺鉴权三要素头(SecretKey/SystemCode/Token 全缺); + B 组:带真实 SecretKey/SystemCode(运行时读 docs/SystemCode_secertkey.txt,不落明文)、缺 Token。 + +判定口径(任务书 + core/network/ApiCaller.kt 实证契约): + 存在性 :HTTP 状态非 404 且非连接错误(000); + 双层结构 :HTTP 500 且 body 为 JSON、含字符串字段 ExceptionMessage, + 其值可再解析为含 code 字段的信封 JSON(ASP.NET 异常包装内嵌业务信封); + 信封解包模拟:内嵌信封 code 必为字符串且取值 ∈ {"200","500","429","403"}(对齐 ApiEnvelope)。 + +节流:请求间隔 ≥1.5s;不主动触发 403(连续报错锁定);429 若出现如实记录。 + +用法:python3 docs/e2e-evidence/contract_probe.py +产出:docs/e2e-evidence/responses/{A|B}_{章节}.txt 原始响应 + docs/e2e-evidence/contract_results.json 结构化结果 + docs/e2e-evidence/contract_evidence.md 证据汇总表 +""" +import json +import time +import uuid +import urllib.request +import urllib.error +import ssl +from pathlib import Path + +BASE_URL = "https://jcd.stec.p-q.co" +WORKSPACE = Path(__file__).resolve().parents[2] +EVIDENCE_DIR = WORKSPACE / "docs" / "e2e-evidence" +RESPONSE_DIR = EVIDENCE_DIR / "responses" +KEY_FILE = WORKSPACE / "docs" / "SystemCode_secertkey.txt" + +REQUEST_INTERVAL_SEC = 1.5 +TIMEOUT_SEC = 25 +VALID_ENVELOPE_CODES = {"200", "500", "429", "403"} + +# 登录/验证码类端点按接口文档 1.2.3 天然不携带 Token。 +# 探测用参数均为无效占位(10000000000 非法号段 / e2e-probe-* 假 ID / e2e 弱凭据), +# 不触达任何真实业务数据,也不触发真实短信下发。 +ENDPOINTS = [ + { + "sec": "2.2.1", "name": "账号密码登录", "method": "POST", + "path": "/OutWebApi/api/LoginByAccount", + "body_json": {"LoginName": "e2e_probe_placeholder", "Password": "E2eProbe#2026"}, + }, + { + "sec": "2.2.2", "name": "发送验证码", "method": "GET", + "path": "/OutWebApi/api/LoginSecurityCode", + "query": {"MobilePhone": "10000000000"}, + }, + { + "sec": "2.2.3", "name": "手机验证码登录", "method": "POST", + "path": "/OutWebApi/api/LoginByMobile", + "body_json": {"Mobile": "10000000000", "VerificationCode": "000000"}, + }, + { + "sec": "2.3.1", "name": "修改登录密码", "method": "POST", + "path": "/OutWebApi/api/ModifyPassword", + "body_json": {"Password": "E2eProbe#2026", "ConfirmAnswer": "e2e-probe"}, + }, + {"sec": "2.4", "name": "获取当前用户信息", "method": "GET", "path": "/OutWebApi/api/GetUserInfo"}, + {"sec": "2.5.1.1", "name": "获取当前用户项目列表", "method": "GET", "path": "/OutWebApi/api/GetProjectList"}, + { + "sec": "2.6", "name": "获取APP端任务列表", "method": "GET", + "path": "/OutWebApi/api/GetUserPlanGroup", + "query": {"PlanDate": "2026-09-04"}, + }, + {"sec": "2.7", "name": "获取项目监测计划任务列表", "method": "GET", "path": "/OutWebApi/api/GetPlanGroup"}, + { + "sec": "2.9", "name": "获取测组包含的监测点列表", "method": "GET", + "path": "/OutWebApi/api/GetPointBySurveyGroup", + "query": {"GroupID": "e2e-probe-group"}, + }, + { + "sec": "2.10", "name": "获取项目监测计划统计表", "method": "GET", + "path": "/OutWebApi/api/GetPlanStatistical", + "query": {"ProjectID": "e2e-probe-project"}, + }, + { + "sec": "2.11", "name": "获取指定监测计划的测组统计表", "method": "GET", + "path": "/OutWebApi/api/GetSurveyGroupList", + "query": {"PlanID": "e2e-probe-plan"}, + }, + { + "sec": "2.12", "name": "监测点统计表", "method": "GET", + "path": "/OutWebApi/api/GetPointStatistics", + "query": {"PlanID": "e2e-probe-plan", "GroupID": "e2e-probe-group"}, + }, + { + "sec": "2.13", "name": "数据解析(原始文件上传)", "method": "POST_MULTIPART", + "path": "/OutWebApi/api/AnalysisMonitorData", + "multipart_fields": { + "ProjectID": "e2e-probe-project", + "WorkPointID": "e2e-probe-workpoint", + "SurveyGroupID": "e2e-probe-group", + "PlanID": "e2e-probe-plan", + "MonitorDate": "2026-09-04", + "WorkInfo": "e2e-probe", + "Equipment": "e2e-probe", + "Weather": "晴", + "PointsData": json.dumps( + [{"Point": "e2e-probe-point", "PointName": "e2e-probe-point", + "CurrentValue": "0", "CurrentChangeValue": "0", "IsInitValue": "false"}], + ensure_ascii=False), + }, + "multipart_file": ("E2eProbe.txt", "e2e probe placeholder file\n"), + }, + { + "sec": "2.14", "name": "获取基准点", "method": "GET", + "path": "/OutWebApi/api/ControlPoint", + "query": {"ProjectID": "e2e-probe-project"}, + }, + {"sec": "2.15", "name": "获取工点数据", "method": "GET", "path": "/OutWebApi/api/GetWorkPoint"}, +] + +# 与 core/network/AuthInterceptor 注入头保持同名。 +HEADER_SECRET_KEY = "SecretKey" +HEADER_SYSTEM_CODE = "SystemCode" +HEADER_TOKEN = "Token" + + +def load_keys(): + """解析 docs/SystemCode_secertkey.txt(格式:`SystemCode:Mobile` / `SecretKey:`)。""" + keys = {} + for line in KEY_FILE.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if ":" not in line: + continue + name, _, value = line.partition(":") + keys[name.strip()] = value.strip() + return keys.get("SystemCode", ""), keys.get("SecretKey", "") + + +def build_request(ep, case, system_code, secret_key): + """按端点与用例组构造 urllib 请求;返回 (request, 描述行)。""" + url = BASE_URL + ep["path"] + method = ep["method"] + headers = {"User-Agent": "stec-cmd-e2e-probe/1.0", "Accept": "application/json, text/*;q=0.9"} + if case == "B": + headers[HEADER_SYSTEM_CODE] = system_code + headers[HEADER_SECRET_KEY] = secret_key + # A/B 两组均不带 Token(负向目标即缺 Token 行为)。 + + data = None + if method == "GET": + if ep.get("query"): + from urllib.parse import urlencode + url += "?" + urlencode(ep["query"]) + elif method == "POST": + body = json.dumps(ep["body_json"], ensure_ascii=False).encode("utf-8") + headers["Content-Type"] = "application/json; charset=utf-8" + data = body + elif method == "POST_MULTIPART": + boundary = "----e2eProbe" + uuid.uuid4().hex[:12] + parts = [] + for name, value in ep["multipart_fields"].items(): + parts.append( + f"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n\r\n{value}\r\n".encode("utf-8")) + filename, content = ep["multipart_file"] + parts.append( + (f"--{boundary}\r\nContent-Disposition: form-data; name=\"Files\"; " + f"filename=\"{filename}\"\r\nContent-Type: text/plain\r\n\r\n").encode("utf-8") + + content.encode("utf-8") + b"\r\n") + parts.append(f"--{boundary}--\r\n".encode("utf-8")) + headers["Content-Type"] = f"multipart/form-data; boundary={boundary}" + data = b"".join(parts) + else: + raise ValueError(f"未知方法 {method}") + + req = urllib.request.Request(url, data=data, headers=headers, method="GET" if method == "GET" else "POST") + auth = {k: headers.get(k) for k in (HEADER_SECRET_KEY, HEADER_SYSTEM_CODE, HEADER_TOKEN)} + return req, auth + + +def do_request(req): + """发请求并归一返回 (http_status, body_text, err)。连接失败时 status 记 '000'。""" + ctx = ssl.create_default_context() + try: + with urllib.request.urlopen(req, timeout=TIMEOUT_SEC, context=ctx) as resp: + return str(resp.status), resp.read().decode("utf-8", errors="replace"), None + except urllib.error.HTTPError as e: + return str(e.code), e.read().decode("utf-8", errors="replace"), None + except Exception as e: # 连接失败/超时/TLS + return "000", "", f"{type(e).__name__}: {e}" + + +def analyze(ep, case, status, body): + """按判定口径分析一个响应,返回结果 dict。""" + result = { + "sec": ep["sec"], "name": ep["name"], "case": case, + "http_status": status, + "exists": None, "double_layer": None, + "envelope_code": None, "envelope_code_is_str": None, + "envelope_msg": None, "envelope_data": None, + "exception_type": None, "notes": "", + } + if status == "000": + result["exists"] = False + result["notes"] = "连接失败" + return result + result["exists"] = status != "404" + if status == "404": + result["notes"] = "端点不存在" + return result + + wrapper = None + if body.strip().startswith("{"): + try: + wrapper = json.loads(body) + except json.JSONDecodeError: + wrapper = None + + embedded = None + if isinstance(wrapper, dict): + result["exception_type"] = wrapper.get("ExceptionType") + raw = wrapper.get("ExceptionMessage") + if isinstance(raw, str): + try: + embedded = json.loads(raw) + except json.JSONDecodeError: + embedded = None + elif isinstance(raw, dict): + embedded = raw # 个别端点可能直接内嵌对象 + + if embedded and isinstance(embedded, dict) and "code" in embedded: + result["double_layer"] = (status == "500") + code = embedded.get("code") + result["envelope_code"] = code + result["envelope_code_is_str"] = isinstance(code, str) + result["envelope_msg"] = embedded.get("msg") + result["envelope_data"] = embedded.get("data") + result["notes"] = ( + "双层结构成立" if result["double_layer"] and result["envelope_code_is_str"] + else "内嵌信封可解但形态偏离(见 http_status/code 类型)") + else: + # 可能是裸业务信封(HTTP 200 + {code,...})或非 JSON(HTML 错误页等) + if isinstance(wrapper, dict) and "code" in wrapper: + result["double_layer"] = False + code = wrapper.get("code") + result["envelope_code"] = code + result["envelope_code_is_str"] = isinstance(code, str) + result["envelope_msg"] = wrapper.get("msg") + result["envelope_data"] = wrapper.get("data") + result["notes"] = "裸业务信封(未走 ASP.NET 异常包装)" + else: + result["double_layer"] = False + result["notes"] = "响应非可解析双层结构(原始体见存档)" + return result + + +def save_raw(ep, case, status, auth, body, err): + stem = f"{case}_{ep['sec'].replace('.', '_')}" + keys_desc = "无三要素头" if not auth.get(HEADER_SECRET_KEY) else ( + f"{HEADER_SYSTEM_CODE}={auth.get(HEADER_SYSTEM_CODE)}, " + f"{HEADER_SECRET_KEY}={str(auth.get(HEADER_SECRET_KEY))[:6]}***(脱敏), 无 Token") + lines = [ + f"# 端点 {ep['sec']} {ep['name']} 用例组 {case}" + ("(缺三要素头)" if case == "A" else "(带密钥缺 Token)"), + f"# 请求:{ep['method']} {BASE_URL}{ep['path']}", + f"# 请求头:{keys_desc}", + f"# HTTP 状态:{status}" + (f" 错误:{err}" if err else ""), + f"# 存档时间:{time.strftime('%Y-%m-%dT%H:%M:%S%z')}", + "=" * 60, + body if body else "(空响应体)", + ] + (RESPONSE_DIR / f"{stem}.txt").write_text("\n".join(lines), encoding="utf-8") + return RESPONSE_DIR / f"{stem}.txt" + + +def write_markdown(results): + rows = [] + verdict_a = verdict_b = 0 + for r in results: + ok = ( + r["exists"] is True + and r["http_status"] == "500" + and r["double_layer"] is True + and r["envelope_code_is_str"] is True + and r["envelope_code"] in VALID_ENVELOPE_CODES + ) + if r["case"] == "A" and ok: + verdict_a += 1 + if r["case"] == "B" and ok: + verdict_b += 1 + code = r["envelope_code"] + rows.append( + "| {sec} | {name} | {case} | {st} | {ex} | {dl} | {code} | {isstr} | {msg} | {note} |".format( + sec=r["sec"], name=r["name"], case=r["case"], st=r["http_status"], + ex="存在" if r["exists"] else "否", + dl="成立" if r["double_layer"] else "不成立", + code="`%s`" % code if code is not None else "—", + isstr="是" if r["envelope_code_is_str"] else "否", + msg=(r["envelope_msg"] or "")[:40] if isinstance(r["envelope_msg"], str) else ("" if r["envelope_msg"] is None else str(r["envelope_msg"])[:40]), + note=r["notes"], + )) + md = "\n".join([ + "# 契约层负向探测证据表(自动生成于 %s)" % time.strftime("%Y-%m-%d %H:%M:%S"), + "", + "- 服务器:%s(接口管理规范 V4.5 测试环境)" % BASE_URL, + "- 用例组 A:缺三要素头(SecretKey/SystemCode/Token 全缺);用例组 B:带 docs/SystemCode_secertkey.txt 密钥、缺 Token。", + "- 判定:存在性=HTTP≠404/000;双层结构=HTTP 500 且 ExceptionMessage 内嵌信封 JSON;信封解包=code 为字符串且 ∈ {200,500,429,403}。", + "- 通过:A 组 %d/15,B 组 %d/15。" % (verdict_a, verdict_b), + "- 原始响应存档:`docs/e2e-evidence/responses/`(密钥已脱敏)。", + "", + "| 章节 | 端点 | 用例 | HTTP | 存在 | 双层结构 | 信封code | code为字符串 | 信封msg | 备注 |", + "|---|---|---|---|---|---|---|---|---|---|", + ] + rows + [""]) + (EVIDENCE_DIR / "contract_evidence.md").write_text(md, encoding="utf-8") + return verdict_a, verdict_b + + +def main(): + RESPONSE_DIR.mkdir(parents=True, exist_ok=True) + system_code, secret_key = load_keys() + if not secret_key: + raise SystemExit("密钥文件解析失败:%s" % KEY_FILE) + print("密钥加载完成(SystemCode=%s, SecretKey=%s…脱敏)" % (system_code, secret_key[:6])) + + results = [] + for case in ("A", "B"): + for ep in ENDPOINTS: + req, auth = build_request(ep, case, system_code, secret_key) + status, body, err = do_request(req) + raw_path = save_raw(ep, case, status, auth, body, err) + r = analyze(ep, case, status, body) + if err: + r["notes"] += f"({err})" + r["raw_file"] = str(raw_path.relative_to(WORKSPACE)) + results.append(r) + print("[{case}] {sec:<8} {st:<3} 双层={dl} code={code} {name}".format( + case=case, sec=ep["sec"], st=status, + dl=r["double_layer"], code=r["envelope_code"], name=ep["name"])) + time.sleep(REQUEST_INTERVAL_SEC) + + (EVIDENCE_DIR / "contract_results.json").write_text( + json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8") + verdict_a, verdict_b = write_markdown(results) + print("完成:A 组通过 %d/15,B 组通过 %d/15;证据见 docs/e2e-evidence/" % (verdict_a, verdict_b)) + + +if __name__ == "__main__": + main() diff --git a/docs/e2e-evidence/contract_results.json b/docs/e2e-evidence/contract_results.json new file mode 100644 index 0000000..cfe67c4 --- /dev/null +++ b/docs/e2e-evidence/contract_results.json @@ -0,0 +1,454 @@ +[ + { + "sec": "2.2.1", + "name": "账号密码登录", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "认证未通过,请检查SystemCode与SecretKey的值是否正确", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_2_1.txt" + }, + { + "sec": "2.2.2", + "name": "发送验证码", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "认证未通过,请检查SystemCode与SecretKey的值是否正确", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_2_2.txt" + }, + { + "sec": "2.2.3", + "name": "手机验证码登录", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "认证未通过,请检查SystemCode与SecretKey的值是否正确", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_2_3.txt" + }, + { + "sec": "2.3.1", + "name": "修改登录密码", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_3_1.txt" + }, + { + "sec": "2.4", + "name": "获取当前用户信息", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_4.txt" + }, + { + "sec": "2.5.1.1", + "name": "获取当前用户项目列表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_5_1_1.txt" + }, + { + "sec": "2.6", + "name": "获取APP端任务列表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_6.txt" + }, + { + "sec": "2.7", + "name": "获取项目监测计划任务列表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_7.txt" + }, + { + "sec": "2.9", + "name": "获取测组包含的监测点列表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_9.txt" + }, + { + "sec": "2.10", + "name": "获取项目监测计划统计表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_10.txt" + }, + { + "sec": "2.11", + "name": "获取指定监测计划的测组统计表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_11.txt" + }, + { + "sec": "2.12", + "name": "监测点统计表", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_12.txt" + }, + { + "sec": "2.13", + "name": "数据解析(原始文件上传)", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_13.txt" + }, + { + "sec": "2.14", + "name": "获取基准点", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": false, + "envelope_code": null, + "envelope_code_is_str": null, + "envelope_msg": null, + "envelope_data": null, + "exception_type": "System.Exception", + "notes": "响应非可解析双层结构(原始体见存档)", + "raw_file": "docs/e2e-evidence/responses/A_2_14.txt" + }, + { + "sec": "2.15", + "name": "获取工点数据", + "case": "A", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/A_2_15.txt" + }, + { + "sec": "2.2.1", + "name": "账号密码登录", + "case": "B", + "http_status": "200", + "exists": true, + "double_layer": false, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "用户名或密码错误!", + "envelope_data": null, + "exception_type": null, + "notes": "裸业务信封(未走 ASP.NET 异常包装)", + "raw_file": "docs/e2e-evidence/responses/B_2_2_1.txt" + }, + { + "sec": "2.2.2", + "name": "发送验证码", + "case": "B", + "http_status": "200", + "exists": true, + "double_layer": false, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "短信验证码发送失败,失败信息:System.AggregateException: 发生一个或多个错误。 ---> TencentCloud.Common.TencentCloudSDKException: The request with exception: 发送请求时出错。\r\n 在 TencentCloud.Common.AbstractClient.d__43.MoveNext() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Common\\AbstractClient.cs:行号 318\r\n--- 引发异常的上一位置中堆栈跟踪的末尾 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n 在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n 在 TencentCloud.Common.AbstractClient.d__35.MoveNext() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Common\\AbstractClient.cs:行号 97\r\n --- 内部异常堆栈跟踪的结尾 ---\r\n 在 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)\r\n 在 System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)\r\n 在 System.Threading.Tasks.Task.Wait()\r\n 在 TencentCloud.Common.AbstractClient.InternalRequestSync(AbstractModel request, String actionName) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Common\\AbstractClient.cs:行号 137\r\n 在 TencentCloud.Sms.V20210111.SmsClient.SendSmsSync(SendSmsRequest req) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Sms\\V20210111\\SmsClient.cs:行号 729\r\n 在 NK.Message.MobilMsg.SendMsg(String signName, String templateId, String[] mobiles, String[] templateParam, String Endpoint, String ClientType) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\MobilMsg.cs:行号 102\r\n---> (内部异常 #0) message:The request with exception: 发送请求时出错。 requestId<---\r\n", + "envelope_data": { + "result": false + }, + "exception_type": null, + "notes": "裸业务信封(未走 ASP.NET 异常包装)", + "raw_file": "docs/e2e-evidence/responses/B_2_2_2.txt" + }, + { + "sec": "2.2.3", + "name": "手机验证码登录", + "case": "B", + "http_status": "200", + "exists": true, + "double_layer": false, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "此号码的用户不存在,请确认手机号是否和系统中维护的一致", + "envelope_data": null, + "exception_type": null, + "notes": "裸业务信封(未走 ASP.NET 异常包装)", + "raw_file": "docs/e2e-evidence/responses/B_2_2_3.txt" + }, + { + "sec": "2.3.1", + "name": "修改登录密码", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_3_1.txt" + }, + { + "sec": "2.4", + "name": "获取当前用户信息", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_4.txt" + }, + { + "sec": "2.5.1.1", + "name": "获取当前用户项目列表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_5_1_1.txt" + }, + { + "sec": "2.6", + "name": "获取APP端任务列表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_6.txt" + }, + { + "sec": "2.7", + "name": "获取项目监测计划任务列表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_7.txt" + }, + { + "sec": "2.9", + "name": "获取测组包含的监测点列表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_9.txt" + }, + { + "sec": "2.10", + "name": "获取项目监测计划统计表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_10.txt" + }, + { + "sec": "2.11", + "name": "获取指定监测计划的测组统计表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_11.txt" + }, + { + "sec": "2.12", + "name": "监测点统计表", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_12.txt" + }, + { + "sec": "2.13", + "name": "数据解析(原始文件上传)", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_13.txt" + }, + { + "sec": "2.14", + "name": "获取基准点", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": false, + "envelope_code": null, + "envelope_code_is_str": null, + "envelope_msg": null, + "envelope_data": null, + "exception_type": "System.Exception", + "notes": "响应非可解析双层结构(原始体见存档)", + "raw_file": "docs/e2e-evidence/responses/B_2_14.txt" + }, + { + "sec": "2.15", + "name": "获取工点数据", + "case": "B", + "http_status": "500", + "exists": true, + "double_layer": true, + "envelope_code": "500", + "envelope_code_is_str": true, + "envelope_msg": "无效的token。", + "envelope_data": null, + "exception_type": "NK.Public.Exceptions.BusinessException", + "notes": "双层结构成立", + "raw_file": "docs/e2e-evidence/responses/B_2_15.txt" + } +] \ No newline at end of file diff --git a/docs/e2e-evidence/protocol-test-results/TEST-com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest.xml b/docs/e2e-evidence/protocol-test-results/TEST-com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest.xml new file mode 100644 index 0000000..8d28984 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/TEST-com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/e2e-evidence/protocol-test-results/TEST-com.stec.cmd.protocol.vm208.Vm208FrameParserTest.xml b/docs/e2e-evidence/protocol-test-results/TEST-com.stec.cmd.protocol.vm208.Vm208FrameParserTest.xml new file mode 100644 index 0000000..d5fefc2 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/TEST-com.stec.cmd.protocol.vm208.Vm208FrameParserTest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/docs/e2e-evidence/protocol-test-results/html/classes/com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest.html b/docs/e2e-evidence/protocol-test-results/html/classes/com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest.html new file mode 100644 index 0000000..1b41b39 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/classes/com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest.html @@ -0,0 +1,116 @@ + + + + + +Test results - Class com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest + + + + + +
+

Class com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.031s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestDurationResult
帧头错误的完整帧被丢弃0.001spassed
整帧一次喂入解析出协议示例读数0.001spassed
粘包两帧一次解析0spassed
脏帧被丢弃后继续重同步0.022spassed
逐字节分片喂入仍可切帧(残包容错)0.007spassed
+
+
+ +
+ + diff --git a/docs/e2e-evidence/protocol-test-results/html/classes/com.stec.cmd.protocol.vm208.Vm208FrameParserTest.html b/docs/e2e-evidence/protocol-test-results/html/classes/com.stec.cmd.protocol.vm208.Vm208FrameParserTest.html new file mode 100644 index 0000000..394c4e7 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/classes/com.stec.cmd.protocol.vm208.Vm208FrameParserTest.html @@ -0,0 +1,126 @@ + + + + + +Test results - Class com.stec.cmd.protocol.vm208.Vm208FrameParserTest + + + + + +
+

Class com.stec.cmd.protocol.vm208.Vm208FrameParserTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.020s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestDurationResult
分片喂入仍可切帧0spassed
整帧解析出协议示例读数0.011spassed
未知状态字母映射 UNKNOWN 而非崩溃0spassed
激励值越界抛出0.005spassed
激励命令编码与应答回解析0.003spassed
状态字母映射 Q 与 N0spassed
缺字段脏帧被丢弃0.001spassed
+
+
+ +
+ + diff --git a/docs/e2e-evidence/protocol-test-results/html/css/base-style.css b/docs/e2e-evidence/protocol-test-results/html/css/base-style.css new file mode 100644 index 0000000..4afa73e --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/docs/e2e-evidence/protocol-test-results/html/css/style.css b/docs/e2e-evidence/protocol-test-results/html/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/docs/e2e-evidence/protocol-test-results/html/index.html b/docs/e2e-evidence/protocol-test-results/html/index.html new file mode 100644 index 0000000..64c6425 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/index.html @@ -0,0 +1,153 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
12
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.051s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+com.stec.cmd.protocol.ie1000 +5000.031s100%
+com.stec.cmd.protocol.vm208 +7000.020s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+com.stec.cmd.protocol.ie1000.Ie1000FrameParserTest +5000.031s100%
+com.stec.cmd.protocol.vm208.Vm208FrameParserTest +7000.020s100%
+
+
+ +
+ + diff --git a/docs/e2e-evidence/protocol-test-results/html/js/report.js b/docs/e2e-evidence/protocol-test-results/html/js/report.js new file mode 100644 index 0000000..83bab4a --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/docs/e2e-evidence/protocol-test-results/html/packages/com.stec.cmd.protocol.ie1000.html b/docs/e2e-evidence/protocol-test-results/html/packages/com.stec.cmd.protocol.ie1000.html new file mode 100644 index 0000000..2de2791 --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/packages/com.stec.cmd.protocol.ie1000.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package com.stec.cmd.protocol.ie1000 + + + + + +
+

Package com.stec.cmd.protocol.ie1000

+ +
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.031s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+Ie1000FrameParserTest +5000.031s100%
+
+
+ +
+ + diff --git a/docs/e2e-evidence/protocol-test-results/html/packages/com.stec.cmd.protocol.vm208.html b/docs/e2e-evidence/protocol-test-results/html/packages/com.stec.cmd.protocol.vm208.html new file mode 100644 index 0000000..7cca75b --- /dev/null +++ b/docs/e2e-evidence/protocol-test-results/html/packages/com.stec.cmd.protocol.vm208.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package com.stec.cmd.protocol.vm208 + + + + + +
+

Package com.stec.cmd.protocol.vm208

+ +
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.020s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+Vm208FrameParserTest +7000.020s100%
+
+
+ +
+ + diff --git a/docs/e2e-evidence/responses/A_2_10.txt b/docs/e2e-evidence/responses/A_2_10.txt new file mode 100644 index 0000000..9ab4f5f --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_10.txt @@ -0,0 +1,7 @@ +# 端点 2.10 获取项目监测计划统计表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPlanStatistical +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:19+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPlanStatisticalController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_11.txt b/docs/e2e-evidence/responses/A_2_11.txt new file mode 100644 index 0000000..009f992 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_11.txt @@ -0,0 +1,7 @@ +# 端点 2.11 获取指定监测计划的测组统计表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetSurveyGroupList +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:21+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetSurveyGroupListController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_12.txt b/docs/e2e-evidence/responses/A_2_12.txt new file mode 100644 index 0000000..bc0ec10 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_12.txt @@ -0,0 +1,7 @@ +# 端点 2.12 监测点统计表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPointStatistics +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:22+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPointStatisticsController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_13.txt b/docs/e2e-evidence/responses/A_2_13.txt new file mode 100644 index 0000000..812fe81 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_13.txt @@ -0,0 +1,7 @@ +# 端点 2.13 数据解析(原始文件上传) 用例组 A(缺三要素头) +# 请求:POST_MULTIPART https://jcd.stec.p-q.co/OutWebApi/api/AnalysisMonitorData +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:24+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.AnalysisMonitorDataController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\AnalysisMonitorDataController.cs:行号 31\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_14.txt b/docs/e2e-evidence/responses/A_2_14.txt new file mode 100644 index 0000000..a6d106d --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_14.txt @@ -0,0 +1,7 @@ +# 端点 2.14 获取基准点 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/ControlPoint +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:26+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"当前用户不存在。","ExceptionType":"System.Exception","StackTrace":" 在 NK.Service.UserService.GetCurrentUserInfo() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Service\\UserService.cs:行号 95\r\n 在 NK.Service.UserService.<>c.b__2_0() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Service\\UserService.cs:行号 71\r\n 在 NK.Public.HttpRuntimeCacheHelper.Get[T](String key, Func`1 fun, Int32 second) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Public\\HttpRuntimeCacheHelper.cs:行号 94\r\n 在 NK.Public.CacheHelper.Get[T](String key, Func`1 fun, Int32 second) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Public\\CacheHelper.cs:行号 65\r\n 在 NK.Service.UserService.get_CurrentUser() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Service\\UserService.cs:行号 69\r\n 在 OutWebApi.Controllers.ControlPointController.Get(String ProjectID) 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\ControlPointController.cs:行号 23\r\n 在 lambda_method(Closure , Object , Object[] )\r\n 在 System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.b__c(Object instance, Object[] methodParameters)\r\n 在 System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_15.txt b/docs/e2e-evidence/responses/A_2_15.txt new file mode 100644 index 0000000..d92ce7d --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_15.txt @@ -0,0 +1,7 @@ +# 端点 2.15 获取工点数据 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetWorkPoint +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:28+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetWorkPointController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_2_1.txt b/docs/e2e-evidence/responses/A_2_2_1.txt new file mode 100644 index 0000000..a7fc03a --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_2_1.txt @@ -0,0 +1,7 @@ +# 端点 2.2.1 账号密码登录 用例组 A(缺三要素头) +# 请求:POST https://jcd.stec.p-q.co/OutWebApi/api/LoginByAccount +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:04+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"认证未通过,请检查SystemCode与SecretKey的值是否正确\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.LoginByAccountController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_2_2.txt b/docs/e2e-evidence/responses/A_2_2_2.txt new file mode 100644 index 0000000..2badbd4 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_2_2.txt @@ -0,0 +1,7 @@ +# 端点 2.2.2 发送验证码 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/LoginSecurityCode +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:06+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"认证未通过,请检查SystemCode与SecretKey的值是否正确\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.LoginSecurityCodeController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\Login\\LoginSecurityCodeController.cs:行号 18\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_2_3.txt b/docs/e2e-evidence/responses/A_2_2_3.txt new file mode 100644 index 0000000..7b2f1ef --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_2_3.txt @@ -0,0 +1,7 @@ +# 端点 2.2.3 手机验证码登录 用例组 A(缺三要素头) +# 请求:POST https://jcd.stec.p-q.co/OutWebApi/api/LoginByMobile +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:08+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"认证未通过,请检查SystemCode与SecretKey的值是否正确\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.LoginByMobileController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\Login\\LoginByMobileController.cs:行号 22\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_3_1.txt b/docs/e2e-evidence/responses/A_2_3_1.txt new file mode 100644 index 0000000..c4f517e --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_3_1.txt @@ -0,0 +1,7 @@ +# 端点 2.3.1 修改登录密码 用例组 A(缺三要素头) +# 请求:POST https://jcd.stec.p-q.co/OutWebApi/api/ModifyPassword +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:09+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.ModifyPasswordController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_4.txt b/docs/e2e-evidence/responses/A_2_4.txt new file mode 100644 index 0000000..4fd2866 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_4.txt @@ -0,0 +1,7 @@ +# 端点 2.4 获取当前用户信息 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetUserInfo +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:11+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetUserInfoController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_5_1_1.txt b/docs/e2e-evidence/responses/A_2_5_1_1.txt new file mode 100644 index 0000000..8460e92 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_5_1_1.txt @@ -0,0 +1,7 @@ +# 端点 2.5.1.1 获取当前用户项目列表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetProjectList +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:13+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetProjectListController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_6.txt b/docs/e2e-evidence/responses/A_2_6.txt new file mode 100644 index 0000000..dc539ef --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_6.txt @@ -0,0 +1,7 @@ +# 端点 2.6 获取APP端任务列表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetUserPlanGroup +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:14+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetUserPlanGroupController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_7.txt b/docs/e2e-evidence/responses/A_2_7.txt new file mode 100644 index 0000000..0a4eebc --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_7.txt @@ -0,0 +1,7 @@ +# 端点 2.7 获取项目监测计划任务列表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPlanGroup +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:16+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPlanGroupController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/A_2_9.txt b/docs/e2e-evidence/responses/A_2_9.txt new file mode 100644 index 0000000..1204307 --- /dev/null +++ b/docs/e2e-evidence/responses/A_2_9.txt @@ -0,0 +1,7 @@ +# 端点 2.9 获取测组包含的监测点列表 用例组 A(缺三要素头) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPointBySurveyGroup +# 请求头:无三要素头 +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:18+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPointBySurveyGroupController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\Points\\GetPointBySurveyGroupController.cs:行号 16\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_10.txt b/docs/e2e-evidence/responses/B_2_10.txt new file mode 100644 index 0000000..a6d966a --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_10.txt @@ -0,0 +1,7 @@ +# 端点 2.10 获取项目监测计划统计表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPlanStatistical +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:45+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPlanStatisticalController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_11.txt b/docs/e2e-evidence/responses/B_2_11.txt new file mode 100644 index 0000000..2d6d0a8 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_11.txt @@ -0,0 +1,7 @@ +# 端点 2.11 获取指定监测计划的测组统计表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetSurveyGroupList +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:46+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetSurveyGroupListController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_12.txt b/docs/e2e-evidence/responses/B_2_12.txt new file mode 100644 index 0000000..394190d --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_12.txt @@ -0,0 +1,7 @@ +# 端点 2.12 监测点统计表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPointStatistics +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:48+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPointStatisticsController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_13.txt b/docs/e2e-evidence/responses/B_2_13.txt new file mode 100644 index 0000000..82bed64 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_13.txt @@ -0,0 +1,7 @@ +# 端点 2.13 数据解析(原始文件上传) 用例组 B(带密钥缺 Token) +# 请求:POST_MULTIPART https://jcd.stec.p-q.co/OutWebApi/api/AnalysisMonitorData +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:50+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.AnalysisMonitorDataController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\AnalysisMonitorDataController.cs:行号 31\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_14.txt b/docs/e2e-evidence/responses/B_2_14.txt new file mode 100644 index 0000000..6016953 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_14.txt @@ -0,0 +1,7 @@ +# 端点 2.14 获取基准点 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/ControlPoint +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:51+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"当前用户不存在。","ExceptionType":"System.Exception","StackTrace":" 在 NK.Service.UserService.GetCurrentUserInfo() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Service\\UserService.cs:行号 95\r\n 在 NK.Service.UserService.<>c.b__2_0() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Service\\UserService.cs:行号 71\r\n 在 NK.Public.HttpRuntimeCacheHelper.Get[T](String key, Func`1 fun, Int32 second) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Public\\HttpRuntimeCacheHelper.cs:行号 94\r\n 在 NK.Public.CacheHelper.Get[T](String key, Func`1 fun, Int32 second) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Public\\CacheHelper.cs:行号 65\r\n 在 NK.Service.UserService.get_CurrentUser() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Service\\UserService.cs:行号 69\r\n 在 OutWebApi.Controllers.ControlPointController.Get(String ProjectID) 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\ControlPointController.cs:行号 23\r\n 在 lambda_method(Closure , Object , Object[] )\r\n 在 System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.b__c(Object instance, Object[] methodParameters)\r\n 在 System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_15.txt b/docs/e2e-evidence/responses/B_2_15.txt new file mode 100644 index 0000000..cb2b464 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_15.txt @@ -0,0 +1,7 @@ +# 端点 2.15 获取工点数据 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetWorkPoint +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:53+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetWorkPointController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_2_1.txt b/docs/e2e-evidence/responses/B_2_2_1.txt new file mode 100644 index 0000000..3ab53e5 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_2_1.txt @@ -0,0 +1,7 @@ +# 端点 2.2.1 账号密码登录 用例组 B(带密钥缺 Token) +# 请求:POST https://jcd.stec.p-q.co/OutWebApi/api/LoginByAccount +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:200 +# 存档时间:2026-09-04T03:11:30+0800 +============================================================ +{"code":"500","msg":"用户名或密码错误!","data":null} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_2_2.txt b/docs/e2e-evidence/responses/B_2_2_2.txt new file mode 100644 index 0000000..1c20905 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_2_2.txt @@ -0,0 +1,7 @@ +# 端点 2.2.2 发送验证码 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/LoginSecurityCode +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:200 +# 存档时间:2026-09-04T03:11:31+0800 +============================================================ +{"code":"500","msg":"短信验证码发送失败,失败信息:System.AggregateException: 发生一个或多个错误。 ---> TencentCloud.Common.TencentCloudSDKException: The request with exception: 发送请求时出错。\r\n 在 TencentCloud.Common.AbstractClient.d__43.MoveNext() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Common\\AbstractClient.cs:行号 318\r\n--- 引发异常的上一位置中堆栈跟踪的末尾 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n 在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n 在 TencentCloud.Common.AbstractClient.d__35.MoveNext() 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Common\\AbstractClient.cs:行号 97\r\n --- 内部异常堆栈跟踪的结尾 ---\r\n 在 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)\r\n 在 System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)\r\n 在 System.Threading.Tasks.Task.Wait()\r\n 在 TencentCloud.Common.AbstractClient.InternalRequestSync(AbstractModel request, String actionName) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Common\\AbstractClient.cs:行号 137\r\n 在 TencentCloud.Sms.V20210111.SmsClient.SendSmsSync(SendSmsRequest req) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\TencentCloud\\Sms\\V20210111\\SmsClient.cs:行号 729\r\n 在 NK.Message.MobilMsg.SendMsg(String signName, String templateId, String[] mobiles, String[] templateParam, String Endpoint, String ClientType) 位置 E:\\An\\Project\\SHCJ\\BaseComponent\\NK.Message\\MobilMsg.cs:行号 102\r\n---> (内部异常 #0) message:The request with exception: 发送请求时出错。 requestId<---\r\n","data":{"result":false}} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_2_3.txt b/docs/e2e-evidence/responses/B_2_2_3.txt new file mode 100644 index 0000000..36a81ad --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_2_3.txt @@ -0,0 +1,7 @@ +# 端点 2.2.3 手机验证码登录 用例组 B(带密钥缺 Token) +# 请求:POST https://jcd.stec.p-q.co/OutWebApi/api/LoginByMobile +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:200 +# 存档时间:2026-09-04T03:11:33+0800 +============================================================ +{"code":"500","msg":"此号码的用户不存在,请确认手机号是否和系统中维护的一致","data":null} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_3_1.txt b/docs/e2e-evidence/responses/B_2_3_1.txt new file mode 100644 index 0000000..e57e29b --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_3_1.txt @@ -0,0 +1,7 @@ +# 端点 2.3.1 修改登录密码 用例组 B(带密钥缺 Token) +# 请求:POST https://jcd.stec.p-q.co/OutWebApi/api/ModifyPassword +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:35+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.ModifyPasswordController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_4.txt b/docs/e2e-evidence/responses/B_2_4.txt new file mode 100644 index 0000000..101f14a --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_4.txt @@ -0,0 +1,7 @@ +# 端点 2.4 获取当前用户信息 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetUserInfo +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:36+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetUserInfoController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_5_1_1.txt b/docs/e2e-evidence/responses/B_2_5_1_1.txt new file mode 100644 index 0000000..cdf08cf --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_5_1_1.txt @@ -0,0 +1,7 @@ +# 端点 2.5.1.1 获取当前用户项目列表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetProjectList +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:38+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetProjectListController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_6.txt b/docs/e2e-evidence/responses/B_2_6.txt new file mode 100644 index 0000000..6fff738 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_6.txt @@ -0,0 +1,7 @@ +# 端点 2.6 获取APP端任务列表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetUserPlanGroup +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:40+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetUserPlanGroupController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_7.txt b/docs/e2e-evidence/responses/B_2_7.txt new file mode 100644 index 0000000..e2ecfa6 --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_7.txt @@ -0,0 +1,7 @@ +# 端点 2.7 获取项目监测计划任务列表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPlanGroup +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:42+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPlanGroupController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/B_2_9.txt b/docs/e2e-evidence/responses/B_2_9.txt new file mode 100644 index 0000000..1e6cb4c --- /dev/null +++ b/docs/e2e-evidence/responses/B_2_9.txt @@ -0,0 +1,7 @@ +# 端点 2.9 获取测组包含的监测点列表 用例组 B(带密钥缺 Token) +# 请求:GET https://jcd.stec.p-q.co/OutWebApi/api/GetPointBySurveyGroup +# 请求头:SystemCode=Mobile, SecretKey=6C2782***(脱敏), 无 Token +# HTTP 状态:500 +# 存档时间:2026-09-04T03:11:43+0800 +============================================================ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetPointBySurveyGroupController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\Points\\GetPointBySurveyGroupController.cs:行号 16\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file diff --git a/docs/e2e-evidence/responses/manual_curl_GetUserInfo_nohdr.txt b/docs/e2e-evidence/responses/manual_curl_GetUserInfo_nohdr.txt new file mode 100644 index 0000000..51e6a50 --- /dev/null +++ b/docs/e2e-evidence/responses/manual_curl_GetUserInfo_nohdr.txt @@ -0,0 +1 @@ +{"Message":"发生错误。","ExceptionMessage":"{\"code\":\"500\",\"msg\":\"无效的token。\",\"data\":null}","ExceptionType":"NK.Public.Exceptions.BusinessException","StackTrace":" 在 OutWebApi.BaseApiController..ctor() 位置 E:\\WorkData\\城建\\code\\WebApps\\OutWebApi\\Controllers\\BaseApiController.cs:行号 58\r\n 在 OutWebApi.Controllers.GetUserInfoController..ctor()\r\n 在 lambda_method(Closure )\r\n 在 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"} \ No newline at end of file