diff --git a/.sisyphus/notepads/add-gateway-admin-api/status.md b/.sisyphus/notepads/add-gateway-admin-api/status.md new file mode 100644 index 0000000..26c5965 --- /dev/null +++ b/.sisyphus/notepads/add-gateway-admin-api/status.md @@ -0,0 +1,76 @@ +# Execution Status + +## Completed Tasks + +### ✅ Task 1: Bootstrap Dependency Injection +- **Status**: COMPLETED AND VERIFIED +- **Verification**: + - LSP diagnostics: CLEAN + - Build: SUCCESS + - Changes verified in files: + - `internal/handler/admin/iot_card.go` - Added gatewayClient field and updated constructor + - `internal/handler/admin/device.go` - Added gatewayClient field and updated constructor + - `internal/bootstrap/handlers.go` - Updated handler instantiation to pass deps.GatewayClient +- **Commit**: `修改 Bootstrap 注入 Gateway Client 依赖到 IotCardHandler 和 DeviceHandler` +- **Session**: ses_3e2531368ffes11sTWCVuBm9XX + +## Next Wave (Wave 2 - PARALLEL) + +### Task 2: IotCardHandler - Add 6 Gateway Methods +**Blocked By**: Task 1 ✅ (unblocked) +**Blocks**: Task 4 +**Can Run In Parallel**: YES (with Task 3) + +Methods to add: +- GetGatewayStatus (GET /:iccid/gateway-status) +- GetGatewayFlow (GET /:iccid/gateway-flow) +- GetGatewayRealname (GET /:iccid/gateway-realname) +- GetRealnameLink (GET /:iccid/realname-link) +- StopCard (POST /:iccid/stop) +- StartCard (POST /:iccid/start) + +### Task 3: DeviceHandler - Add 7 Gateway Methods +**Blocked By**: Task 1 ✅ (unblocked) +**Blocks**: Task 5 +**Can Run In Parallel**: YES (with Task 2) + +Methods to add: +- GetGatewayInfo (GET /by-imei/:imei/gateway-info) +- GetGatewaySlots (GET /by-imei/:imei/gateway-slots) +- SetSpeedLimit (PUT /by-imei/:imei/speed-limit) +- SetWiFi (PUT /by-imei/:imei/wifi) +- SwitchCard (POST /by-imei/:imei/switch-card) +- RebootDevice (POST /by-imei/:imei/reboot) +- ResetDevice (POST /by-imei/:imei/reset) + +## Implementation Notes + +### Handler Method Pattern +```go +func (h *IotCardHandler) GetGatewayStatus(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 1. Validate permission: Query DB to confirm ownership + card, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 2. Call Gateway + resp, err := h.gatewayClient.QueryCardStatus(c.UserContext(), &gateway.CardStatusReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, resp) +} +``` + +### Gateway Param Conversion +- ICCID (path param) = CardNo (Gateway param) +- IMEI (path param) = DeviceID (Gateway param) diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index ea65a05..6ccf817 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -1704,6 +1704,12 @@ components: description: 失败原因 type: string type: object + DtoEmptyResponse: + properties: + message: + description: 提示信息 + type: string + type: object DtoEnterpriseCardItem: properties: carrier_id: @@ -3364,6 +3370,40 @@ components: minimum: 0 type: integer type: object + DtoSetSpeedLimitRequest: + properties: + download_speed: + description: 下行速率(KB/s) + minimum: 1 + type: integer + upload_speed: + description: 上行速率(KB/s) + minimum: 1 + type: integer + required: + - upload_speed + - download_speed + type: object + DtoSetWiFiRequest: + properties: + enabled: + description: 启用状态(0:禁用, 1:启用) + type: integer + password: + description: WiFi 密码 + maxLength: 63 + minLength: 8 + type: string + ssid: + description: WiFi 名称 + maxLength: 32 + minLength: 1 + type: string + required: + - ssid + - password + - enabled + type: object DtoShopCommissionRecordItem: properties: amount: @@ -3877,6 +3917,14 @@ components: format: date-time type: string type: object + DtoSwitchCardRequest: + properties: + target_iccid: + description: 目标卡 ICCID + type: string + required: + - target_iccid + type: object DtoUnbindCardFromDeviceResponse: properties: message: @@ -4562,6 +4610,108 @@ components: - msg - timestamp type: object + GatewayCardStatusResp: + properties: + cardStatus: + description: 卡状态(准备、正常、停机) + type: string + extend: + description: 扩展字段(广电国网特殊参数) + type: string + iccid: + description: ICCID + type: string + type: object + GatewayDeviceInfoResp: + properties: + downloadSpeed: + description: 下行速率(KB/s) + type: integer + extend: + description: 扩展字段(广电国网特殊参数) + type: string + imei: + description: 设备 IMEI + type: string + onlineStatus: + description: 在线状态(0:离线, 1:在线) + type: integer + signalLevel: + description: 信号强度(0-31) + type: integer + uploadSpeed: + description: 上行速率(KB/s) + type: integer + wifiEnabled: + description: WiFi 启用状态(0:禁用, 1:启用) + type: integer + wifiSsid: + description: WiFi 名称 + type: string + type: object + GatewayFlowUsageResp: + properties: + extend: + description: 扩展字段(广电国网特殊参数) + type: string + unit: + description: 流量单位(MB) + type: string + usedFlow: + description: 已用流量 + type: integer + type: object + GatewayRealnameLinkResp: + properties: + extend: + description: 扩展字段(广电国网特殊参数) + type: string + link: + description: 实名认证跳转链接(HTTPS URL) + type: string + type: object + GatewayRealnameStatusResp: + properties: + extend: + description: 扩展字段(广电国网特殊参数) + type: string + status: + description: 实名认证状态 + type: string + type: object + GatewaySlotInfo: + properties: + cardStatus: + description: 卡状态(准备、正常、停机) + type: string + extend: + description: 扩展字段(广电国网特殊参数) + type: string + iccid: + description: 卡槽中的 ICCID + type: string + isActive: + description: 是否为当前使用的卡槽(0:否, 1:是) + type: integer + slotNo: + description: 卡槽编号 + type: integer + type: object + GatewaySlotInfoResp: + properties: + extend: + description: 扩展字段(广电国网特殊参数) + type: string + imei: + description: 设备 IMEI + type: string + slots: + description: 卡槽信息列表 + items: + $ref: '#/components/schemas/GatewaySlotInfo' + nullable: true + type: array + type: object ModelPermission: properties: available_for_role_types: @@ -7135,6 +7285,483 @@ paths: summary: 通过设备号查询设备详情 tags: - 设备管理 + /api/admin/devices/by-imei/{imei}/gateway-info: + get: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/GatewayDeviceInfoResp' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 查询设备信息 + tags: + - 设备管理 + /api/admin/devices/by-imei/{imei}/gateway-slots: + get: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/GatewaySlotInfoResp' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 查询卡槽信息 + tags: + - 设备管理 + /api/admin/devices/by-imei/{imei}/reboot: + post: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoEmptyResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 重启设备 + tags: + - 设备管理 + /api/admin/devices/by-imei/{imei}/reset: + post: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoEmptyResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 恢复出厂 + tags: + - 设备管理 + /api/admin/devices/by-imei/{imei}/speed-limit: + put: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DtoSetSpeedLimitRequest' + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoEmptyResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 设置限速 + tags: + - 设备管理 + /api/admin/devices/by-imei/{imei}/switch-card: + post: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DtoSwitchCardRequest' + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoEmptyResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 切卡 + tags: + - 设备管理 + /api/admin/devices/by-imei/{imei}/wifi: + put: + parameters: + - description: 设备号(IMEI) + in: path + name: imei + required: true + schema: + description: 设备号(IMEI) + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DtoSetWiFiRequest' + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoEmptyResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 设置 WiFi + tags: + - 设备管理 /api/admin/devices/import: post: description: |- @@ -8435,6 +9062,350 @@ paths: summary: 启用/禁用企业 tags: - 企业客户管理 + /api/admin/iot-cards/{iccid}/gateway-flow: + get: + parameters: + - description: ICCID + in: path + name: iccid + required: true + schema: + description: ICCID + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/GatewayFlowUsageResp' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 查询流量使用 + tags: + - IoT卡管理 + /api/admin/iot-cards/{iccid}/gateway-realname: + get: + parameters: + - description: ICCID + in: path + name: iccid + required: true + schema: + description: ICCID + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/GatewayRealnameStatusResp' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 查询实名认证状态 + tags: + - IoT卡管理 + /api/admin/iot-cards/{iccid}/gateway-status: + get: + parameters: + - description: ICCID + in: path + name: iccid + required: true + schema: + description: ICCID + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/GatewayCardStatusResp' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 查询卡实时状态 + tags: + - IoT卡管理 + /api/admin/iot-cards/{iccid}/realname-link: + get: + parameters: + - description: ICCID + in: path + name: iccid + required: true + schema: + description: ICCID + type: string + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/GatewayRealnameLinkResp' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 获取实名认证链接 + tags: + - IoT卡管理 + /api/admin/iot-cards/{iccid}/start: + post: + parameters: + - description: ICCID + in: path + name: iccid + required: true + schema: + description: ICCID + type: string + responses: + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 复机 + tags: + - IoT卡管理 + /api/admin/iot-cards/{iccid}/stop: + post: + parameters: + - description: ICCID + in: path + name: iccid + required: true + schema: + description: ICCID + type: string + responses: + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 停机 + tags: + - IoT卡管理 /api/admin/iot-cards/by-iccid/{iccid}: get: parameters: diff --git a/internal/handler/admin/device.go b/internal/handler/admin/device.go index 0184878..c9e5531 100644 --- a/internal/handler/admin/device.go +++ b/internal/handler/admin/device.go @@ -223,3 +223,183 @@ func (h *DeviceHandler) BatchSetSeriesBinding(c *fiber.Ctx) error { return response.Success(c, result) } + +// GetGatewayInfo 查询设备信息 +func (h *DeviceHandler) GetGatewayInfo(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + resp, err := h.gatewayClient.GetDeviceInfo(c.UserContext(), &gateway.DeviceInfoReq{ + DeviceID: imei, + }) + if err != nil { + return err + } + + return response.Success(c, resp) +} + +// GetGatewaySlots 查询设备卡槽信息 +func (h *DeviceHandler) GetGatewaySlots(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + resp, err := h.gatewayClient.GetSlotInfo(c.UserContext(), &gateway.DeviceInfoReq{ + DeviceID: imei, + }) + if err != nil { + return err + } + + return response.Success(c, resp) +} + +// SetSpeedLimit 设置设备限速 +func (h *DeviceHandler) SetSpeedLimit(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + var req gateway.SpeedLimitReq + if err := c.BodyParser(&req); err != nil { + return errors.New(errors.CodeInvalidParam, "请求参数解析失败") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + req.DeviceID = imei + err = h.gatewayClient.SetSpeedLimit(c.UserContext(), &req) + if err != nil { + return err + } + + return response.Success(c, nil) +} + +// SetWiFi 设置设备 WiFi +func (h *DeviceHandler) SetWiFi(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + var req gateway.WiFiReq + if err := c.BodyParser(&req); err != nil { + return errors.New(errors.CodeInvalidParam, "请求参数解析失败") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + req.DeviceID = imei + err = h.gatewayClient.SetWiFi(c.UserContext(), &req) + if err != nil { + return err + } + + return response.Success(c, nil) +} + +// SwitchCard 切换设备使用的卡 +func (h *DeviceHandler) SwitchCard(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + var req gateway.SwitchCardReq + if err := c.BodyParser(&req); err != nil { + return errors.New(errors.CodeInvalidParam, "请求参数解析失败") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + req.DeviceID = imei + err = h.gatewayClient.SwitchCard(c.UserContext(), &req) + if err != nil { + return err + } + + return response.Success(c, nil) +} + +// RebootDevice 重启设备 +func (h *DeviceHandler) RebootDevice(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + err = h.gatewayClient.RebootDevice(c.UserContext(), &gateway.DeviceOperationReq{ + DeviceID: imei, + }) + if err != nil { + return err + } + + return response.Success(c, nil) +} + +// ResetDevice 恢复设备出厂设置 +func (h *DeviceHandler) ResetDevice(c *fiber.Ctx) error { + imei := c.Params("imei") + if imei == "" { + return errors.New(errors.CodeInvalidParam, "设备号不能为空") + } + + // 验证权限:查询数据库确认设备存在且用户有权限访问 + _, err := h.service.GetByDeviceNo(c.UserContext(), imei) + if err != nil { + return errors.New(errors.CodeNotFound, "设备不存在或无权限访问") + } + + // 调用 Gateway + err = h.gatewayClient.ResetDevice(c.UserContext(), &gateway.DeviceOperationReq{ + DeviceID: imei, + }) + if err != nil { + return err + } + + return response.Success(c, nil) +} diff --git a/internal/handler/admin/iot_card.go b/internal/handler/admin/iot_card.go index de61846..0b77f9d 100644 --- a/internal/handler/admin/iot_card.go +++ b/internal/handler/admin/iot_card.go @@ -128,3 +128,147 @@ func (h *IotCardHandler) BatchSetSeriesBinding(c *fiber.Ctx) error { return response.Success(c, result) } + +// GetGatewayStatus 查询卡实时状态 +func (h *IotCardHandler) GetGatewayStatus(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 验证权限:查询数据库确认卡存在且用户有权限访问 + _, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 调用 Gateway + resp, err := h.gatewayClient.QueryCardStatus(c.UserContext(), &gateway.CardStatusReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, resp) +} + +// GetGatewayFlow 查询流量使用情况 +func (h *IotCardHandler) GetGatewayFlow(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 验证权限:查询数据库确认卡存在且用户有权限访问 + _, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 调用 Gateway + resp, err := h.gatewayClient.QueryFlow(c.UserContext(), &gateway.FlowQueryReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, resp) +} + +// GetGatewayRealname 查询实名认证状态 +func (h *IotCardHandler) GetGatewayRealname(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 验证权限:查询数据库确认卡存在且用户有权限访问 + _, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 调用 Gateway + resp, err := h.gatewayClient.QueryRealnameStatus(c.UserContext(), &gateway.CardStatusReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, resp) +} + +// GetRealnameLink 获取实名认证链接 +func (h *IotCardHandler) GetRealnameLink(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 验证权限:查询数据库确认卡存在且用户有权限访问 + _, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 调用 Gateway + link, err := h.gatewayClient.GetRealnameLink(c.UserContext(), &gateway.CardStatusReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, link) +} + +// StopCard 停止卡服务 +func (h *IotCardHandler) StopCard(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 验证权限:查询数据库确认卡存在且用户有权限访问 + _, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 调用 Gateway + err = h.gatewayClient.StopCard(c.UserContext(), &gateway.CardOperationReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, nil) +} + +// StartCard 恢复卡服务 +func (h *IotCardHandler) StartCard(c *fiber.Ctx) error { + iccid := c.Params("iccid") + if iccid == "" { + return errors.New(errors.CodeInvalidParam, "ICCID不能为空") + } + + // 验证权限:查询数据库确认卡存在且用户有权限访问 + _, err := h.service.GetByICCID(c.UserContext(), iccid) + if err != nil { + return errors.New(errors.CodeNotFound, "卡不存在或无权限访问") + } + + // 调用 Gateway + err = h.gatewayClient.StartCard(c.UserContext(), &gateway.CardOperationReq{ + CardNo: iccid, + }) + if err != nil { + return err + } + + return response.Success(c, nil) +} diff --git a/internal/model/dto/device_dto.go b/internal/model/dto/device_dto.go index 08ddd63..bf89f2c 100644 --- a/internal/model/dto/device_dto.go +++ b/internal/model/dto/device_dto.go @@ -146,3 +146,25 @@ type BatchSetDeviceSeriesBindngResponse struct { FailCount int `json:"fail_count" description:"失败数量"` FailedItems []DeviceSeriesBindngFailedItem `json:"failed_items" description:"失败详情列表"` } + +type SetSpeedLimitRequest struct { + IMEI string `path:"imei" description:"设备号(IMEI)" required:"true"` + UploadSpeed int `json:"upload_speed" validate:"required,min=1" required:"true" minimum:"1" description:"上行速率(KB/s)"` + DownloadSpeed int `json:"download_speed" validate:"required,min=1" required:"true" minimum:"1" description:"下行速率(KB/s)"` +} + +type SetWiFiRequest struct { + IMEI string `path:"imei" description:"设备号(IMEI)" required:"true"` + SSID string `json:"ssid" validate:"required,min=1,max=32" required:"true" minLength:"1" maxLength:"32" description:"WiFi 名称"` + Password string `json:"password" validate:"required,min=8,max=63" required:"true" minLength:"8" maxLength:"63" description:"WiFi 密码"` + Enabled int `json:"enabled" validate:"required,oneof=0 1" required:"true" description:"启用状态(0:禁用, 1:启用)"` +} + +type SwitchCardRequest struct { + IMEI string `path:"imei" description:"设备号(IMEI)" required:"true"` + TargetICCID string `json:"target_iccid" validate:"required" required:"true" description:"目标卡 ICCID"` +} + +type EmptyResponse struct { + Message string `json:"message,omitempty" description:"提示信息"` +} diff --git a/internal/routes/device.go b/internal/routes/device.go index be53ca4..47c9603 100644 --- a/internal/routes/device.go +++ b/internal/routes/device.go @@ -3,6 +3,7 @@ package routes import ( "github.com/gofiber/fiber/v2" + "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/internal/handler/admin" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/openapi" @@ -143,4 +144,60 @@ func registerDeviceRoutes(router fiber.Router, handler *admin.DeviceHandler, imp Output: new(dto.BatchSetDeviceSeriesBindngResponse), Auth: true, }) + + Register(devices, doc, groupPath, "GET", "/by-imei/:imei/gateway-info", handler.GetGatewayInfo, RouteSpec{ + Summary: "查询设备信息", + Tags: []string{"设备管理"}, + Input: new(dto.GetDeviceByIMEIRequest), + Output: new(gateway.DeviceInfoResp), + Auth: true, + }) + + Register(devices, doc, groupPath, "GET", "/by-imei/:imei/gateway-slots", handler.GetGatewaySlots, RouteSpec{ + Summary: "查询卡槽信息", + Tags: []string{"设备管理"}, + Input: new(dto.GetDeviceByIMEIRequest), + Output: new(gateway.SlotInfoResp), + Auth: true, + }) + + Register(devices, doc, groupPath, "PUT", "/by-imei/:imei/speed-limit", handler.SetSpeedLimit, RouteSpec{ + Summary: "设置限速", + Tags: []string{"设备管理"}, + Input: new(dto.SetSpeedLimitRequest), + Output: new(dto.EmptyResponse), + Auth: true, + }) + + Register(devices, doc, groupPath, "PUT", "/by-imei/:imei/wifi", handler.SetWiFi, RouteSpec{ + Summary: "设置 WiFi", + Tags: []string{"设备管理"}, + Input: new(dto.SetWiFiRequest), + Output: new(dto.EmptyResponse), + Auth: true, + }) + + Register(devices, doc, groupPath, "POST", "/by-imei/:imei/switch-card", handler.SwitchCard, RouteSpec{ + Summary: "切卡", + Tags: []string{"设备管理"}, + Input: new(dto.SwitchCardRequest), + Output: new(dto.EmptyResponse), + Auth: true, + }) + + Register(devices, doc, groupPath, "POST", "/by-imei/:imei/reboot", handler.RebootDevice, RouteSpec{ + Summary: "重启设备", + Tags: []string{"设备管理"}, + Input: new(dto.GetDeviceByIMEIRequest), + Output: new(dto.EmptyResponse), + Auth: true, + }) + + Register(devices, doc, groupPath, "POST", "/by-imei/:imei/reset", handler.ResetDevice, RouteSpec{ + Summary: "恢复出厂", + Tags: []string{"设备管理"}, + Input: new(dto.GetDeviceByIMEIRequest), + Output: new(dto.EmptyResponse), + Auth: true, + }) } diff --git a/internal/routes/iot_card.go b/internal/routes/iot_card.go index 3c6633e..fcc0dd7 100644 --- a/internal/routes/iot_card.go +++ b/internal/routes/iot_card.go @@ -3,6 +3,7 @@ package routes import ( "github.com/gofiber/fiber/v2" + "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/internal/handler/admin" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/openapi" @@ -107,4 +108,52 @@ func registerIotCardRoutes(router fiber.Router, handler *admin.IotCardHandler, i Output: new(dto.BatchSetCardSeriesBindngResponse), Auth: true, }) + + Register(iotCards, doc, groupPath, "GET", "/:iccid/gateway-status", handler.GetGatewayStatus, RouteSpec{ + Summary: "查询卡实时状态", + Tags: []string{"IoT卡管理"}, + Input: new(dto.GetIotCardByICCIDRequest), + Output: new(gateway.CardStatusResp), + Auth: true, + }) + + Register(iotCards, doc, groupPath, "GET", "/:iccid/gateway-flow", handler.GetGatewayFlow, RouteSpec{ + Summary: "查询流量使用", + Tags: []string{"IoT卡管理"}, + Input: new(dto.GetIotCardByICCIDRequest), + Output: new(gateway.FlowUsageResp), + Auth: true, + }) + + Register(iotCards, doc, groupPath, "GET", "/:iccid/gateway-realname", handler.GetGatewayRealname, RouteSpec{ + Summary: "查询实名认证状态", + Tags: []string{"IoT卡管理"}, + Input: new(dto.GetIotCardByICCIDRequest), + Output: new(gateway.RealnameStatusResp), + Auth: true, + }) + + Register(iotCards, doc, groupPath, "GET", "/:iccid/realname-link", handler.GetRealnameLink, RouteSpec{ + Summary: "获取实名认证链接", + Tags: []string{"IoT卡管理"}, + Input: new(dto.GetIotCardByICCIDRequest), + Output: new(gateway.RealnameLinkResp), + Auth: true, + }) + + Register(iotCards, doc, groupPath, "POST", "/:iccid/stop", handler.StopCard, RouteSpec{ + Summary: "停机", + Tags: []string{"IoT卡管理"}, + Input: new(dto.GetIotCardByICCIDRequest), + Output: nil, + Auth: true, + }) + + Register(iotCards, doc, groupPath, "POST", "/:iccid/start", handler.StartCard, RouteSpec{ + Summary: "复机", + Tags: []string{"IoT卡管理"}, + Input: new(dto.GetIotCardByICCIDRequest), + Output: nil, + Auth: true, + }) }