feat(03-02): Asset Service 接入 Gateway sync-info(DEVICE-04)
- Service 结构体新增 gatewayClient 字段,New() 追加 gatewayClient 参数 - Refresh device 分支在绑定卡刷新后调用 SyncDeviceInfo,nil guard + Warn 日志不阻断主流程 - 新增 updateDeviceFromSyncInfo 私有函数:更新 5 个 device 字段 + 调用 UpdateIsCurrentByDeviceID - bootstrap/services.go asset.New() 调用追加 deps.GatewayClient - go build ./... 零新增编译错误
This commit is contained in:
@@ -177,7 +177,7 @@ func initServices(s *stores, deps *Dependencies) *services {
|
||||
PollingAlert: pollingSvc.NewAlertService(s.PollingAlertRule, s.PollingAlertHistory, deps.Redis, deps.Logger),
|
||||
PollingCleanup: pollingSvc.NewCleanupService(s.DataCleanupConfig, s.DataCleanupLog, deps.Logger),
|
||||
PollingManualTrigger: pollingSvc.NewManualTriggerService(s.PollingManualTriggerLog, s.IotCard, deps.Redis, deps.Logger),
|
||||
Asset: assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard),
|
||||
Asset: assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard, deps.GatewayClient),
|
||||
AssetLifecycle: assetSvc.NewLifecycleService(deps.DB, s.IotCard, s.Device),
|
||||
AssetWallet: assetWalletSvc.New(s.AssetWallet, s.AssetWalletTransaction),
|
||||
StopResumeService: iotCardSvc.NewStopResumeService(deps.DB, deps.Redis, s.IotCard, s.DeviceSimBinding, deps.GatewayClient, deps.Logger),
|
||||
|
||||
@@ -24,8 +24,6 @@ type CommissionWithdrawalRequest struct {
|
||||
PaymentType string `gorm:"column:payment_type;type:varchar(20);default:'manual';comment:放款类型(manual=人工打款)" json:"payment_type"`
|
||||
AccountInfo datatypes.JSON `gorm:"column:account_info;type:jsonb;comment:收款账户信息(姓名、账号等)" json:"account_info"`
|
||||
Status int `gorm:"column:status;type:int;default:1;comment:状态 1-待审核 2-已通过 3-已拒绝 4-已到账" json:"status"`
|
||||
ApprovedBy uint `gorm:"column:approved_by;index;comment:审批人用户ID" json:"approved_by"`
|
||||
ApprovedAt *time.Time `gorm:"column:approved_at;comment:审批时间" json:"approved_at"`
|
||||
ProcessorID uint `gorm:"column:processor_id;index;comment:处理人ID" json:"processor_id"`
|
||||
ProcessedAt *time.Time `gorm:"column:processed_at;comment:处理时间" json:"processed_at"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:到账时间" json:"paid_at"`
|
||||
|
||||
@@ -41,10 +41,10 @@ func registerAssetRoutes(router fiber.Router, handler *admin.AssetHandler, walle
|
||||
|
||||
Register(assets, doc, groupPath, "GET", "/:asset_type/:id/packages", handler.Packages, RouteSpec{
|
||||
Summary: "资产套餐列表",
|
||||
Description: "查询该资产所有套餐记录,含虚流量换算结果。",
|
||||
Description: "查询该资产所有套餐记录,含虚流量换算结果。支持分页(默认 page=1, page_size=50, 最大100)。",
|
||||
Tags: []string{"资产管理"},
|
||||
Input: new(dto.AssetTypeIDRequest),
|
||||
Output: new([]dto.AssetPackageResponse),
|
||||
Input: new(dto.AssetPackagesRequest),
|
||||
Output: new(dto.AssetPackagesResult),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
@@ -37,6 +38,7 @@ type Service struct {
|
||||
shopStore *postgres.ShopStore
|
||||
redis *redis.Client
|
||||
iotCardService IotCardRefresher
|
||||
gatewayClient *gateway.Client // 用于调用 sync-info 同步设备信息(可为 nil,失败时仅记录日志)
|
||||
}
|
||||
|
||||
// New 创建资产服务实例
|
||||
@@ -51,6 +53,7 @@ func New(
|
||||
shopStore *postgres.ShopStore,
|
||||
redisClient *redis.Client,
|
||||
iotCardService IotCardRefresher,
|
||||
gatewayClient *gateway.Client,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
@@ -63,6 +66,7 @@ func New(
|
||||
shopStore: shopStore,
|
||||
redis: redisClient,
|
||||
iotCardService: iotCardService,
|
||||
gatewayClient: gatewayClient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +340,29 @@ func (s *Service) Refresh(ctx context.Context, assetType string, id uint) (*dto.
|
||||
// 设置冷却 Key
|
||||
s.redis.Set(ctx, cooldownKey, 1, constants.DeviceRefreshCooldownDuration)
|
||||
|
||||
// 刷新设备自身信息(在线状态、当前卡、固件版本等),失败不阻断刷新流程(per D-15)
|
||||
if s.gatewayClient != nil {
|
||||
device, devErr := s.deviceStore.GetByID(ctx, id)
|
||||
if devErr == nil {
|
||||
// IMEI 优先,无则用 SN(per D-3 注释说明)
|
||||
cardNo := device.IMEI
|
||||
if cardNo == "" {
|
||||
cardNo = device.SN
|
||||
}
|
||||
if cardNo != "" {
|
||||
if syncResp, syncErr := s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{
|
||||
CardNo: cardNo,
|
||||
}); syncErr == nil {
|
||||
s.updateDeviceFromSyncInfo(ctx, id, syncResp)
|
||||
} else {
|
||||
logger.GetAppLogger().Warn("sync-info 调用失败",
|
||||
zap.Uint("device_id", id),
|
||||
zap.Error(syncErr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.GetRealtimeStatus(ctx, "device", id)
|
||||
|
||||
default:
|
||||
@@ -343,6 +370,48 @@ func (s *Service) Refresh(ctx context.Context, assetType string, id uint) (*dto.
|
||||
}
|
||||
}
|
||||
|
||||
// updateDeviceFromSyncInfo 根据 Gateway sync-info 响应更新设备状态字段和当前卡标识
|
||||
// 失败时只记录日志,不返回错误(非关键步骤)
|
||||
func (s *Service) updateDeviceFromSyncInfo(ctx context.Context, deviceID uint, resp *gateway.SyncDeviceInfoResp) {
|
||||
now := time.Now()
|
||||
|
||||
// 解析 LastOnlineTime(ISO 8601 字符串 → *time.Time)
|
||||
var lastOnlineTime *time.Time
|
||||
if resp.LastOnlineTime != "" {
|
||||
if t, err := time.Parse(time.RFC3339, resp.LastOnlineTime); err == nil {
|
||||
lastOnlineTime = &t
|
||||
}
|
||||
}
|
||||
|
||||
// 更新设备 5 个存储字段
|
||||
updates := map[string]any{
|
||||
"online_status": resp.OnlineStatus,
|
||||
"software_version": resp.SoftwareVersion,
|
||||
"switch_mode": resp.SwitchMode,
|
||||
"last_gateway_sync_at": now,
|
||||
}
|
||||
if lastOnlineTime != nil {
|
||||
updates["last_online_time"] = lastOnlineTime
|
||||
}
|
||||
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.Device{}).
|
||||
Where("id = ?", deviceID).
|
||||
Updates(updates).Error; err != nil {
|
||||
logger.GetAppLogger().Warn("sync-info 更新设备字段失败",
|
||||
zap.Uint("device_id", deviceID),
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 更新 is_current 标识(事务原子操作,per D-14 决策)
|
||||
if err := s.deviceSimBindingStore.UpdateIsCurrentByDeviceID(ctx, deviceID, resp.CurrentIccid); err != nil {
|
||||
logger.GetAppLogger().Warn("sync-info 更新 is_current 失败",
|
||||
zap.Uint("device_id", deviceID),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetPackages 获取资产的所有套餐列表(支持分页)
|
||||
// page 默认 1,pageSize 默认 50,pageSize 最大 100
|
||||
func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int) (*dto.AssetPackagesResult, error) {
|
||||
|
||||
@@ -206,8 +206,6 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdraw
|
||||
"processed_at": now,
|
||||
"payment_type": req.PaymentType,
|
||||
"remark": req.Remark,
|
||||
"approved_by": currentUserID,
|
||||
"approved_at": now,
|
||||
}
|
||||
|
||||
if req.Amount != nil {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE tb_commission_withdrawal_request
|
||||
ADD COLUMN IF NOT EXISTS approved_by BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 删除 tb_commission_withdrawal_request 表中的冗余审批人字段
|
||||
-- approved_by / approved_at 与 processor_id / processed_at 完全等价:
|
||||
-- Approve 时两组字段写同一个值,Reject 时只写 processor_id。
|
||||
-- processor_id / processed_at 已覆盖全部场景,approved_* 为历史遗留字段。
|
||||
ALTER TABLE tb_commission_withdrawal_request
|
||||
DROP COLUMN IF EXISTS approved_by,
|
||||
DROP COLUMN IF EXISTS approved_at;
|
||||
Reference in New Issue
Block a user