# 智能电表 — 联调验签与充值记录对接说明 ## 一、数据库脚本执行顺序 在业务库(如 `funingyunwei`)依次执行: | 顺序 | 脚本 | 说明 | |------|------|------| | 1 | `yw_electrical_actions.sql` | 异步操作记录表 | | 2 | `business.yw_electrical.permissions.sql` | 含 `business:ywelectrical:device` | | 3 | `business.yw_electrical_charge.permissions.sql` | 充值记录查询/导出 | | 4 | `business.yw_electrical_param.permissions.sql` | 电表参数(若未执行过) | | 5 | `business.yw_electrical.menu.sql` | 智能电表、充值记录、参数设置菜单 + admin 授权 | | 6 | `business.yw_electrical_warning.*` | 报警记录(若未执行过) | 执行后请 **重新登录** 或刷新权限缓存,侧栏才会出现新菜单。 --- ## 二、异步回调 `electricalNotify` 联调验签 ### 2.1 回调地址 - **路径**:`POST {服务根地址}/electronic/electricalNotify` - **Content-Type**:`application/x-www-form-urlencoded`(表单字段) - **字段**:`response_content`、`timestamp`、`sign` - **配置**:`ElectronicConstant.notify_url` 必须与三方平台后台配置的 **完整公网 URL** 一致(含路径)。 示例: ```text https://your-domain.com/electronic/electricalNotify ``` ### 2.2 验签算法(与出站 `ElectronicToolUtil.getSign` 一致) 1. 取参与签名字段,组成 `Map`(**推荐**): - `auth_code` = `ElectronicConstant.auth_code` - `response_content` = 原始 JSON 字符串(与 POST 参数完全一致,不要二次格式化) - `timestamp` = 平台 POST 的 timestamp 字符串 2. 将 Map 的 **key 按字典序排序**,依次拼接各 key 对应的 **value**(只拼 value,不含 key 名)。 3. 在拼接结果末尾追加 `ElectronicConstant.nonce`。 4. 对最终字符串做 **UTF-8 MD5**,得到 32 位小写十六进制(比较时忽略大小写)。 若上述验签失败,代码会 **再尝试** 仅使用 `response_content + timestamp` 两个字段(不含 `auth_code`),以兼容部分平台版本。 ### 2.3 本地自测 sign(Java) ```java Map data = new HashMap<>(); data.put("auth_code", ElectronicConstant.auth_code); data.put("response_content", responseContent); // 与 POST 一致 data.put("timestamp", timestamp); String sign = ElectronicToolUtil.getSign(data); ``` ### 2.4 响应约定 | 场景 | HTTP 状态 | 响应体 | 平台行为 | |------|-----------|--------|----------| | 验签通过且处理完成 | 200 | `SUCCESS` | 停止重试 | | 验签失败 | 400 | `FAIL` | 按策略重试 | | 非 200 或 `FAIL` | - | - | 间隔重试直至成功或达上限 | ### 2.5 `response_content` 单条结构 ```json { "opr_id": "与提交异步任务时一致", "status": "SUCCESS", "cid": "采集器号", "address": "表地址", "error_msg": "失败时的错误码或说明" } ``` **status 与本地 `yw_electrical_actions.status` 映射:** | 平台 status | actions.status | 说明 | |-------------|----------------|------| | ACCEPTED / QUEUE / PROCESSING | 0 | 处理中 | | SUCCESS / DELIVERED | 1 | 成功 | | FAIL / TIMEOUT / NOTSUPPORT / CANCELED / RESPONSE_TIMEOUT / RESPONSE_FAIL / NOTFOUND | 2 | 失败 | ### 2.6 联调检查清单 - [ ] `dmvisit_admin` 已启动,Shiro 已放行 `/electronic/electricalNotify` - [ ] 公网/NAT 能访问回调 URL(内网需端口映射或穿透) - [ ] `auth_code`、`nonce` 与三方后台一致 - [ ] 提交远程操作后 `yw_electrical_actions` 有 `opr_id` 且 `status=0` - [ ] 模拟 POST 回调后 `status` 更新,拉闸/合闸/开户/充值副作用正确 - [ ] `yw_electrical_log` 有 `type=1` 的推送记录 ### 2.7 常见问题 **验签一直失败** - 核对 `response_content` 是否与平台发送字节级一致(URL 编码、空格、转义)。 - 确认 timestamp 为字符串而非带小数。 - 用平台文档确认是否包含 `auth_code` 参与签名。 **回调成功但 actions 不更新** - 查 `opr_id` 是否与 `yw_electrical_actions.opr_id` 完全一致(提交时本地生成的 UUID)。 --- ## 三、充值记录页对接 ### 3.1 数据流 ```text 用户「确认充值」 → operate(recharge) → 三方 recharger(异步) → 写入 yw_electrical_charge(status=0 充值中,opr_id=任务ID) → 写入 yw_electrical_actions + yw_electrical_log 平台回调 electricalNotify(status=SUCCESS) → 更新 yw_electrical_actions.status=1 → 按 opr_id 更新 yw_electrical_charge.status=1,statusTime/statusInfo → 刷新 yw_electrical 余额(refreshBalanceFromData) 回调 FAIL → actions.status=2,charge.status=2 ``` ### 3.2 表字段对应 | yw_electrical_charge | 来源 | |----------------------|------| | obj_id | 电表 id | | address / name / c_id | 电表档案 | | money | 充值金额 | | remark | 充值备注 | | opr_id | 异步任务 opr_id | | status | 0充值中 / 1成功 / 2失败 | | banlance | 充值前账户余额(电表 balance) | | room_names | 绑定房间展示名 | ### 3.3 前端页面 - **菜单路径**:`/business/ywelectricalcharge` - **页面**:`admin/src/views/business/ywelectricalcharge.vue` - **接口**:`/visitsAdmin/cloudService/business/ywElectricalCharge/page` - **权限**:`business:ywelectricalcharge:query` 用户在智能电表远程控制提交充值后,提示「请到充值记录查看结果」,即在列表按表名/地址、`opr_id`、状态筛选查看。 ### 3.4 与「商户充值」区别 侧栏「商户充值 / 充值记录」若为 `yw_top_up_log` 等业务,与 **电表充值记录**(`yw_electrical_charge`)为不同数据源;日常用电下「充值记录」菜单指向电表专用页面。 --- ## 四、相关配置项汇总 | 配置项 | 位置 | 说明 | |--------|------|------| | auth_code | ElectronicConstant | 三方授权码 | | nonce | ElectronicConstant | 签名随机串 | | notify_url | ElectronicConstant | 异步通知完整 URL | | api_url / api2_url | ElectronicConstant | 三方 API 根地址 | --- ## 五、定时任务(system_timer + quartz_job) 由 `system_timer` 读取 `quartz_job` 表,通过 `visitServiceJob` Bean 反射调用 `VisitServiceFegin` 对应方法,HTTP 落到 `admin_timer` 的 `YwTimerController`。 配置脚本:`server/db/quartz_job.yw_timer.sql`(执行后需在作业调度平台重启/恢复任务以加载 Cron)。 | 任务 | Cron | module(Feign 方法) | HTTP(admin_timer) | |------|------|----------------------|---------------------| | 采集器状态 | `0 */5 * * * ?` | `getElectricalStatus` | GET `/timer/yw/getElectricalStatus` | | 批量抄表 | `30 0 * * * ?` | `syncElectricalMeterData` | GET `/timer/yw/syncElectricalMeterData` | | 日志清理 | `0 30 2 * * ?` | `cleanElectricalLog` | GET `/timer/yw/cleanElectricalLog` | | 空调网关状态 | `0 */5 * * * ?` | `syncConditionerGatewayStatus` | GET `/timer/yw/syncConditionerGatewayStatus` | | 空调内机状态 | `0 */10 * * * ?` | `syncConditionerIndoorUnits` | GET `/timer/yw/syncConditionerIndoorUnits` |