From 246ea6e28770d411d3528dc35364156d8eb412ed Mon Sep 17 00:00:00 2001 From: huang Date: Mon, 2 Feb 2026 17:27:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20Bootstrap=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=20Gateway=20Client=20=E4=BE=9D=E8=B5=96=E5=88=B0=20Io?= =?UTF-8?q?tCardHandler=20=E5=92=8C=20DeviceHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .sisyphus/boulder.json | 4 +- .../notepads/add-gateway-admin-api/context.md | 81 +++++++++++++++++++ .../add-gateway-admin-api/learnings.md | 75 +++++++++++++++++ internal/bootstrap/handlers.go | 4 +- internal/handler/admin/device.go | 9 ++- internal/handler/admin/iot_card.go | 11 ++- pkg/openapi/handlers.go | 4 +- tests/integration/account_role_test.go | 13 ++- 8 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 .sisyphus/notepads/add-gateway-admin-api/context.md create mode 100644 .sisyphus/notepads/add-gateway-admin-api/learnings.md diff --git a/.sisyphus/boulder.json b/.sisyphus/boulder.json index eb1acfc..b685c21 100644 --- a/.sisyphus/boulder.json +++ b/.sisyphus/boulder.json @@ -1,8 +1,8 @@ { "active_plan": "/Users/break/csxjProject/junhong_cmp_fiber/.sisyphus/plans/add-gateway-admin-api.md", - "started_at": "2026-02-02T07:13:46.674Z", + "started_at": "2026-02-02T09:24:48.582Z", "session_ids": [ - "ses_3e2ccb556ffeOMG11wg2q0HOzK" + "ses_3e254bedbffeBTwWDP2VQqDr7q" ], "plan_name": "add-gateway-admin-api" } \ No newline at end of file diff --git a/.sisyphus/notepads/add-gateway-admin-api/context.md b/.sisyphus/notepads/add-gateway-admin-api/context.md new file mode 100644 index 0000000..e62e429 --- /dev/null +++ b/.sisyphus/notepads/add-gateway-admin-api/context.md @@ -0,0 +1,81 @@ +# Context & Architecture Understanding + +## Key Findings + +### Gateway Client Already Initialized +- ✅ `internal/gateway/client.go` - Complete Gateway client implementation +- ✅ `internal/bootstrap/dependencies.go` - GatewayClient is a field in Dependencies struct +- ✅ `internal/gateway/flow_card.go` - 6+ card-related Gateway methods +- ✅ `internal/gateway/device.go` - 7+ device-related Gateway methods + +### Current Handler Structure +- `internal/handler/admin/iot_card.go` - Has 4 existing methods (ListStandalone, GetByICCID, AllocateCards, RecallCards) +- `internal/handler/admin/device.go` - Has 5 existing methods (List, GetByID, GetByIMEI, Delete, ListCards) +- Both handlers receive only service, not Gateway client yet + +### Service Layer Already Uses Gateway +- `internal/service/iot_card/service.go` - Already has gateway.Client dependency +- `internal/service/device/service.go` - Needs to be checked if it has gateway.Client + +### Handler Constructor Pattern +```go +// Current pattern +func NewIotCardHandler(service *iotCardService.Service) *IotCardHandler +func NewDeviceHandler(service *deviceService.Service) *DeviceHandler + +// New pattern (needed) +func NewIotCardHandler(service *iotCardService.Service, gatewayClient *gateway.Client) *IotCardHandler +func NewDeviceHandler(service *deviceService.Service, gatewayClient *gateway.Client) *DeviceHandler +``` + +### Bootstrap Injection Pattern +```go +// In initHandlers() function at internal/bootstrap/handlers.go +IotCard: admin.NewIotCardHandler(svc.IotCard), +Device: admin.NewDeviceHandler(svc.Device), + +// Needs to be changed to: +IotCard: admin.NewIotCardHandler(svc.IotCard, deps.GatewayClient), +Device: admin.NewDeviceHandler(svc.Device, deps.GatewayClient), +``` + +### Route Registration Pattern +```go +// From internal/routes/iot_card.go +Register(iotCards, doc, groupPath, "GET", "/standalone", handler.ListStandalone, RouteSpec{ + Summary: "单卡列表(未绑定设备)", + Tags: []string{"IoT卡管理"}, + Input: new(dto.ListStandaloneIotCardRequest), + Output: new(dto.ListStandaloneIotCardResponse), + Auth: true, +}) +``` + +## Gateway Method Mappings + +### Card Methods (flow_card.go) +- QueryCardStatus(ctx, req) → CardStatusResp +- QueryFlow(ctx, req) → FlowUsageResp +- QueryRealnameStatus(ctx, req) → RealnameStatusResp +- GetRealnameLink(ctx, req) → string (link) +- StopCard(ctx, req) → error +- StartCard(ctx, req) → error + +### Device Methods (device.go) +- GetDeviceInfo(ctx, req) → DeviceInfoResp +- GetSlotInfo(ctx, req) → SlotInfoResp +- SetSpeedLimit(ctx, req) → error +- SetWiFi(ctx, req) → error +- SwitchCard(ctx, req) → error +- RebootDevice(ctx, req) → error +- ResetDevice(ctx, req) → error + +## Store Methods for Permission Validation +- `IotCardStore.GetByICCID(ctx, iccid)` - Validate card ownership +- `DeviceStore.GetByDeviceNo(ctx, imei)` - Validate device ownership + +## Important Conventions +1. Permission errors return: `errors.New(errors.CodeNotFound, "卡不存在或无权限访问")` +2. All card params: ICCID from path param, CardNo = ICCID +3. All device params: IMEI from path param, DeviceID = IMEI +4. Handler methods follow: Get params → Validate permissions → Call Gateway → Format response diff --git a/.sisyphus/notepads/add-gateway-admin-api/learnings.md b/.sisyphus/notepads/add-gateway-admin-api/learnings.md new file mode 100644 index 0000000..49ab000 --- /dev/null +++ b/.sisyphus/notepads/add-gateway-admin-api/learnings.md @@ -0,0 +1,75 @@ +# Learnings - add-gateway-admin-api + +## Plan Overview +- **Goal**: Expose 13 Gateway third-party capabilities as admin management APIs +- **Deliverables**: 6 IoT card endpoints + 7 device endpoints +- **Effort**: Medium +- **Parallel Execution**: YES - 2 waves + +## Execution Strategy +``` +Wave 1: Task 1 (Bootstrap dependency injection) +Wave 2: Task 2 + Task 3 (Parallel - Card handlers + Device handlers) +Wave 3: Task 4 + Task 5 (Parallel - Register routes) +Wave 4: Task 6 (Integration tests) +``` + +## Critical Dependencies +- Task 1 BLOCKS Task 2, 3 +- Task 2 BLOCKS Task 4 +- Task 3 BLOCKS Task 5 +- Task 4, 5 BLOCK Task 6 + +## Key Files +- `internal/bootstrap/handlers.go` - Dependency injection for handlers +- `internal/handler/admin/iot_card.go` - Card handler (6 new methods) +- `internal/handler/admin/device.go` - Device handler (7 new methods) +- `internal/routes/iot_card.go` - Card routes registration +- `internal/routes/device.go` - Device routes registration +- `internal/gateway/flow_card.go` - Gateway card methods +- `internal/gateway/device.go` - Gateway device methods +- `tests/integration/iot_card_gateway_test.go` - Card integration tests +- `tests/integration/device_gateway_test.go` - Device integration tests + +## API Design Principles +1. Simple passthrough - no additional business logic +2. Permission validation: Query DB to confirm ownership before calling Gateway +3. Error handling: Use `errors.New(errors.CodeNotFound, "卡不存在或无权限访问")` +4. Tags: Use `["IoT卡管理"]` for cards, `["设备管理"]` for devices + +## Route Patterns +**Card routes** (base: `/api/admin/iot-cards`): +- GET `/:iccid/gateway-status` +- GET `/:iccid/gateway-flow` +- GET `/:iccid/gateway-realname` +- GET `/:iccid/realname-link` +- POST `/:iccid/stop` +- POST `/:iccid/start` + +**Device routes** (base: `/api/admin/devices`): +- GET `/by-imei/:imei/gateway-info` +- GET `/by-imei/:imei/gateway-slots` +- PUT `/by-imei/:imei/speed-limit` +- PUT `/by-imei/:imei/wifi` +- POST `/by-imei/:imei/switch-card` +- POST `/by-imei/:imei/reboot` +- POST `/by-imei/:imei/reset` + +## Verification Commands +```bash +# Build check +go build ./cmd/api + +# OpenAPI docs generation +go run cmd/gendocs/main.go + +# Integration tests +source .env.local && go test -v ./tests/integration/... -run TestGateway +``` + +## Important Notes +- Do NOT modify Gateway Client itself +- Do NOT add extra business logic (simple passthrough only) +- Do NOT add async task processing +- Do NOT add caching layer +- All handlers must validate permissions first before calling Gateway diff --git a/internal/bootstrap/handlers.go b/internal/bootstrap/handlers.go index 0bb4425..1a8a7fa 100644 --- a/internal/bootstrap/handlers.go +++ b/internal/bootstrap/handlers.go @@ -30,9 +30,9 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers { EnterpriseDeviceH5: h5.NewEnterpriseDeviceHandler(svc.EnterpriseDevice), Authorization: admin.NewAuthorizationHandler(svc.Authorization), MyCommission: admin.NewMyCommissionHandler(svc.MyCommission), - IotCard: admin.NewIotCardHandler(svc.IotCard), + IotCard: admin.NewIotCardHandler(svc.IotCard, deps.GatewayClient), IotCardImport: admin.NewIotCardImportHandler(svc.IotCardImport), - Device: admin.NewDeviceHandler(svc.Device), + Device: admin.NewDeviceHandler(svc.Device, deps.GatewayClient), DeviceImport: admin.NewDeviceImportHandler(svc.DeviceImport), AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(svc.AssetAllocationRecord), Storage: admin.NewStorageHandler(deps.StorageService), diff --git a/internal/handler/admin/device.go b/internal/handler/admin/device.go index 4bc3e04..0184878 100644 --- a/internal/handler/admin/device.go +++ b/internal/handler/admin/device.go @@ -5,6 +5,7 @@ import ( "github.com/gofiber/fiber/v2" + "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/internal/model/dto" deviceService "github.com/break/junhong_cmp_fiber/internal/service/device" "github.com/break/junhong_cmp_fiber/pkg/constants" @@ -14,12 +15,14 @@ import ( ) type DeviceHandler struct { - service *deviceService.Service + service *deviceService.Service + gatewayClient *gateway.Client } -func NewDeviceHandler(service *deviceService.Service) *DeviceHandler { +func NewDeviceHandler(service *deviceService.Service, gatewayClient *gateway.Client) *DeviceHandler { return &DeviceHandler{ - service: service, + service: service, + gatewayClient: gatewayClient, } } diff --git a/internal/handler/admin/iot_card.go b/internal/handler/admin/iot_card.go index a040a4f..de61846 100644 --- a/internal/handler/admin/iot_card.go +++ b/internal/handler/admin/iot_card.go @@ -3,6 +3,7 @@ package admin import ( "github.com/gofiber/fiber/v2" + "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/internal/model/dto" iotCardService "github.com/break/junhong_cmp_fiber/internal/service/iot_card" "github.com/break/junhong_cmp_fiber/pkg/constants" @@ -12,11 +13,15 @@ import ( ) type IotCardHandler struct { - service *iotCardService.Service + service *iotCardService.Service + gatewayClient *gateway.Client } -func NewIotCardHandler(service *iotCardService.Service) *IotCardHandler { - return &IotCardHandler{service: service} +func NewIotCardHandler(service *iotCardService.Service, gatewayClient *gateway.Client) *IotCardHandler { + return &IotCardHandler{ + service: service, + gatewayClient: gatewayClient, + } } func (h *IotCardHandler) ListStandalone(c *fiber.Ctx) error { diff --git a/pkg/openapi/handlers.go b/pkg/openapi/handlers.go index b213eb4..ceffba4 100644 --- a/pkg/openapi/handlers.go +++ b/pkg/openapi/handlers.go @@ -27,9 +27,9 @@ func BuildDocHandlers() *bootstrap.Handlers { EnterpriseDeviceH5: h5.NewEnterpriseDeviceHandler(nil), Authorization: admin.NewAuthorizationHandler(nil), MyCommission: admin.NewMyCommissionHandler(nil), - IotCard: admin.NewIotCardHandler(nil), + IotCard: admin.NewIotCardHandler(nil, nil), IotCardImport: admin.NewIotCardImportHandler(nil), - Device: admin.NewDeviceHandler(nil), + Device: admin.NewDeviceHandler(nil, nil), DeviceImport: admin.NewDeviceImportHandler(nil), AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(nil), Storage: admin.NewStorageHandler(nil), diff --git a/tests/integration/account_role_test.go b/tests/integration/account_role_test.go index 8054716..2cb3d2b 100644 --- a/tests/integration/account_role_test.go +++ b/tests/integration/account_role_test.go @@ -8,6 +8,7 @@ import ( "github.com/break/junhong_cmp_fiber/internal/model" accountService "github.com/break/junhong_cmp_fiber/internal/service/account" + accountAuditService "github.com/break/junhong_cmp_fiber/internal/service/account_audit" postgresStore "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/tests/testutils/integ" @@ -21,7 +22,11 @@ func TestAccountRoleAssociation_AssignRoles(t *testing.T) { accountStore := postgresStore.NewAccountStore(env.TX, env.Redis) roleStore := postgresStore.NewRoleStore(env.TX) accountRoleStore := postgresStore.NewAccountRoleStore(env.TX, env.Redis) - accService := accountService.New(accountStore, roleStore, accountRoleStore) + shopStore := postgresStore.NewShopStore(env.TX, env.Redis) + enterpriseStore := postgresStore.NewEnterpriseStore(env.TX, env.Redis) + auditLogStore := postgresStore.NewAccountOperationLogStore(env.TX) + auditService := accountAuditService.NewService(auditLogStore) + accService := accountService.New(accountStore, roleStore, accountRoleStore, shopStore, enterpriseStore, auditService) // 获取超级管理员上下文 userCtx := env.GetSuperAdminContext() @@ -213,7 +218,11 @@ func TestAccountRoleAssociation_SoftDelete(t *testing.T) { accountStore := postgresStore.NewAccountStore(env.TX, env.Redis) roleStore := postgresStore.NewRoleStore(env.TX) accountRoleStore := postgresStore.NewAccountRoleStore(env.TX, env.Redis) - accService := accountService.New(accountStore, roleStore, accountRoleStore) + shopStore := postgresStore.NewShopStore(env.TX, env.Redis) + enterpriseStore := postgresStore.NewEnterpriseStore(env.TX, env.Redis) + auditLogStore := postgresStore.NewAccountOperationLogStore(env.TX) + auditService := accountAuditService.NewService(auditLogStore) + accService := accountService.New(accountStore, roleStore, accountRoleStore, shopStore, enterpriseStore, auditService) // 获取超级管理员上下文 userCtx := env.GetSuperAdminContext()