init: web_hgdjc_server

This commit is contained in:
2026-11-07 10:46:18 +08:00
commit b2ff6ac8ff
707 changed files with 147645 additions and 0 deletions
+343
View File
@@ -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
}