feat: 在 IoT 卡和设备列表响应中添加套餐系列名称字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m2s

主要变更:
- 在 StandaloneIotCardResponse 和 DeviceResponse 中添加 series_name 字段
- 在 iot_card 和 device service 中添加 loadSeriesNames 方法批量加载系列名称
- 更新相关方法以支持 series_name 的填充

其他变更:
- 新增 OpenSpec 测试生成和共识锁定 skill
- 新增 MCP 配置文件
- 更新 CLAUDE.md 项目规范文档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 15:28:41 +08:00
parent dc84cef2ce
commit 8ab5ebc3af
9 changed files with 1149 additions and 12 deletions

View File

@@ -32,6 +32,7 @@ type DeviceResponse struct {
StatusName string `json:"status_name" description:"状态名称"`
BoundCardCount int `json:"bound_card_count" description:"已绑定卡数量"`
SeriesID *uint `json:"series_id,omitempty" description:"套餐系列ID"`
SeriesName string `json:"series_name,omitempty" description:"套餐系列名称"`
FirstCommissionPaid bool `json:"first_commission_paid" description:"一次性佣金是否已发放"`
AccumulatedRecharge int64 `json:"accumulated_recharge" description:"累计充值金额(分)"`
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间"`

View File

@@ -41,6 +41,7 @@ type StandaloneIotCardResponse struct {
NetworkStatus int `json:"network_status" description:"网络状态 (0:停机, 1:开机)"`
DataUsageMB int64 `json:"data_usage_mb" description:"累计流量使用(MB)"`
SeriesID *uint `json:"series_id,omitempty" description:"套餐系列ID"`
SeriesName string `json:"series_name,omitempty" description:"套餐系列名称"`
FirstCommissionPaid bool `json:"first_commission_paid" description:"一次性佣金是否已发放"`
AccumulatedRecharge int64 `json:"accumulated_recharge" description:"累计充值金额(分)"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`

View File

@@ -102,6 +102,7 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
}
shopMap := s.loadShopData(ctx, devices)
seriesMap := s.loadSeriesNames(ctx, devices)
bindingCounts, err := s.getBindingCounts(ctx, s.extractDeviceIDs(devices))
if err != nil {
return nil, err
@@ -109,7 +110,7 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
list := make([]*dto.DeviceResponse, 0, len(devices))
for _, device := range devices {
item := s.toDeviceResponse(device, shopMap, bindingCounts)
item := s.toDeviceResponse(device, shopMap, seriesMap, bindingCounts)
list = append(list, item)
}
@@ -138,12 +139,13 @@ func (s *Service) Get(ctx context.Context, id uint) (*dto.DeviceResponse, error)
}
shopMap := s.loadShopData(ctx, []*model.Device{device})
seriesMap := s.loadSeriesNames(ctx, []*model.Device{device})
bindingCounts, err := s.getBindingCounts(ctx, []uint{device.ID})
if err != nil {
return nil, err
}
return s.toDeviceResponse(device, shopMap, bindingCounts), nil
return s.toDeviceResponse(device, shopMap, seriesMap, bindingCounts), nil
}
// GetByDeviceNo 通过设备号获取设备详情
@@ -157,12 +159,13 @@ func (s *Service) GetByDeviceNo(ctx context.Context, deviceNo string) (*dto.Devi
}
shopMap := s.loadShopData(ctx, []*model.Device{device})
seriesMap := s.loadSeriesNames(ctx, []*model.Device{device})
bindingCounts, err := s.getBindingCounts(ctx, []uint{device.ID})
if err != nil {
return nil, err
}
return s.toDeviceResponse(device, shopMap, bindingCounts), nil
return s.toDeviceResponse(device, shopMap, seriesMap, bindingCounts), nil
}
func (s *Service) Delete(ctx context.Context, id uint) error {
@@ -432,6 +435,29 @@ func (s *Service) loadShopData(ctx context.Context, devices []*model.Device) map
return shopMap
}
func (s *Service) loadSeriesNames(ctx context.Context, devices []*model.Device) map[uint]string {
seriesIDs := make([]uint, 0)
seriesIDSet := make(map[uint]bool)
for _, device := range devices {
if device.SeriesID != nil && *device.SeriesID > 0 && !seriesIDSet[*device.SeriesID] {
seriesIDs = append(seriesIDs, *device.SeriesID)
seriesIDSet[*device.SeriesID] = true
}
}
seriesMap := make(map[uint]string)
if len(seriesIDs) > 0 {
var seriesList []model.PackageSeries
s.db.WithContext(ctx).Where("id IN ?", seriesIDs).Find(&seriesList)
for _, series := range seriesList {
seriesMap[series.ID] = series.SeriesName
}
}
return seriesMap
}
func (s *Service) getBindingCounts(ctx context.Context, deviceIDs []uint) (map[uint]int64, error) {
result := make(map[uint]int64)
if len(deviceIDs) == 0 {
@@ -458,7 +484,7 @@ func (s *Service) extractDeviceIDs(devices []*model.Device) []uint {
return ids
}
func (s *Service) toDeviceResponse(device *model.Device, shopMap map[uint]string, bindingCounts map[uint]int64) *dto.DeviceResponse {
func (s *Service) toDeviceResponse(device *model.Device, shopMap map[uint]string, seriesMap map[uint]string, bindingCounts map[uint]int64) *dto.DeviceResponse {
resp := &dto.DeviceResponse{
ID: device.ID,
DeviceNo: device.DeviceNo,
@@ -484,6 +510,10 @@ func (s *Service) toDeviceResponse(device *model.Device, shopMap map[uint]string
resp.ShopName = shopMap[*device.ShopID]
}
if device.SeriesID != nil && *device.SeriesID > 0 {
resp.SeriesName = seriesMap[*device.SeriesID]
}
return resp
}

View File

@@ -109,10 +109,11 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
}
shopMap := s.loadShopNames(ctx, cards)
seriesMap := s.loadSeriesNames(ctx, cards)
list := make([]*dto.StandaloneIotCardResponse, 0, len(cards))
for _, card := range cards {
item := s.toStandaloneResponse(card, shopMap)
item := s.toStandaloneResponse(card, shopMap, seriesMap)
list = append(list, item)
}
@@ -141,7 +142,8 @@ func (s *Service) GetByICCID(ctx context.Context, iccid string) (*dto.IotCardDet
}
shopMap := s.loadShopNames(ctx, []*model.IotCard{card})
standaloneResp := s.toStandaloneResponse(card, shopMap)
seriesMap := s.loadSeriesNames(ctx, []*model.IotCard{card})
standaloneResp := s.toStandaloneResponse(card, shopMap, seriesMap)
return &dto.IotCardDetailResponse{
StandaloneIotCardResponse: *standaloneResp,
@@ -171,7 +173,30 @@ func (s *Service) loadShopNames(ctx context.Context, cards []*model.IotCard) map
return shopMap
}
func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]string) *dto.StandaloneIotCardResponse {
func (s *Service) loadSeriesNames(ctx context.Context, cards []*model.IotCard) map[uint]string {
seriesIDs := make([]uint, 0)
seriesIDSet := make(map[uint]bool)
for _, card := range cards {
if card.SeriesID != nil && *card.SeriesID > 0 && !seriesIDSet[*card.SeriesID] {
seriesIDs = append(seriesIDs, *card.SeriesID)
seriesIDSet[*card.SeriesID] = true
}
}
seriesMap := make(map[uint]string)
if len(seriesIDs) > 0 {
var seriesList []model.PackageSeries
s.db.WithContext(ctx).Where("id IN ?", seriesIDs).Find(&seriesList)
for _, series := range seriesList {
seriesMap[series.ID] = series.SeriesName
}
}
return seriesMap
}
func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]string, seriesMap map[uint]string) *dto.StandaloneIotCardResponse {
resp := &dto.StandaloneIotCardResponse{
ID: card.ID,
ICCID: card.ICCID,
@@ -203,6 +228,10 @@ func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]str
resp.ShopName = shopMap[*card.ShopID]
}
if card.SeriesID != nil && *card.SeriesID > 0 {
resp.SeriesName = seriesMap[*card.SeriesID]
}
return resp
}