package admin import ( "context" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" "github.com/qianfree/team-api/internal/dao" "github.com/qianfree/team-api/internal/logic/common" do "github.com/qianfree/team-api/internal/model/do" v1 "github.com/qianfree/team-api/api/admin/v1" "github.com/qianfree/team-api/internal/utility/export" ) // ListPromoCodes 获取优惠码列表 func (s *sAdmin) ListPromoCodes(ctx context.Context, req *v1.PromoCodeListReq) (*v1.PromoCodeListRes, error) { page, pageSize := common.NormalizePagination(req.Page, req.PageSize) var total int items := make([]*v1.PromoCodeItem, 0) err := dao.OrdPromoCodes.Ctx(ctx). OrderDesc("created_at"). Page(page, pageSize). ScanAndCount(&items, &total, false) if err != nil { return nil, err } return &v1.PromoCodeListRes{ List: items, Total: total, Page: page, PageSize: pageSize, }, nil } // CreatePromoCode 创建优惠码 func (s *sAdmin) CreatePromoCode(ctx context.Context, req *v1.PromoCodeCreateReq) (*v1.PromoCodeCreateRes, error) { // 字段白名单:req.Data 是原始 map,直接 Insert 会把 used_count / id / created_at 等 // 非预期字段一并写入。此处仅放行显式列出的列(与 UpdatePromoCode 的白名单口径一致), // 杜绝请求体越权注入内部字段。 data := do.OrdPromoCodes{} for k, v := range req.Data { switch k { case "code": data.Code = v case "name": data.Name = v case "type": data.Type = v case "discount_value": data.DiscountValue = v case "min_amount": data.MinAmount = v case "max_discount": data.MaxDiscount = v case "total_count": data.TotalCount = v case "per_user_limit": data.PerUserLimit = v case "status": data.Status = v case "valid_from": data.ValidFrom = gconv.GTime(v) case "valid_to": data.ValidTo = gconv.GTime(v) case "plan_ids": data.PlanIds = gconv.Int64s(v) } } if data.Code == nil || gconv.String(data.Code) == "" { return nil, common.NewBadRequestError("优惠码 code 不能为空") } result, err := dao.OrdPromoCodes.Ctx(ctx).Insert(data) if err != nil { return nil, err } id, err := result.LastInsertId() if err != nil { return nil, err } return &v1.PromoCodeCreateRes{ID: id}, nil } // UpdatePromoCode 更新优惠码 func (s *sAdmin) UpdatePromoCode(ctx context.Context, req *v1.PromoCodeUpdateReq) (*v1.PromoCodeUpdateRes, error) { count, err := dao.OrdPromoCodes.Ctx(ctx).Where("id", req.Id).Count() if err != nil { return nil, err } if count == 0 { return nil, common.NewNotFoundError("优惠码") } data := do.OrdPromoCodes{} allowedFields := map[string]bool{ "name": true, "type": true, "discount_value": true, "min_amount": true, "total_count": true, "max_discount": true, "status": true, "starts_at": true, "expires_at": true, } for k, v := range req.Update { if !allowedFields[k] { continue } switch k { case "name": data.Name = v.(string) case "type": data.Type = v.(string) case "discount_value": data.DiscountValue = v case "min_amount": data.MinAmount = v case "total_count": data.TotalCount = v case "max_discount": data.MaxDiscount = v case "status": data.Status = v.(string) } } _, err = dao.OrdPromoCodes.Ctx(ctx). Where("id", req.Id). Data(data). Update() if err != nil { return nil, err } return &v1.PromoCodeUpdateRes{}, nil } // GetPromoCodeUsages 获取优惠码使用记录 func (s *sAdmin) GetPromoCodeUsages(ctx context.Context, req *v1.PromoCodeUsagesReq) (*v1.PromoCodeUsagesRes, error) { page, pageSize := common.NormalizePagination(req.Page, req.PageSize) var total int items := make([]*v1.PromoCodeUsageItem, 0) err := dao.OrdPromoCodeUsages.Ctx(ctx). Where("promo_code_id", req.Id). OrderDesc("created_at"). Page(page, pageSize). ScanAndCount(&items, &total, false) if err != nil { return nil, err } return &v1.PromoCodeUsagesRes{ List: items, Total: total, Page: page, PageSize: pageSize, }, nil } // ExportPromoCodes exports promo code list to CSV or Excel. func (s *sAdmin) ExportPromoCodes(ctx context.Context, req *v1.PromoCodeExportReq) (*v1.PromoCodeExportRes, error) { columns := []export.Column{ {Field: "id", Header: "ID"}, {Field: "code", Header: "优惠码"}, {Field: "name", Header: "名称"}, {Field: "type", Header: "类型"}, {Field: "discount_value", Header: "折扣值"}, {Field: "min_amount", Header: "最低金额"}, {Field: "total_count", Header: "总数"}, {Field: "used_count", Header: "已用数"}, {Field: "status", Header: "状态"}, {Field: "created_at", Header: "创建时间"}, } config := export.Config{ Format: req.Format, Filename: "优惠码_" + gtime.Now().Format("Ymd_His"), Columns: columns, } promoFields := "id, code, name, type, discount_value, min_amount, total_count, used_count, status, created_at" return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) { offset := 0 for { var batch []struct { Id int64 `json:"id"` Code string `json:"code"` Name string `json:"name"` Type string `json:"type"` DiscountValue float64 `json:"discount_value"` MinAmount float64 `json:"min_amount"` TotalCount int `json:"total_count"` UsedCount int `json:"used_count"` Status string `json:"status"` CreatedAt *gtime.Time `json:"created_at"` } if err := dao.OrdPromoCodes.Ctx(ctx).Fields(promoFields).OrderDesc("created_at").Limit(1000).Offset(offset).Scan(&batch); err != nil { return } for _, item := range batch { if !yield(map[string]any{ "id": item.Id, "code": item.Code, "name": item.Name, "type": item.Type, "discount_value": item.DiscountValue, "min_amount": item.MinAmount, "total_count": item.TotalCount, "used_count": item.UsedCount, "status": item.Status, "created_at": item.CreatedAt.String(), }) { return } } if len(batch) < 1000 { break } offset += 1000 } }) }