281 lines
8.3 KiB
Go
281 lines
8.3 KiB
Go
package app
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"time"
|
||
|
||
"bindbox-game/internal/code"
|
||
"bindbox-game/internal/pkg/core"
|
||
"bindbox-game/internal/pkg/validation"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
)
|
||
|
||
// ==================== 用户次数卡 API ====================
|
||
|
||
type getGamePassesRequest struct {
|
||
ActivityID *int64 `form:"activity_id"`
|
||
}
|
||
|
||
type userGamePassItem struct {
|
||
ID int64 `json:"id"`
|
||
ActivityID int64 `json:"activity_id"`
|
||
ActivityName string `json:"activity_name"`
|
||
Remaining int32 `json:"remaining"`
|
||
ExpiredAt string `json:"expired_at"`
|
||
Source string `json:"source"`
|
||
}
|
||
|
||
type getGamePassesResponse struct {
|
||
TotalRemaining int32 `json:"total_remaining"`
|
||
GlobalRemaining int32 `json:"global_remaining"` // 全局通用次数
|
||
Passes []userGamePassItem `json:"passes"`
|
||
}
|
||
|
||
// GetGamePasses 获取用户可用的游戏次数卡
|
||
// @Summary 获取用户次数卡
|
||
// @Description 查询当前用户可用的游戏次数卡
|
||
// @Tags APP端.用户
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security LoginVerifyToken
|
||
// @Param activity_id query integer false "活动ID,不传返回所有可用次数卡"
|
||
// @Success 200 {object} getGamePassesResponse
|
||
// @Failure 400 {object} code.Failure
|
||
// @Router /api/app/game-passes/available [get]
|
||
func (h *handler) GetGamePasses() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(getGamePassesRequest)
|
||
res := new(getGamePassesResponse)
|
||
if err := ctx.ShouldBindForm(req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
|
||
userID := int64(ctx.SessionUserInfo().Id)
|
||
now := time.Now()
|
||
|
||
// 查询用户的次数卡(已过滤过期和剩余为0的)
|
||
q := h.readDB.UserGamePasses.WithContext(ctx.RequestContext()).
|
||
Where(h.readDB.UserGamePasses.UserID.Eq(userID)).
|
||
Where(h.readDB.UserGamePasses.Remaining.Gt(0))
|
||
|
||
passes, err := q.Find()
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error()))
|
||
return
|
||
}
|
||
|
||
// 获取活动名称
|
||
activityIDs := make([]int64, 0)
|
||
for _, p := range passes {
|
||
if p.ActivityID > 0 {
|
||
activityIDs = append(activityIDs, p.ActivityID)
|
||
}
|
||
}
|
||
activityMap := make(map[int64]string)
|
||
if len(activityIDs) > 0 {
|
||
activities, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).
|
||
Where(h.readDB.Activities.ID.In(activityIDs...)).Find()
|
||
for _, a := range activities {
|
||
activityMap[a.ID] = a.Name
|
||
}
|
||
}
|
||
|
||
res.Passes = make([]userGamePassItem, 0)
|
||
for _, p := range passes {
|
||
// 检查是否过期
|
||
if !p.ExpiredAt.IsZero() && p.ExpiredAt.Before(now) {
|
||
continue
|
||
}
|
||
|
||
// 如果指定了活动ID,只返回全局通用的和该活动的
|
||
if req.ActivityID != nil && *req.ActivityID > 0 {
|
||
if p.ActivityID != 0 && p.ActivityID != *req.ActivityID {
|
||
continue
|
||
}
|
||
}
|
||
|
||
expiredAt := ""
|
||
if !p.ExpiredAt.IsZero() {
|
||
expiredAt = p.ExpiredAt.Format("2006-01-02 15:04:05")
|
||
}
|
||
|
||
item := userGamePassItem{
|
||
ID: p.ID,
|
||
ActivityID: p.ActivityID,
|
||
ActivityName: activityMap[p.ActivityID],
|
||
Remaining: p.Remaining,
|
||
ExpiredAt: expiredAt,
|
||
Source: p.Source,
|
||
}
|
||
|
||
res.TotalRemaining += p.Remaining
|
||
if p.ActivityID == 0 {
|
||
res.GlobalRemaining += p.Remaining
|
||
}
|
||
res.Passes = append(res.Passes, item)
|
||
}
|
||
|
||
ctx.Payload(res)
|
||
}
|
||
}
|
||
|
||
// ==================== 套餐列表 API ====================
|
||
|
||
type getPackagesRequest struct {
|
||
ActivityID *int64 `form:"activity_id"`
|
||
}
|
||
|
||
type packageItem struct {
|
||
ID int64 `json:"id"`
|
||
Name string `json:"name"`
|
||
PassCount int32 `json:"pass_count"`
|
||
Price int64 `json:"price"`
|
||
OriginalPrice int64 `json:"original_price"`
|
||
ValidDays int32 `json:"valid_days"`
|
||
ActivityID int64 `json:"activity_id"`
|
||
}
|
||
|
||
type getPackagesResponse struct {
|
||
Packages []packageItem `json:"packages"`
|
||
}
|
||
|
||
// GetGamePassPackages 获取可购买的套餐列表
|
||
// @Summary 获取次数卡套餐
|
||
// @Description 获取可购买的次数卡套餐列表
|
||
// @Tags APP端.用户
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param activity_id query integer false "活动ID,不传返回全局套餐"
|
||
// @Success 200 {object} getPackagesResponse
|
||
// @Failure 400 {object} code.Failure
|
||
// @Router /api/app/game-passes/packages [get]
|
||
func (h *handler) GetGamePassPackages() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(getPackagesRequest)
|
||
res := new(getPackagesResponse)
|
||
if err := ctx.ShouldBindForm(req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
|
||
q := h.readDB.GamePassPackages.WithContext(ctx.RequestContext()).
|
||
Where(h.readDB.GamePassPackages.Status.Eq(1)) // 只返回上架的
|
||
|
||
// 如果指定了活动ID,返回全局套餐(activity_id=0)和该活动的专属套餐
|
||
if req.ActivityID != nil && *req.ActivityID > 0 {
|
||
q = q.Where(h.readDB.GamePassPackages.ActivityID.In(0, *req.ActivityID))
|
||
} else {
|
||
// 只返回全局套餐
|
||
q = q.Where(h.readDB.GamePassPackages.ActivityID.Eq(0))
|
||
}
|
||
|
||
packages, err := q.Order(h.readDB.GamePassPackages.SortOrder.Desc(), h.readDB.GamePassPackages.ID.Asc()).Find()
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error()))
|
||
return
|
||
}
|
||
|
||
res.Packages = make([]packageItem, len(packages))
|
||
for i, p := range packages {
|
||
res.Packages[i] = packageItem{
|
||
ID: p.ID,
|
||
Name: p.Name,
|
||
PassCount: p.PassCount,
|
||
Price: p.Price,
|
||
OriginalPrice: p.OriginalPrice,
|
||
ValidDays: p.ValidDays,
|
||
ActivityID: p.ActivityID,
|
||
}
|
||
}
|
||
|
||
ctx.Payload(res)
|
||
}
|
||
}
|
||
|
||
// ==================== 购买套餐 API ====================
|
||
|
||
type purchasePackageRequest struct {
|
||
PackageID int64 `json:"package_id" binding:"required"`
|
||
Count int32 `json:"count"` // 购买数量
|
||
}
|
||
|
||
type purchasePackageResponse struct {
|
||
OrderNo string `json:"order_no"`
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// PurchaseGamePassPackage 购买次数卡套餐(创建订单)
|
||
// @Summary 购买次数卡套餐
|
||
// @Description 购买次数卡套餐,创建订单等待支付
|
||
// @Tags APP端.用户
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security LoginVerifyToken
|
||
// @Param RequestBody body purchasePackageRequest true "请求参数"
|
||
// @Success 200 {object} purchasePackageResponse
|
||
// @Failure 400 {object} code.Failure
|
||
// @Router /api/app/game-passes/purchase [post]
|
||
func (h *handler) PurchaseGamePassPackage() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(purchasePackageRequest)
|
||
res := new(purchasePackageResponse)
|
||
if err := ctx.ShouldBindJSON(req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
|
||
if req.Count <= 0 {
|
||
req.Count = 1
|
||
}
|
||
|
||
userID := int64(ctx.SessionUserInfo().Id)
|
||
|
||
// 查询套餐信息
|
||
pkg, err := h.readDB.GamePassPackages.WithContext(ctx.RequestContext()).
|
||
Where(h.readDB.GamePassPackages.ID.Eq(req.PackageID)).
|
||
Where(h.readDB.GamePassPackages.Status.Eq(1)).
|
||
First()
|
||
if err != nil || pkg == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "套餐不存在或已下架"))
|
||
return
|
||
}
|
||
|
||
// Calculate total price
|
||
totalPrice := pkg.Price * int64(req.Count)
|
||
|
||
// 创建订单
|
||
now := time.Now()
|
||
orderNo := now.Format("20060102150405") + fmt.Sprintf("%04d", now.UnixNano()%10000)
|
||
order := &model.Orders{
|
||
UserID: userID,
|
||
OrderNo: "GP" + orderNo,
|
||
SourceType: 4, // 次数卡购买
|
||
TotalAmount: totalPrice,
|
||
ActualAmount: totalPrice,
|
||
Status: 1, // 待支付
|
||
Remark: fmt.Sprintf("game_pass_package:%s|count:%d", pkg.Name, req.Count),
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
|
||
if err := h.writeDB.Orders.WithContext(ctx.RequestContext()).
|
||
Omit(h.writeDB.Orders.PaidAt, h.writeDB.Orders.CancelledAt).
|
||
Create(order); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error()))
|
||
return
|
||
}
|
||
|
||
// 在备注中记录套餐ID和数量
|
||
remark := fmt.Sprintf("%s|pkg_id:%d|count:%d", order.Remark, pkg.ID, req.Count)
|
||
h.writeDB.Orders.WithContext(ctx.RequestContext()).
|
||
Where(h.writeDB.Orders.ID.Eq(order.ID)).
|
||
Updates(map[string]any{"remark": remark})
|
||
|
||
res.OrderNo = order.OrderNo
|
||
res.Message = "订单创建成功,请完成支付"
|
||
ctx.Payload(res)
|
||
}
|
||
}
|