Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
package srv_rechargepackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/models/rechargepackagemod"
|
||||
|
||||
"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
|
||||
}
|
||||
Reference in New Issue
Block a user