修改 Bootstrap 注入 Gateway Client 依赖到 IotCardHandler 和 DeviceHandler
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"active_plan": "/Users/break/csxjProject/junhong_cmp_fiber/.sisyphus/plans/add-gateway-admin-api.md",
|
"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": [
|
"session_ids": [
|
||||||
"ses_3e2ccb556ffeOMG11wg2q0HOzK"
|
"ses_3e254bedbffeBTwWDP2VQqDr7q"
|
||||||
],
|
],
|
||||||
"plan_name": "add-gateway-admin-api"
|
"plan_name": "add-gateway-admin-api"
|
||||||
}
|
}
|
||||||
81
.sisyphus/notepads/add-gateway-admin-api/context.md
Normal file
81
.sisyphus/notepads/add-gateway-admin-api/context.md
Normal file
@@ -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
|
||||||
75
.sisyphus/notepads/add-gateway-admin-api/learnings.md
Normal file
75
.sisyphus/notepads/add-gateway-admin-api/learnings.md
Normal file
@@ -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
|
||||||
@@ -30,9 +30,9 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
|
|||||||
EnterpriseDeviceH5: h5.NewEnterpriseDeviceHandler(svc.EnterpriseDevice),
|
EnterpriseDeviceH5: h5.NewEnterpriseDeviceHandler(svc.EnterpriseDevice),
|
||||||
Authorization: admin.NewAuthorizationHandler(svc.Authorization),
|
Authorization: admin.NewAuthorizationHandler(svc.Authorization),
|
||||||
MyCommission: admin.NewMyCommissionHandler(svc.MyCommission),
|
MyCommission: admin.NewMyCommissionHandler(svc.MyCommission),
|
||||||
IotCard: admin.NewIotCardHandler(svc.IotCard),
|
IotCard: admin.NewIotCardHandler(svc.IotCard, deps.GatewayClient),
|
||||||
IotCardImport: admin.NewIotCardImportHandler(svc.IotCardImport),
|
IotCardImport: admin.NewIotCardImportHandler(svc.IotCardImport),
|
||||||
Device: admin.NewDeviceHandler(svc.Device),
|
Device: admin.NewDeviceHandler(svc.Device, deps.GatewayClient),
|
||||||
DeviceImport: admin.NewDeviceImportHandler(svc.DeviceImport),
|
DeviceImport: admin.NewDeviceImportHandler(svc.DeviceImport),
|
||||||
AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(svc.AssetAllocationRecord),
|
AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(svc.AssetAllocationRecord),
|
||||||
Storage: admin.NewStorageHandler(deps.StorageService),
|
Storage: admin.NewStorageHandler(deps.StorageService),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||||
deviceService "github.com/break/junhong_cmp_fiber/internal/service/device"
|
deviceService "github.com/break/junhong_cmp_fiber/internal/service/device"
|
||||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||||
@@ -14,12 +15,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DeviceHandler struct {
|
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{
|
return &DeviceHandler{
|
||||||
service: service,
|
service: service,
|
||||||
|
gatewayClient: gatewayClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package admin
|
|||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||||
iotCardService "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
|
iotCardService "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
|
||||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||||
@@ -12,11 +13,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IotCardHandler struct {
|
type IotCardHandler struct {
|
||||||
service *iotCardService.Service
|
service *iotCardService.Service
|
||||||
|
gatewayClient *gateway.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIotCardHandler(service *iotCardService.Service) *IotCardHandler {
|
func NewIotCardHandler(service *iotCardService.Service, gatewayClient *gateway.Client) *IotCardHandler {
|
||||||
return &IotCardHandler{service: service}
|
return &IotCardHandler{
|
||||||
|
service: service,
|
||||||
|
gatewayClient: gatewayClient,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *IotCardHandler) ListStandalone(c *fiber.Ctx) error {
|
func (h *IotCardHandler) ListStandalone(c *fiber.Ctx) error {
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ func BuildDocHandlers() *bootstrap.Handlers {
|
|||||||
EnterpriseDeviceH5: h5.NewEnterpriseDeviceHandler(nil),
|
EnterpriseDeviceH5: h5.NewEnterpriseDeviceHandler(nil),
|
||||||
Authorization: admin.NewAuthorizationHandler(nil),
|
Authorization: admin.NewAuthorizationHandler(nil),
|
||||||
MyCommission: admin.NewMyCommissionHandler(nil),
|
MyCommission: admin.NewMyCommissionHandler(nil),
|
||||||
IotCard: admin.NewIotCardHandler(nil),
|
IotCard: admin.NewIotCardHandler(nil, nil),
|
||||||
IotCardImport: admin.NewIotCardImportHandler(nil),
|
IotCardImport: admin.NewIotCardImportHandler(nil),
|
||||||
Device: admin.NewDeviceHandler(nil),
|
Device: admin.NewDeviceHandler(nil, nil),
|
||||||
DeviceImport: admin.NewDeviceImportHandler(nil),
|
DeviceImport: admin.NewDeviceImportHandler(nil),
|
||||||
AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(nil),
|
AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(nil),
|
||||||
Storage: admin.NewStorageHandler(nil),
|
Storage: admin.NewStorageHandler(nil),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
accountService "github.com/break/junhong_cmp_fiber/internal/service/account"
|
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"
|
postgresStore "github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||||
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
|
"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)
|
accountStore := postgresStore.NewAccountStore(env.TX, env.Redis)
|
||||||
roleStore := postgresStore.NewRoleStore(env.TX)
|
roleStore := postgresStore.NewRoleStore(env.TX)
|
||||||
accountRoleStore := postgresStore.NewAccountRoleStore(env.TX, env.Redis)
|
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()
|
userCtx := env.GetSuperAdminContext()
|
||||||
@@ -213,7 +218,11 @@ func TestAccountRoleAssociation_SoftDelete(t *testing.T) {
|
|||||||
accountStore := postgresStore.NewAccountStore(env.TX, env.Redis)
|
accountStore := postgresStore.NewAccountStore(env.TX, env.Redis)
|
||||||
roleStore := postgresStore.NewRoleStore(env.TX)
|
roleStore := postgresStore.NewRoleStore(env.TX)
|
||||||
accountRoleStore := postgresStore.NewAccountRoleStore(env.TX, env.Redis)
|
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()
|
userCtx := env.GetSuperAdminContext()
|
||||||
|
|||||||
Reference in New Issue
Block a user