This commit is contained in:
@@ -204,15 +204,16 @@ func createFiberApp(cfg *config.Config, appLogger *zap.Logger) *fiber.App {
|
|||||||
registerTimeParserCompat()
|
registerTimeParserCompat()
|
||||||
|
|
||||||
return fiber.New(fiber.Config{
|
return fiber.New(fiber.Config{
|
||||||
AppName: "君鸿卡管系统 v1.0.0",
|
AppName: "君鸿卡管系统 v1.0.0",
|
||||||
StrictRouting: true,
|
StrictRouting: true,
|
||||||
CaseSensitive: true,
|
CaseSensitive: true,
|
||||||
JSONEncoder: sonic.Marshal,
|
JSONEncoder: sonic.Marshal,
|
||||||
JSONDecoder: sonic.Unmarshal,
|
JSONDecoder: sonic.Unmarshal,
|
||||||
Prefork: cfg.Server.Prefork,
|
EnableSplittingOnParsers: true,
|
||||||
ReadTimeout: cfg.Server.ReadTimeout,
|
Prefork: cfg.Server.Prefork,
|
||||||
WriteTimeout: cfg.Server.WriteTimeout,
|
ReadTimeout: cfg.Server.ReadTimeout,
|
||||||
ErrorHandler: internalMiddleware.ErrorHandler(appLogger),
|
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:"虚拟号(模糊查询)"`
|
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:"设备名称(模糊查询)"`
|
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:已停用)"`
|
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"`
|
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:"批次号"`
|
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:"设备类型"`
|
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:"每页数量"`
|
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:已停用)"`
|
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"`
|
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"`
|
SeriesID *uint `json:"series_id" query:"series_id" description:"套餐系列ID"`
|
||||||
ICCID string `json:"iccid" query:"iccid" validate:"omitempty,max=20" maxLength:"20" description:"ICCID(模糊查询)"`
|
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:"卡接入号(模糊查询)"`
|
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 {
|
if req.Status != nil {
|
||||||
filters["status"] = *req.Status
|
filters["status"] = *req.Status
|
||||||
}
|
}
|
||||||
if req.ShopID != nil {
|
shopIDs, hasShopIDs := normalizeShopIDs(req.ShopIDs)
|
||||||
filters["shop_id"] = req.ShopID
|
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 != "" {
|
if req.BatchNo != "" {
|
||||||
filters["batch_no"] = req.BatchNo
|
filters["batch_no"] = req.BatchNo
|
||||||
@@ -141,6 +148,25 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
|
|||||||
}, nil
|
}, 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 获取设备详情
|
// Get 获取设备详情
|
||||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.DeviceResponse, error) {
|
func (s *Service) Get(ctx context.Context, id uint) (*dto.DeviceResponse, error) {
|
||||||
device, err := s.deviceStore.GetByID(ctx, id)
|
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 {
|
if req.CarrierID != nil {
|
||||||
filters["carrier_id"] = *req.CarrierID
|
filters["carrier_id"] = *req.CarrierID
|
||||||
}
|
}
|
||||||
if req.ShopID != nil {
|
shopIDs, hasShopIDs := normalizeShopIDs(req.ShopIDs)
|
||||||
filters["shop_id"] = *req.ShopID
|
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 != "" {
|
if req.ICCID != "" {
|
||||||
filters["iccid"] = req.ICCID
|
filters["iccid"] = req.ICCID
|
||||||
@@ -200,8 +207,16 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
|
|||||||
shopID := middleware.GetShopIDFromContext(ctx)
|
shopID := middleware.GetShopIDFromContext(ctx)
|
||||||
if shopID > 0 {
|
if shopID > 0 {
|
||||||
subordinateIDs, err := s.shopStore.GetSubordinateShopIDs(ctx, shopID)
|
subordinateIDs, err := s.shopStore.GetSubordinateShopIDs(ctx, shopID)
|
||||||
if err == nil && len(subordinateIDs) > 1 {
|
if err == nil {
|
||||||
filters["subordinate_shop_ids"] = subordinateIDs
|
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
|
}, 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 获取单卡详情
|
// GetByICCID 通过 ICCID 获取单卡详情
|
||||||
func (s *Service) GetByICCID(ctx context.Context, iccid string) (*dto.IotCardDetailResponse, error) {
|
func (s *Service) GetByICCID(ctx context.Context, iccid string) (*dto.IotCardDetailResponse, error) {
|
||||||
card, err := s.iotCardStore.GetByICCID(ctx, iccid)
|
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 {
|
if status, ok := filters["status"].(int); ok && status > 0 {
|
||||||
query = query.Where("status = ?", status)
|
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 {
|
if shopID == nil {
|
||||||
query = query.Where("shop_id IS NULL")
|
query = query.Where("shop_id IS NULL")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -677,7 +677,7 @@ func (s *IotCardStore) listStandaloneParallelTwoPhase(ctx context.Context, opts
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
q := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
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)
|
q = s.applyStandaloneFilters(ctx, q, filters)
|
||||||
|
|
||||||
var ids []cardIDWithTime
|
var ids []cardIDWithTime
|
||||||
@@ -692,7 +692,7 @@ func (s *IotCardStore) listStandaloneParallelTwoPhase(ctx context.Context, opts
|
|||||||
var count int64
|
var count int64
|
||||||
if !hasCachedTotal {
|
if !hasCachedTotal {
|
||||||
countQ := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
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)
|
countQ = s.applyStandaloneFilters(ctx, countQ, filters)
|
||||||
if err := countQ.Count(&count).Error; err != nil {
|
if err := countQ.Count(&count).Error; err != nil {
|
||||||
results[idx] = shopResult{err: err}
|
results[idx] = shopResult{err: err}
|
||||||
@@ -781,8 +781,8 @@ func (s *IotCardStore) listStandaloneParallelTwoPhase(ctx context.Context, opts
|
|||||||
}
|
}
|
||||||
|
|
||||||
// applyStandaloneFilters 应用独立卡列表的通用过滤条件
|
// applyStandaloneFilters 应用独立卡列表的通用过滤条件
|
||||||
// 注意:不包含 shop_id、deleted_at 条件(由调用方控制)
|
// 注意:不包含 deleted_at 条件(由调用方控制)
|
||||||
// 也不包含 subordinate_shop_ids(仅用于路由选择,不作为查询条件)
|
// subordinate_shop_ids 仅用于路由选择,不作为查询条件
|
||||||
// is_standalone 仅在 filters["is_standalone"] 非 nil 时拼接(不传则不过滤)
|
// is_standalone 仅在 filters["is_standalone"] 非 nil 时拼接(不传则不过滤)
|
||||||
func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.DB, filters map[string]any) *gorm.DB {
|
func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.DB, filters map[string]any) *gorm.DB {
|
||||||
// is_standalone 为可选过滤条件,仅在明确传入时应用
|
// 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 {
|
if carrierID, ok := filters["carrier_id"].(uint); ok && carrierID > 0 {
|
||||||
query = query.Where("carrier_id = ?", carrierID)
|
query = query.Where("carrier_id = ?", carrierID)
|
||||||
}
|
}
|
||||||
// 并行路径下 shop_id 已由调用方设置,此处仅处理显式的 shop_id 过滤
|
// 并行路径下 shop_id 已由调用方设置,此处仅处理显式店铺筛选。
|
||||||
if shopID, ok := filters["shop_id"].(uint); ok && shopID > 0 {
|
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)
|
query = query.Where("shop_id = ?", shopID)
|
||||||
}
|
}
|
||||||
if iccid, ok := filters["iccid"].(string); ok && iccid != "" {
|
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 {
|
if parentID != nil {
|
||||||
query = query.Where("parent_id = ?", *parentID)
|
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 {
|
} else {
|
||||||
query = query.Where("parent_id IS NULL")
|
query = query.Where("parent_id IS NULL")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user