## Context 现有代理开放接口(`internal/service/agent_open_api/service.go`)已实现卡维度的流量查询、状态查询、实名查询和钱包购买。设备维度的 Gateway 操作(切网、重启、恢复出厂)已在 `internal/service/device/gateway_service.go` 中实现,设备级套餐流量查询通过 `PackageUsageStore.ListByCarrier(ctx, "device", deviceID, ...)` 支持。 本次变更在现有 `agent_open_api` service 中扩展设备维度能力,复用已有的 device service 和 package usage store,不引入新的数据模型或迁移。 ## Goals / Non-Goals **Goals:** - 新增 4 个代理开放接口:设备流量查询、切网、重启、恢复出厂 - 复用现有 device service 的 Gateway 调用逻辑,不重复实现 - 权限校验与卡接口保持一致(`shop_id IN SubordinateShopIDs`) - 设备标识符支持虚拟号和 IMEI **Non-Goals:** - 不新增数据库表或迁移 - 不修改现有卡接口 - 不支持 SN 作为设备标识符(开放接口只暴露虚拟号/IMEI) - 不支持批量设备操作 ## Decisions ### 决策一:agent_open_api service 注入 device.Service 依赖 **选择**:在 `agent_open_api.Service` 结构体中新增 `deviceService *device.Service` 字段,通过构造函数注入。 **理由**:device service 已封装了 Gateway 调用、审计日志和设备查询逻辑,直接复用避免重复实现。相比新建独立函数,注入 service 更符合项目分层规范。 **替代方案**:在 agent_open_api service 中直接注入 deviceStore 和 gatewayClient,自行实现权限校验和 Gateway 调用。缺点是重复了 device service 中已有的审计日志和错误处理逻辑。 ### 决策二:设备权限校验方式 **选择**:查询设备后,手动检查 `device.ShopID != nil && SubordinateShopIDs 包含 *device.ShopID`。 **理由**:`ApplyShopFilter` 作用于 GORM query,而 device service 的 `GetByIdentifier` 内部已有自己的查询逻辑。为避免侵入 device service,在 agent_open_api service 层做显式权限校验,与卡接口的 `resolveOpenAPICard` 模式一致。 **实现**:新增 `resolveOpenAPIDevice(ctx, deviceNo)` 私有方法,复用 `device.Service.GetDeviceByIdentifier`,然后校验 shop_id。 ### 决策三:设备流量查询复用 PackageUsageStore **选择**:直接在 `agent_open_api.Service` 中调用已注入的 `packageUsageStore.ListByCarrier(ctx, "device", device.ID, &status)`。 **理由**:`packageUsageStore` 已在 agent_open_api service 中注入(用于卡流量查询),`ListByCarrier` 支持 `"device"` 类型,无需额外改动 store 层。 ### 决策四:切网接口调用 device.Service.GatewaySwitchCard **选择**:agent_open_api service 调用 `deviceService.GatewaySwitchCard(ctx, identifier, &dto.SwitchCardRequest{ICCID: req.ICCID})`。 **理由**:`GatewaySwitchCard` 已包含审计日志记录,开放接口复用可保证操作可追溯。 **注意**:`GatewaySwitchCard` 内部通过 `getGatewayDevice` 再次查询设备,存在两次查询。考虑到操作频率低,接受此开销,不做优化。 ## Risks / Trade-offs - **[风险] device service 审计日志中的操作者信息**:`GatewaySwitchCard` 等方法通过 `middleware.GetUserIDFromContext` 获取操作者 ID。开放接口认证中间件已将代理账号 ID 写入 `ContextKeyUserID`,审计日志可正确记录操作者。→ 无需额外处理。 - **[风险] 设备标识符两次查询**:`resolveOpenAPIDevice` 查一次,`GatewaySwitchCard` 内部再查一次。→ 接受,操作类接口频率低,影响可忽略。 - **[Trade-off] 不支持 SN**:admin 接口支持虚拟号/IMEI/SN,开放接口只支持虚拟号/IMEI。→ 简化外部接口,SN 是内部管理标识,不适合对外暴露。 ## Migration Plan 无数据库变更,无需迁移。直接部署新版本即可。 ## Open Questions (无)