bindbox-game/internal/api/user/coupons_app.go
邹方成 45815bfb7d chore: 清理无用文件与优化代码结构
refactor(utils): 修复密码哈希比较逻辑错误
feat(user): 新增按状态筛选优惠券接口
docs: 添加虚拟发货与任务中心相关文档
fix(wechat): 修正Code2Session上下文传递问题
test: 补充订单折扣与积分转换测试用例
build: 更新配置文件与构建脚本
style: 清理多余的空行与注释
2025-12-18 17:35:55 +08:00

142 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package app
import (
"fmt"
"net/http"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/pkg/validation"
"bindbox-game/internal/repository/mysql/model"
)
type listCouponsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Status *int32 `form:"status"` // 1未使用 2已使用 3已过期不传默认1
}
type listCouponsResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []couponItem `json:"list"`
}
type couponItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Amount int64 `json:"amount"`
Remaining int64 `json:"remaining"`
ValidStart string `json:"valid_start"`
ValidEnd string `json:"valid_end"`
Status int32 `json:"status"`
Rules string `json:"rules"`
UsedAt string `json:"used_at,omitempty"` // 使用时间(已使用时返回)
}
// ListUserCoupons 查看用户优惠券
// @Summary 查看用户优惠券
// @Description 查看用户持有的优惠券列表,支持按状态过滤
// @Tags APP端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Security LoginVerifyToken
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Param status query int false "状态1未使用 2已使用 3已过期不传默认1"
// @Success 200 {object} listCouponsResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/users/{user_id}/coupons [get]
func (h *handler) ListUserCoupons() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listCouponsRequest)
rsp := new(listCouponsResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID := int64(ctx.SessionUserInfo().Id)
// 默认查询未使用的优惠券
status := int32(1)
if req.Status != nil && *req.Status > 0 {
status = *req.Status
}
items, total, err := h.user.ListCouponsByStatus(ctx.RequestContext(), userID, status, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10003, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
if len(items) == 0 {
rsp.List = []couponItem{}
ctx.Payload(rsp)
return
}
ids := make([]int64, 0, len(items))
for _, it := range items {
ids = append(ids, it.CouponID)
}
rows, err := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.SystemCoupons.ID.In(ids...)).Find()
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10003, err.Error()))
return
}
mp := make(map[int64]*model.SystemCoupons, len(rows))
for i := range rows {
c := rows[i]
mp[c.ID] = c
}
rsp.List = make([]couponItem, 0, len(items))
for _, it := range items {
sc := mp[it.CouponID]
name := ""
amount := int64(0)
remaining := int64(0)
rules := ""
if sc != nil {
name = sc.Name
// 金额券amount 显示模板面值remaining 显示当前余额
if sc.DiscountType == 1 {
amount = sc.DiscountValue
_ = h.repo.GetDbR().Raw("SELECT COALESCE(balance_amount,0) FROM user_coupons WHERE id=?", it.ID).Scan(&remaining).Error
} else {
amount = sc.DiscountValue
remaining = sc.DiscountValue
}
rules = buildCouponRules(sc)
}
vs := it.ValidStart.Format("2006-01-02 15:04:05")
ve := ""
if !it.ValidEnd.IsZero() {
ve = it.ValidEnd.Format("2006-01-02 15:04:05")
}
usedAt := ""
if !it.UsedAt.IsZero() {
usedAt = it.UsedAt.Format("2006-01-02 15:04:05")
}
vi := couponItem{ID: it.ID, Name: name, Amount: amount, Remaining: remaining, ValidStart: vs, ValidEnd: ve, Status: it.Status, Rules: rules, UsedAt: usedAt}
rsp.List = append(rsp.List, vi)
}
ctx.Payload(rsp)
}
}
func buildCouponRules(c *model.SystemCoupons) string {
switch c.DiscountType {
case 1:
return fmt.Sprintf("直减%v分满%v分可用", c.DiscountValue, c.MinSpend)
case 2:
return fmt.Sprintf("满%v分减%v分", c.MinSpend, c.DiscountValue)
case 3:
p := float64(c.DiscountValue) / 10.0
return fmt.Sprintf("折扣%0.1f%%,满%v分可用", p, c.MinSpend)
default:
return ""
}
}