package srv_rechargepackage import ( "context" "fmt" "hkdj-web-server/pub/common/srvdef" "hkdj-web-server/pub/models/rechargepackagemod" "strings" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) const ( defaultPageNum int64 = 1 defaultPageSize int64 = 10 maxPageSize int64 = 100 ) // ListRechargePackage 分页查询购买套餐配置。 func ListRechargePackage(ctx context.Context, req *RechargePackageListReq) (*RechargePackageListResp, *srvdef.SrvErr) { if req == nil { return nil, srvdef.New(srvdef.ErrParamError, "参数不能为空", "") } filter := buildListFilter(req) pageNum, pageSize := normalizePage(req.PageNum, req.PageSize) total, err := rechargepackagemod.CountRechargePackage(ctx, filter) if err != nil { return nil, err } resp := &RechargePackageListResp{ Total: total, List: make([]*RechargePackageItem, 0), } if total == 0 { return resp, nil } list, err := rechargepackagemod.FindManyRechargePackage(ctx, filter, options.Find(). SetSort(bson.D{ {Key: rechargepackagemod.FN.RechargePackage.PackageType, Value: 1}, {Key: rechargepackagemod.FN.RechargePackage.Sort, Value: 1}, {Key: rechargepackagemod.FN.RechargePackage.Id, Value: -1}, }). SetSkip((pageNum-1)*pageSize). SetLimit(pageSize)) if err != nil { return nil, err } for _, item := range list { resp.List = append(resp.List, rechargePackageToWebItem(item)) } return resp, nil } // ListVipPackageOptions 查询启用的会员畅享套餐选项,不分页。 func ListVipPackageOptions(ctx context.Context) ([]*VipPackageOption, *srvdef.SrvErr) { list, err := rechargepackagemod.FindManyRechargePackage(ctx, bson.M{ rechargepackagemod.FN.RechargePackage.PackageType: rechargepackagemod.PackageTypeVip, rechargepackagemod.FN.RechargePackage.Status: rechargepackagemod.StatusEnabled, }, options.Find(). SetProjection(bson.D{ {Key: rechargepackagemod.FN.RechargePackage.Id, Value: 1}, {Key: rechargepackagemod.FN.RechargePackage.Name, Value: 1}, }). SetSort(bson.D{ {Key: rechargepackagemod.FN.RechargePackage.Sort, Value: 1}, {Key: rechargepackagemod.FN.RechargePackage.Id, Value: -1}, })) if err != nil { return nil, err } result := make([]*VipPackageOption, 0, len(list)) for _, item := range list { if item == nil { continue } result = append(result, &VipPackageOption{ Id: item.Id, Name: item.Name, }) } return result, nil } // AddRechargePackage 新增购买套餐配置。 func AddRechargePackage(ctx context.Context, req *RechargePackageAddReq) *srvdef.SrvErr { if req == nil { return srvdef.New(srvdef.ErrParamError, "参数不能为空", "") } doc := &rechargepackagemod.RechargePackage{ PackageType: req.PackageType, Name: strings.TrimSpace(req.Name), Price: req.Price, OriginalPrice: req.OriginalPrice, DurationDays: req.DurationDays, BaseCoin: req.BaseCoin, GiftCoin: req.GiftCoin, Sort: req.Sort, Status: req.Status, Remark: strings.TrimSpace(req.Remark), } if doc.Status == 0 { doc.Status = rechargepackagemod.StatusEnabled } normalizePackageFields(doc) if err := validatePackage(doc); err != nil { return err } doc.SubTitle = buildSubTitle(doc) return rechargepackagemod.InsertOneRechargePackage(ctx, doc) } // UpdateRechargePackage 更新购买套餐配置。 func UpdateRechargePackage(ctx context.Context, req *RechargePackageUpdateReq) *srvdef.SrvErr { if req == nil || req.Id <= 0 { return srvdef.New(srvdef.ErrParamError, "套餐ID不能为空", "") } exist, err := rechargepackagemod.FindOneRechargePackage(ctx, bson.M{rechargepackagemod.FN.RechargePackage.Id: req.Id}) if err != nil { return err } doc := *exist applyUpdate(&doc, req) normalizePackageFields(&doc) if err := validatePackage(&doc); err != nil { return err } doc.SubTitle = buildSubTitle(&doc) now := time.Now() setDoc := bson.M{ rechargepackagemod.FN.RechargePackage.PackageType: doc.PackageType, rechargepackagemod.FN.RechargePackage.Name: doc.Name, rechargepackagemod.FN.RechargePackage.SubTitle: doc.SubTitle, rechargepackagemod.FN.RechargePackage.Price: doc.Price, rechargepackagemod.FN.RechargePackage.OriginalPrice: doc.OriginalPrice, rechargepackagemod.FN.RechargePackage.DurationDays: doc.DurationDays, rechargepackagemod.FN.RechargePackage.BaseCoin: doc.BaseCoin, rechargepackagemod.FN.RechargePackage.GiftCoin: doc.GiftCoin, rechargepackagemod.FN.RechargePackage.Sort: doc.Sort, rechargepackagemod.FN.RechargePackage.Status: doc.Status, rechargepackagemod.FN.RechargePackage.Remark: doc.Remark, rechargepackagemod.FN.RechargePackage.UpdatedAt: now, } return rechargepackagemod.UpdateOneRechargePackage(ctx, bson.M{rechargepackagemod.FN.RechargePackage.Id: req.Id}, bson.M{"$set": setDoc}, ) } // DeleteRechargePackage 删除购买套餐配置。 func DeleteRechargePackage(ctx context.Context, id int64) *srvdef.SrvErr { if id <= 0 { return srvdef.New(srvdef.ErrParamError, "套餐ID不能为空", "") } return rechargepackagemod.DeleteOneRechargePackage(ctx, bson.M{rechargepackagemod.FN.RechargePackage.Id: id}) } // SetRechargePackageStatus 单/批量设置购买套餐启停状态。 func SetRechargePackageStatus(ctx context.Context, req *RechargePackageStatusSetReq) *srvdef.SrvErr { if req == nil { return srvdef.New(srvdef.ErrParamError, "参数不能为空", "") } ids := dedupePositiveInt64Ids(req.Ids) if len(ids) == 0 { return srvdef.New(srvdef.ErrParamError, "套餐ID不能为空", "") } if !isValidStatus(req.Status) { return srvdef.New(srvdef.ErrParamError, "状态无效", "") } return rechargepackagemod.UpdateManyRechargePackage(ctx, bson.M{rechargepackagemod.FN.RechargePackage.Id: bson.M{"$in": ids}}, bson.M{"$set": bson.M{ rechargepackagemod.FN.RechargePackage.Status: req.Status, rechargepackagemod.FN.RechargePackage.UpdatedAt: time.Now(), }}, ) } func buildListFilter(req *RechargePackageListReq) bson.M { filter := bson.M{} if req.Id != nil { filter[rechargepackagemod.FN.RechargePackage.Id] = *req.Id } if req.PackageType != nil { filter[rechargepackagemod.FN.RechargePackage.PackageType] = *req.PackageType } if req.Status != nil { filter[rechargepackagemod.FN.RechargePackage.Status] = *req.Status } if strings.TrimSpace(req.Name) != "" { filter[rechargepackagemod.FN.RechargePackage.Name] = bson.M{ "$regex": primitive.Regex{Pattern: strings.TrimSpace(req.Name), Options: "i"}, } } return filter } // normalizePage 统一分页兜底,限制 pageSize 防止后台误传过大导致慢查询。 func normalizePage(pageNum, pageSize int64) (int64, int64) { if pageNum <= 0 { pageNum = defaultPageNum } if pageSize <= 0 { pageSize = defaultPageSize } if pageSize > maxPageSize { pageSize = maxPageSize } return pageNum, pageSize } // normalizePackageFields 清理不同套餐类型不生效的字段。 // 会员套餐不使用金币字段;金币套餐不使用会员有效期字段。 func normalizePackageFields(doc *rechargepackagemod.RechargePackage) { switch doc.PackageType { case rechargepackagemod.PackageTypeVip: doc.BaseCoin = 0 doc.GiftCoin = 0 case rechargepackagemod.PackageTypeCoin: doc.DurationDays = 0 } } // validatePackage 校验购买套餐配置和套餐类型字段约束。 func validatePackage(doc *rechargepackagemod.RechargePackage) *srvdef.SrvErr { if !isValidPackageType(doc.PackageType) { return srvdef.New(srvdef.ErrParamError, "套餐类型无效", "") } if strings.TrimSpace(doc.Name) == "" { return srvdef.New(srvdef.ErrParamError, "套餐标题不能为空", "") } if doc.Price <= 0 { return srvdef.New(srvdef.ErrParamError, "套餐价格必须大于0", "") } if !isValidStatus(doc.Status) { return srvdef.New(srvdef.ErrParamError, "状态无效", "") } if doc.PackageType == rechargepackagemod.PackageTypeVip && doc.DurationDays <= 0 { return srvdef.New(srvdef.ErrParamError, "会员套餐有效天数必须大于0", "") } if doc.PackageType == rechargepackagemod.PackageTypeCoin && doc.BaseCoin+doc.GiftCoin <= 0 { return srvdef.New(srvdef.ErrParamError, "金币套餐到账金币数必须大于0", "") } return nil } func isValidPackageType(packageType rechargepackagemod.PackageType) bool { return packageType == rechargepackagemod.PackageTypeVip || packageType == rechargepackagemod.PackageTypeCoin } func isValidStatus(status rechargepackagemod.Status) bool { return status == rechargepackagemod.StatusEnabled || status == rechargepackagemod.StatusDisabled } // applyUpdate 将更新请求合并到现有文档副本,再统一做类型归一化和校验。 func applyUpdate(doc *rechargepackagemod.RechargePackage, req *RechargePackageUpdateReq) { if req.PackageType != nil { doc.PackageType = *req.PackageType } if req.Name != nil { doc.Name = strings.TrimSpace(*req.Name) } if req.Price != nil { doc.Price = *req.Price } if req.OriginalPrice != nil { doc.OriginalPrice = *req.OriginalPrice } if req.DurationDays != nil { doc.DurationDays = *req.DurationDays } if req.BaseCoin != nil { doc.BaseCoin = *req.BaseCoin } if req.GiftCoin != nil { doc.GiftCoin = *req.GiftCoin } if req.Sort != nil { doc.Sort = *req.Sort } if req.Status != nil { doc.Status = *req.Status } if req.Remark != nil { doc.Remark = strings.TrimSpace(*req.Remark) } } // buildSubTitle 根据套餐类型和核心配置生成展示副标题,不由后台请求手动传入。 func buildSubTitle(doc *rechargepackagemod.RechargePackage) string { switch doc.PackageType { case rechargepackagemod.PackageTypeVip: return fmt.Sprintf("%d × 24 小时会员", doc.DurationDays) case rechargepackagemod.PackageTypeCoin: if doc.GiftCoin > 0 { return fmt.Sprintf("基础%d + 赠送%d", doc.BaseCoin, doc.GiftCoin) } return fmt.Sprintf("基础%d", doc.BaseCoin) default: return "" } } // rechargePackageToWebItem 转换后台列表展示项,并补充派生字段。 func rechargePackageToWebItem(item *rechargepackagemod.RechargePackage) *RechargePackageItem { return &RechargePackageItem{ Id: item.Id, PackageType: item.PackageType, Name: item.Name, SubTitle: item.SubTitle, Price: item.Price, OriginalPrice: item.OriginalPrice, DurationDays: item.DurationDays, DurationHours: item.DurationDays * 24, BaseCoin: item.BaseCoin, GiftCoin: item.GiftCoin, TotalCoin: item.BaseCoin + item.GiftCoin, Sort: item.Sort, Status: item.Status, Remark: item.Remark, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, } } // dedupePositiveInt64Ids 去重并过滤非法ID,批量操作只处理正整数ID。 func dedupePositiveInt64Ids(ids []int64) []int64 { result := make([]int64, 0, len(ids)) seen := make(map[int64]struct{}, len(ids)) for _, id := range ids { if id <= 0 { continue } if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} result = append(result, id) } return result }