feat(gateway): 新增智电改造网关台账功能(ICC消防物联设备同步) - 新建electric_gateway表(迁移20261024_icc_gateway.sql并同步db_seed:自增id、icc_imei/device_code双唯一、orgCode经customer.icc_code解析customer_id、equip_id可空、is_online/synced_at等);同步服务IccGatewaySyncService复用OAuth2真实鉴权分页拉取消防物联设备(POST /device/subsystem/page)并按imei幂等upsert写icc_sync_log;新增GET /api/electric-gateways列表/详情与POST /sync手动同步(RBAC device:list);注册icc_gateway_sync每10分钟定时任务(菜单62/role_menu/sys_job种子);前端网关台账页(搜索/筛选/同步按钮/在线状态tag/分页)与路由;server补装undici依赖

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
This commit is contained in:
wkandClaude Sonnet 4.6 noreply@anthropic.com authored and trae-solo-agent committed 2026-10-24 12:37:35 +08:00
1 parent 9d3bb13ecf
commit cf0a0d4d31
21 files changed
+1439 -272

No files matched your search

@@ -0,0 +1,13 @@
# Checklist
- [x] electric_gateway 表已建(迁移 20261024_icc_gateway.sql),字段集符合需求方确认:自增主键 id、icc_imei/device_code 双唯一、customer_id→customer、equip_id→customer_equipment 可空、org_code/org_name/manufacturer_code/manufacturer_name/type_code/type_name/product_model/is_online/synced_at/created_at;不含 companyId/companyName/dcpsId/softwareVersion/systemAddress/address/description;与 db_seed.sql 结构一致 —— 迁移已执行到远程生产库(信息模式校验 16 列),db_seed.sql 451-522 行同步
- [x] 菜单 62「智电改造网关台账」挂设备监控(4)(permission device:list)、ADMIN role_menu、sys_job 种子 icc_gateway_sync(cron `*/10 * * * *`)已写入迁移与 db_seed.sql 且幂等 —— 生产库已存在(menu62 查询命中、role_menu(1,62)、job 行 status=1)
- [x] ElectricGateway 实体与迁移结构一致(field 映射、索引)—— 实体含 @ForeignKey Customer/CustomerEquipment、@BelongsTo、3 索引
- [x] icc_gateway_mapper.ts 正确映射 DeviceInfoVO 字段(imei/deviceCode/deviceName/orgCode/orgName/manufacturerCode/manufacturerName/typeCode/typeName/productModel/isOnline)并解析分页响应(PageVO{nextPage,totalCount,pageData})—— 实测映射正确(typeName=组合式电气火灾监控探测器、manufacturerName=大华、productModel=DH-…)
- [x] IccGatewaySyncService 复用 getIccAccessToken/iccFetch 真实鉴权,POST 分页全量拉取(pageSize=50,循环至短页,最大页数保护),orgCode→customer.icc_code 解析 customer_id —— 实测 pulled=2,customerId 解析为 3(KH20261016001)
- [x] 同步按 icc_imei(缺省 device_code)幂等 upsert,跳过缺双键或匹配不到客户的行,更新 synced_at,写 icc_sync_log(sync_type=electric_gateway);失败仅记录不抛业务错误 —— 实测首次 inserted=2,重跑 updated=2 skipped=0;icc_sync_log 2 条 status=1 record_count=2
- [x] GET /api/electric-gateways 列表/详情、POST /api/electric-gateways/sync 已实现;RBAC `device:list` 已映射;非 ADMIN 按客户归属限定数据范围 —— 无 token 401、admin 列表 200
- [x] 定时任务 icc_gateway_sync 已注册(job_registry BUILTIN_JOBS + job_scheduler buildRuntime syncIccGateways + db_seed.sql 种子)—— 调度器启动日志"已加载 6 个任务"含 icc_gateway_sync cron=*/10 * * * *
- [x] server/.env 含 ICC_BASE_URL=https://222.243.55.92:1443、ICC_VERSION=1.0.0、ICC_CLIENT_ID/ICC_CLIENT_SECRET/ICC_USERNAME/ICC_PASSWORD —— 已确认齐全
- [x] 前端 views/device/gateway/index.vue(搜索/同步按钮/表格/在线状态 tag/分页)、路由 /device/gateway、api/device.ts electricGatewayApi 已实现 —— 浏览器实测渲染/同步/筛选/重置全部正常
- [x] 后端编译(tsc --noEmit)与前端类型检查(vue-tsc -b)通过;运行验证:401/200、sync 真实拉取成功不 500、菜单与页面渲染正常 —— 全部实测通过
+46
View File
@@ -0,0 +1,46 @@
# 智电改造网关台账(ICC 消防物联设备同步)Spec
## Why
系统缺少"智电改造网关设备"台账。网关设备数据由 ICC 平台消防物联设备接口(`/evo-apigw/evo-fdbu/{version}/device/subsystem/page`)提供,需定时拉取并落库到新建的 `electric_gateway` 表,运营侧可按客户查看网关设备台账、手动触发同步。本表在设计文档中未定义,需依据接口文档新增表结构(已与需求方确认字段集)。
## What Changes
- **新增** `electric_gateway` 表(数据库迁移 + db_seed.sql 同步):自增主键 `id`、`icc_imei`/`device_code` 双唯一键、`device_name`、`customer_id`(→ customer,由接口 orgCode→customer.icc_code 解析)、`equip_id`(→ customer_equipment,可空,同步不填充)、`org_code`/`org_name`、`manufacturer_code`/`manufacturer_name`、`type_code`/`type_name`、`product_model`、`is_online`、`synced_at`、`created_at`;**不含** companyId/companyName/dcpsId/softwareVersion/systemAddress/address/description(经需求方确认)
- **新增** ICC 网关设备同步:`IccGatewaySyncService` 复用真实 OAuth2 鉴权(`icc_customer_auth.ts` 的 `getIccAccessToken` + `iccFetch`,走 `.env` 已配置凭据),POST 分页拉取消防物联设备全量数据(pageNum/pageSize=50,循环至短页,全量不传 typeCode),按 imei(缺省时 deviceCode)幂等 upsert,写 `icc_sync_log`(sync_type=electric_gateway)
- **新增** 后端接口:`GET /api/electric-gateways` 列表、`GET /api/electric-gateways/:id` 详情、`POST /api/electric-gateways/sync` 手动同步
- **新增** 定时任务:注册 `icc_gateway_sync`(job_registry + job_scheduler buildRuntime + db_seed.sql sys_job 种子,默认 cron `*/10 * * * *`,与既有 ICC 同步任务口径一致)
- **新增** RBAC 映射:`/api/electric-gateways → device:list`
- **新增** 前端"智电改造网关台账"页面(`views/device/gateway/index.vue`),路由 `/device/gateway`,含"同步网关"手动按钮;菜单挂设备监控目录(parent=4)
- **新增** `.env` ICC 网关同步配置(ICC_BASE_URL=https://222.243.55.92:1443、ICC_VERSION=1.0.0、ICC_CLIENT_ID/ICC_CLIENT_SECRET/ICC_USERNAME/ICC_PASSWORD,取自接口文档)
- **安全**:全部 SQL 走 Sequelize 参数绑定;前端不用 v-html(防 XSS);接口返回统一 `code/message/data`
## Impact
- Affected specs: 设备监控(设备台账、智电改造线路台账)、ICC 数据同步、RBAC 权限
- Affected code: server/src(entity/dto/service/controller/task/middleware)、client/src(api/router/views)、database(db_seed.sql、migrations/)
## ADDED Requirements
### Requirement: 网关台账查询(JWT+RBAC)
系统 SHALL 提供网关台账列表与详情查询,仅登录且有 `device:list` 权限的用户可访问;查询支持按 customer_id/equip_id/status(在线状态)/关键词(device_name/device_code/icc_imei)过滤分页;非 ADMIN 业务员按客户归属自动限定数据范围。
#### Scenario: 成功查询
- **WHEN** 已登录用户 GET `/api/electric-gateways?page=1&pageSize=10&customerId=1`
- **THEN** 返回 `code=0,data{list,total}`,含设备名称/编码/imei/客户/设备型号/主机类型/厂商/在线状态/同步时间;无 token 返回 401,无权限返回 403
### Requirement: ICC 网关设备拉取同步(真实鉴权 + 定时 + 手动)
系统 SHALL 提供 ICC 网关设备拉取同步能力:`IccGatewaySyncService` 读取 `.env` 的 ICC 配置(复用 `icc_customer_auth` 真实 OAuth2 密码模式鉴权),POST `/evo-apigw/evo-fdbu/{version}/device/subsystem/page` 分页拉取消防物联设备全量数据(不传 typeCode,pageSize 50,循环至短页,带最大页数保护),字段映射集中在 `icc_gateway_mapper`,按 icc_imei(缺省用 device_code)幂等 upsert 写入 `electric_gateway`,`customer_id` 由 orgCode→customer.icc_code 解析(匹配不到或缺 icc_imei 与 device_code 则跳过),更新 `synced_at`,写 `icc_sync_log`(sync_type=electric_gateway,status/record_count/error_message)。同步入口:① 定时任务(jobKey=`icc_gateway_sync`,默认 `*/10 * * * *`);② 手动 `POST /api/electric-gateways/sync`(JWT+RBAC `device:list`)。
#### Scenario: 定时/手动拉取成功
- **WHEN** 定时任务触发或运营点击"同步网关"
- **THEN** 拉取 ICC 数据并 upsert 对应网关行,`icc_sync_log` 记录成功;返回 `code=0,data{pulled,synced,inserted,updated,skipped}`
#### Scenario: 手动同步鉴权失败
- **WHEN** 未登录或无 `device:list` 权限调用 `/api/electric-gateways/sync`
- **THEN** 返回 401/403,不触发拉取
#### Scenario: ICC 拉取失败(不可达/鉴权失败/接口异常)
- **WHEN** ICC 地址不可达或返回异常
- **THEN** 同步日志记录失败状态与错误信息,业务接口不报错,仅返回已拉取计数
### Requirement: 前端网关台账页
系统 SHALL 提供 `/device/gateway` 页面:搜索栏(设备名称/编码/imei、客户、在线状态)、"同步网关"按钮(调 `/api/electric-gateways/sync`,成功后刷新)、表格(设备名称/编码/imei/客户/厂商/主机类型/设备型号/在线状态 tag/同步时间)、分页;样式与智电改造线路台账一致,全部文本经 Vue 插值转义。
#### Scenario: 打开页面
- **WHEN** 有 `device:list` 权限的用户访问设备监控→智电改造网关台账
- **THEN** 渲染列表并展示分页与筛选
## MODIFIED Requirements
(无既有功能被修改)
+39
View File
@@ -0,0 +1,39 @@
# Tasks
- [x] Task 1: 数据库层:新建迁移 `database/migrations/20261024_icc_gateway.sql` 建 `electric_gateway`(字段集经需求方确认),并把建表/菜单/角色菜单/定时任务种子同步进 `db_seed.sql`;幂等(IF NOT EXISTS / ON CONFLICT)
- [x] SubTask 1.1: 核对生产库是否已有 electric_gateway,避免重复;确认 sys_menu 现有最大 id(迁移用 id=62 并 setval)—— 已核对:表无先例、菜单 id 62 空闲;迁移已执行到远程生产库
- [x] SubTask 1.2: 迁移内容:建表 electric_gateway(自增 id;icc_imei/device_code 唯一;customer_id→customer;equip_id→customer_equipment 可空;org_code/org_name/manufacturer_code/manufacturer_name/type_code/type_name/product_model/is_online/synced_at/created_at;索引 customer_id/equip_id/icc_imei);菜单 62「智电改造网关台账」挂设备监控(4)(permission device:list);ADMIN role_menu;sys_job 种子 icc_gateway_sync(cron `*/10 * * * *`)
- [x] SubTask 1.3: db_seed.sql 同步对应语句(保持幂等一致)—— 已追加段落(451-522 行)
- [x] Task 2: 后端实体与映射器:
- [x] SubTask 2.1: `server/src/entity/electric_gateway.entity.ts`(timestamps:false,结构对齐迁移文件)
- [x] SubTask 2.2: `server/src/task/icc_gateway_mapper.ts`:集中配置端点 `/evo-apigw/evo-fdbu/{version}/device/subsystem/page`、DeviceInfoVO 字段映射(id/imei/deviceCode/deviceName/orgCode/orgName/manufacturerCode/manufacturerName/typeCode/typeName/productModel/isOnline)、分页响应解析(PageVO{nextPage,totalCount,pageData})
- [x] Task 3: 后端同步服务 `server/src/service/icc_gateway_sync.service.ts`:
- [x] SubTask 3.1: 复用 `icc_customer_auth.ts` 的 `getIccAccessToken`/`iccFetch` 真实鉴权;读取 `.env`(ICC_BASE_URL/ICC_VERSION),配置缺失视为失败仅记日志
- [x] SubTask 3.2: POST 分页拉取(pageSize=50、不传 typeCode、循环至短页、最大页数保护),预取 orgCode→customer(id) 映射(customer.icc_code)
- [x] SubTask 3.3: 按 icc_imei(缺省 device_code)幂等 upsert;缺 imei 与 device_code 或匹配不到客户则跳过;更新 synced_at;写 icc_sync_log(sync_type=electric_gateway);拉取失败仅记录不抛业务错误 —— 实测同步成功(pulled=2),重跑 updated=2 幂等
- [x] Task 4: 后端查询服务/Controller/DTO + RBAC:
- [x] SubTask 4.1: `server/src/service/electric_gateway.service.ts`(list/detail,过滤:keyword=device_name/device_code/icc_imei、customer_id、equip_id、is_online;非 ADMIN 按 salesmanId 限定客户范围;decorate 补全客户/设备展示字段)
- [x] SubTask 4.2: `server/src/dto/electric_gateway.dto.ts`(查询/同步入参校验)
- [x] SubTask 4.3: `server/src/controller/electric_gateway.controller.ts`:GET `/api/electric-gateways`、GET `/:id`、POST `/sync`
- [x] SubTask 4.4: `rbac.middleware.ts` 追加 `/api/electric-gateways → device:list`
- [x] Task 5: 定时任务注册:
- [x] SubTask 5.1: `server/src/task/icc_gateway_sync.task.ts`(jobKey=`icc_gateway_sync`,默认 cron `*/10 * * * *`,handler 调 runtime.syncIccGateways)
- [x] SubTask 5.2: `job_registry.ts`:JobRuntime 增加 `syncIccGateways`,BUILTIN_JOBS 追加 iccGatewaySyncJob
- [x] SubTask 5.3: `job_scheduler.service.ts` buildRuntime 增加 `syncIccGateways`(匿名上下文注入 IccGatewaySyncService)
- [x] SubTask 5.4: `server/.env` 补全 ICC 网关同步凭据 —— 已确认 .env 中配置齐全(ICC_BASE_URL=https://222.243.55.92:1443、ICC_VERSION=1.0.0、ICC_CLIENT_ID=xtzdapp、ICC_CLIENT_SECRET、ICC_USERNAME=XTZD接口、ICC_PASSWORD)
- [x] Task 6: 前端:
- [x] SubTask 6.1: `client/src/api/device.ts` 新增 electricGatewayApi.list/get/sync
- [x] SubTask 6.2: `client/src/views/device/gateway/index.vue`(搜索栏+同步按钮+表格+在线状态 tag+分页,样式对齐 views/device/line/index.vue)
- [x] SubTask 6.3: `client/src/router/index.ts` 注册 `/device/gateway`(挂 device 目录)
- [x] Task 7: 验证:
- [x] SubTask 7.1: 后端 `tsc --noEmit`、前端 `vue-tsc -b` 类型检查通过 —— 均通过(exit 0)
- [x] SubTask 7.2: 启动服务:无 token 401(实测 401)、admin 列表 200(实测 total=2)、`POST /sync` 真实拉取成功(pulled=2→落库→重跑 updated=2)、定时任务 icc_gateway_sync 已注册(调度器加载 6 个任务,含 icc_gateway_sync cron */10 * * * *)
- [x] SubTask 7.3: 浏览器验证菜单出现、页面渲染、筛选/分页/同步按钮可用 —— 实测:菜单出现、9 列表格渲染 2 条、同步按钮提示"拉取 2 条,新增 0,更新 2"并刷新、关键词筛选/重置正常
# Task Dependencies
- Task 2/6 依赖 Task 1(表存在)
- Task 3 依赖 Task 2(映射器)
- Task 4 依赖 Task 1/2
- Task 5 依赖 Task 3
- Task 6 依赖 Task 4(接口可用)
- Task 7 依赖全部