This commit is contained in:
@@ -204,15 +204,16 @@ func createFiberApp(cfg *config.Config, appLogger *zap.Logger) *fiber.App {
|
||||
registerTimeParserCompat()
|
||||
|
||||
return fiber.New(fiber.Config{
|
||||
AppName: "君鸿卡管系统 v1.0.0",
|
||||
StrictRouting: true,
|
||||
CaseSensitive: true,
|
||||
JSONEncoder: sonic.Marshal,
|
||||
JSONDecoder: sonic.Unmarshal,
|
||||
Prefork: cfg.Server.Prefork,
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
ErrorHandler: internalMiddleware.ErrorHandler(appLogger),
|
||||
AppName: "君鸿卡管系统 v1.0.0",
|
||||
StrictRouting: true,
|
||||
CaseSensitive: true,
|
||||
JSONEncoder: sonic.Marshal,
|
||||
JSONDecoder: sonic.Unmarshal,
|
||||
EnableSplittingOnParsers: true,
|
||||
Prefork: cfg.Server.Prefork,
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
ErrorHandler: internalMiddleware.ErrorHandler(appLogger),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ type ListDeviceRequest struct {
|
||||
VirtualNo string `json:"virtual_no" query:"virtual_no" validate:"omitempty,max=100" maxLength:"100" description:"虚拟号(模糊查询)"`
|
||||
DeviceName string `json:"device_name" query:"device_name" validate:"omitempty,max=255" maxLength:"255" description:"设备名称(模糊查询)"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)"`
|
||||
ShopID *uint `json:"shop_id" query:"shop_id" description:"店铺ID (NULL表示平台库存)"`
|
||||
ShopID *uint `json:"shop_id" query:"shop_id" description:"店铺ID(兼容旧参数,单选;NULL表示平台库存)"`
|
||||
ShopIDs []uint `json:"shop_ids" query:"shop_ids" validate:"omitempty,dive,min=1" description:"店铺ID列表(多选)"`
|
||||
SeriesID *uint `json:"series_id" query:"series_id" description:"套餐系列ID"`
|
||||
BatchNo string `json:"batch_no" query:"batch_no" validate:"omitempty,max=100" maxLength:"100" description:"批次号"`
|
||||
DeviceType string `json:"device_type" query:"device_type" validate:"omitempty,max=50" maxLength:"50" description:"设备类型"`
|
||||
|
||||
@@ -7,7 +7,8 @@ type ListStandaloneIotCardRequest struct {
|
||||
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)"`
|
||||
CarrierID *uint `json:"carrier_id" query:"carrier_id" description:"运营商ID"`
|
||||
ShopID *uint `json:"shop_id" query:"shop_id" description:"分销商ID"`
|
||||
ShopID *uint `json:"shop_id" query:"shop_id" description:"分销商ID(兼容旧参数,单选)"`
|
||||
ShopIDs []uint `json:"shop_ids" query:"shop_ids" validate:"omitempty,dive,min=1" description:"分销商ID列表(多选)"`
|
||||
SeriesID *uint `json:"series_id" query:"series_id" description:"套餐系列ID"`
|
||||
ICCID string `json:"iccid" query:"iccid" validate:"omitempty,max=20" maxLength:"20" description:"ICCID(模糊查询)"`
|
||||
MSISDN string `json:"msisdn" query:"msisdn" validate:"omitempty,max=20" maxLength:"20" description:"卡接入号(模糊查询)"`
|
||||
|
||||
@@ -93,8 +93,15 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
if req.ShopID != nil {
|
||||
filters["shop_id"] = req.ShopID
|
||||
shopIDs, hasShopIDs := normalizeShopIDs(req.ShopIDs)
|
||||
if hasShopIDs {
|
||||
filters["shop_ids"] = shopIDs
|
||||
} else if req.ShopID != nil {
|
||||
if *req.ShopID == 0 {
|
||||
filters["shop_ids"] = []uint{}
|
||||
} else {
|
||||
filters["shop_id"] = req.ShopID
|
||||
}
|
||||
}
|
||||
if req.BatchNo != "" {
|
||||
filters["batch_no"] = req.BatchNo
|
||||
@@ -141,6 +148,25 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeShopIDs(ids []uint) ([]uint, bool) {
|
||||
if len(ids) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
seen := make(map[uint]struct{}, len(ids))
|
||||
result := make([]uint, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[id]; exists {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
result = append(result, id)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
// Get 获取设备详情
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.DeviceResponse, error) {
|
||||
device, err := s.deviceStore.GetByID(ctx, id)
|
||||
|
||||
@@ -156,8 +156,15 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
|
||||
if req.CarrierID != nil {
|
||||
filters["carrier_id"] = *req.CarrierID
|
||||
}
|
||||
if req.ShopID != nil {
|
||||
filters["shop_id"] = *req.ShopID
|
||||
shopIDs, hasShopIDs := normalizeShopIDs(req.ShopIDs)
|
||||
if hasShopIDs {
|
||||
filters["shop_ids"] = shopIDs
|
||||
} else if req.ShopID != nil {
|
||||
if *req.ShopID == 0 {
|
||||
filters["shop_ids"] = []uint{}
|
||||
} else {
|
||||
filters["shop_id"] = *req.ShopID
|
||||
}
|
||||
}
|
||||
if req.ICCID != "" {
|
||||
filters["iccid"] = req.ICCID
|
||||
@@ -200,8 +207,16 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
if shopID > 0 {
|
||||
subordinateIDs, err := s.shopStore.GetSubordinateShopIDs(ctx, shopID)
|
||||
if err == nil && len(subordinateIDs) > 1 {
|
||||
filters["subordinate_shop_ids"] = subordinateIDs
|
||||
if err == nil {
|
||||
if hasShopIDs {
|
||||
scopedShopIDs := intersectShopIDs(shopIDs, subordinateIDs)
|
||||
filters["shop_ids"] = scopedShopIDs
|
||||
if len(scopedShopIDs) > 1 {
|
||||
filters["subordinate_shop_ids"] = scopedShopIDs
|
||||
}
|
||||
} else if len(subordinateIDs) > 1 {
|
||||
filters["subordinate_shop_ids"] = subordinateIDs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +244,42 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeShopIDs(ids []uint) ([]uint, bool) {
|
||||
if len(ids) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
seen := make(map[uint]struct{}, len(ids))
|
||||
result := make([]uint, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[id]; exists {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
result = append(result, id)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func intersectShopIDs(selectedIDs, allowedIDs []uint) []uint {
|
||||
if len(selectedIDs) == 0 || len(allowedIDs) == 0 {
|
||||
return []uint{}
|
||||
}
|
||||
allowed := make(map[uint]struct{}, len(allowedIDs))
|
||||
for _, id := range allowedIDs {
|
||||
allowed[id] = struct{}{}
|
||||
}
|
||||
result := make([]uint, 0, len(selectedIDs))
|
||||
for _, id := range selectedIDs {
|
||||
if _, ok := allowed[id]; ok {
|
||||
result = append(result, id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetByICCID 通过 ICCID 获取单卡详情
|
||||
func (s *Service) GetByICCID(ctx context.Context, iccid string) (*dto.IotCardDetailResponse, error) {
|
||||
card, err := s.iotCardStore.GetByICCID(ctx, iccid)
|
||||
|
||||
@@ -109,7 +109,13 @@ func (s *DeviceStore) List(ctx context.Context, opts *store.QueryOptions, filter
|
||||
if status, ok := filters["status"].(int); ok && status > 0 {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if shopID, ok := filters["shop_id"].(*uint); ok {
|
||||
if shopIDs, ok := filters["shop_ids"].([]uint); ok {
|
||||
if len(shopIDs) == 0 {
|
||||
query = query.Where("1 = 0")
|
||||
} else {
|
||||
query = query.Where("shop_id IN ?", shopIDs)
|
||||
}
|
||||
} else if shopID, ok := filters["shop_id"].(*uint); ok {
|
||||
if shopID == nil {
|
||||
query = query.Where("shop_id IS NULL")
|
||||
} else {
|
||||
|
||||
@@ -677,7 +677,7 @@ func (s *IotCardStore) listStandaloneParallelTwoPhase(ctx context.Context, opts
|
||||
defer wg.Done()
|
||||
|
||||
q := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("is_standalone = true AND deleted_at IS NULL AND shop_id = ?", sid)
|
||||
Where("deleted_at IS NULL AND shop_id = ?", sid)
|
||||
q = s.applyStandaloneFilters(ctx, q, filters)
|
||||
|
||||
var ids []cardIDWithTime
|
||||
@@ -692,7 +692,7 @@ func (s *IotCardStore) listStandaloneParallelTwoPhase(ctx context.Context, opts
|
||||
var count int64
|
||||
if !hasCachedTotal {
|
||||
countQ := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("is_standalone = true AND deleted_at IS NULL AND shop_id = ?", sid)
|
||||
Where("deleted_at IS NULL AND shop_id = ?", sid)
|
||||
countQ = s.applyStandaloneFilters(ctx, countQ, filters)
|
||||
if err := countQ.Count(&count).Error; err != nil {
|
||||
results[idx] = shopResult{err: err}
|
||||
@@ -781,8 +781,8 @@ func (s *IotCardStore) listStandaloneParallelTwoPhase(ctx context.Context, opts
|
||||
}
|
||||
|
||||
// applyStandaloneFilters 应用独立卡列表的通用过滤条件
|
||||
// 注意:不包含 shop_id、deleted_at 条件(由调用方控制)
|
||||
// 也不包含 subordinate_shop_ids(仅用于路由选择,不作为查询条件)
|
||||
// 注意:不包含 deleted_at 条件(由调用方控制)
|
||||
// subordinate_shop_ids 仅用于路由选择,不作为查询条件
|
||||
// is_standalone 仅在 filters["is_standalone"] 非 nil 时拼接(不传则不过滤)
|
||||
func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.DB, filters map[string]any) *gorm.DB {
|
||||
// is_standalone 为可选过滤条件,仅在明确传入时应用
|
||||
@@ -797,8 +797,14 @@ func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.D
|
||||
if carrierID, ok := filters["carrier_id"].(uint); ok && carrierID > 0 {
|
||||
query = query.Where("carrier_id = ?", carrierID)
|
||||
}
|
||||
// 并行路径下 shop_id 已由调用方设置,此处仅处理显式的 shop_id 过滤
|
||||
if shopID, ok := filters["shop_id"].(uint); ok && shopID > 0 {
|
||||
// 并行路径下 shop_id 已由调用方设置,此处仅处理显式店铺筛选。
|
||||
if shopIDs, ok := filters["shop_ids"].([]uint); ok {
|
||||
if len(shopIDs) == 0 {
|
||||
query = query.Where("1 = 0")
|
||||
} else {
|
||||
query = query.Where("shop_id IN ?", shopIDs)
|
||||
}
|
||||
} else if shopID, ok := filters["shop_id"].(uint); ok && shopID > 0 {
|
||||
query = query.Where("shop_id = ?", shopID)
|
||||
}
|
||||
if iccid, ok := filters["iccid"].(string); ok && iccid != "" {
|
||||
|
||||
@@ -236,6 +236,14 @@ func (s *ShopStore) ListForCascade(ctx context.Context, shopName string, parentI
|
||||
|
||||
if parentID != nil {
|
||||
query = query.Where("parent_id = ?", *parentID)
|
||||
} else if middleware.GetUserTypeFromContext(ctx) == constants.UserTypeAgent {
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
if shopID == 0 {
|
||||
query = query.Where("1 = 0")
|
||||
} else {
|
||||
// 代理账号的级联根节点是自己的店铺,避免默认根查询落到平台顶级店铺。
|
||||
query = query.Where("id = ?", shopID)
|
||||
}
|
||||
} else {
|
||||
query = query.Where("parent_id IS NULL")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user