bindbox-game/internal/api/admin/users_admin.go
邹方成 16e2ede037 feat: 新增订单列表筛选条件与活动信息展示
refactor(orders): 重构订单列表查询逻辑,支持按消耗状态筛选
feat(orders): 订单列表返回新增活动分类与玩法类型信息
fix(orders): 修复订单支付时间空指针问题
docs(swagger): 更新订单相关接口文档
test(matching): 添加对对碰奖励匹配测试用例
chore: 清理无用脚本文件
2025-12-22 15:15:18 +08:00

988 lines
32 KiB
Go
Raw 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 admin
import (
"net/http"
"strconv"
"time"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/pkg/validation"
"bindbox-game/internal/repository/mysql/model"
"bindbox-game/internal/service/user"
)
type listUsersRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Nickname string `form:"nickname"`
InviteCode string `form:"inviteCode"`
StartDate string `form:"startDate"`
EndDate string `form:"endDate"`
ID *int64 `form:"id"`
}
type listUsersResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []adminUserItem `json:"list"`
}
// ListAppUsers 管理端用户列表
// @Summary 管理端用户列表
// @Description 查看APP端用户分页列表
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Param nickname query string false "用户昵称"
// @Param inviteCode query string false "邀请码"
// @Param startDate query string false "开始日期(YYYY-MM-DD)"
// @Param endDate query string false "结束日期(YYYY-MM-DD)"
// @Success 200 {object} listUsersResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users [get]
// @Security LoginVerifyToken
func (h *handler) ListAppUsers() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listUsersRequest)
rsp := new(listUsersResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 20
}
if req.PageSize > 100 {
req.PageSize = 100
}
q := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().
LeftJoin(h.readDB.Channels, h.readDB.Channels.ID.EqCol(h.readDB.Users.ChannelID)).
Select(h.readDB.Users.ALL, h.readDB.Channels.Name.As("channel_name"), h.readDB.Channels.Code.As("channel_code"))
// 应用搜索条件
if req.ID != nil {
q = q.Where(h.readDB.Users.ID.Eq(*req.ID))
}
if req.Nickname != "" {
q = q.Where(h.readDB.Users.Nickname.Like("%" + req.Nickname + "%"))
}
if req.InviteCode != "" {
q = q.Where(h.readDB.Users.InviteCode.Eq(req.InviteCode))
}
if req.StartDate != "" {
if startTime, err := time.Parse("2006-01-02", req.StartDate); err == nil {
q = q.Where(h.readDB.Users.CreatedAt.Gte(startTime))
}
}
if req.EndDate != "" {
if endTime, err := time.Parse("2006-01-02", req.EndDate); err == nil {
// 设置结束时间为当天的23:59:59
endTime = endTime.Add(24 * time.Hour).Add(-time.Second)
q = q.Where(h.readDB.Users.CreatedAt.Lte(endTime))
}
}
total, err := q.Count()
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20101, err.Error()))
return
}
type result struct {
model.Users
ChannelName string
ChannelCode string
}
var rows []result
if err := q.Order(h.readDB.Users.ID.Desc()).Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Scan(&rows); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20102, err.Error()))
return
}
// 获取用户ID列表以批量查询资产
userIDs := make([]int64, len(rows))
for i, v := range rows {
userIDs[i] = v.ID
}
// 批量查询优惠券数量(未使用的)
couponCounts := make(map[int64]int64)
if len(userIDs) > 0 {
type countResult struct {
UserID int64
Count int64
}
var counts []countResult
h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
Select(h.readDB.UserCoupons.UserID, h.readDB.UserCoupons.ID.Count().As("count")).
Where(h.readDB.UserCoupons.UserID.In(userIDs...)).
Where(h.readDB.UserCoupons.Status.Eq(1)). // 1=未使用
Group(h.readDB.UserCoupons.UserID).
Scan(&counts)
for _, c := range counts {
couponCounts[c.UserID] = c.Count
}
}
// 批量查询道具卡数量(未使用的)
cardCounts := make(map[int64]int64)
if len(userIDs) > 0 {
type countResult struct {
UserID int64
Count int64
}
var counts []countResult
h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().
Select(h.readDB.UserItemCards.UserID, h.readDB.UserItemCards.ID.Count().As("count")).
Where(h.readDB.UserItemCards.UserID.In(userIDs...)).
Where(h.readDB.UserItemCards.Status.Eq(1)). // 1=未使用
Group(h.readDB.UserItemCards.UserID).
Scan(&counts)
for _, c := range counts {
cardCounts[c.UserID] = c.Count
}
}
// 批量查询消费统计
todayConsume := make(map[int64]int64)
sevenDayConsume := make(map[int64]int64)
if len(userIDs) > 0 {
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
sevenDayStart := todayStart.AddDate(0, 0, -6) // 包括今天共7天
type consumeResult struct {
UserID int64
Amount int64
}
// 当日消费
var todayRes []consumeResult
h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().
Select(h.readDB.Orders.UserID, h.readDB.Orders.ActualAmount.Sum().As("amount")).
Where(h.readDB.Orders.UserID.In(userIDs...)).
Where(h.readDB.Orders.Status.Eq(2)). // 2=已支付
Where(h.readDB.Orders.CreatedAt.Gte(todayStart)).
Group(h.readDB.Orders.UserID).
Scan(&todayRes)
for _, r := range todayRes {
todayConsume[r.UserID] = r.Amount
}
// 近7天消费
var sevenRes []consumeResult
h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().
Select(h.readDB.Orders.UserID, h.readDB.Orders.ActualAmount.Sum().As("amount")).
Where(h.readDB.Orders.UserID.In(userIDs...)).
Where(h.readDB.Orders.Status.Eq(2)). // 2=已支付
Where(h.readDB.Orders.CreatedAt.Gte(sevenDayStart)).
Group(h.readDB.Orders.UserID).
Scan(&sevenRes)
for _, r := range sevenRes {
sevenDayConsume[r.UserID] = r.Amount
}
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = make([]adminUserItem, len(rows))
for i, v := range rows {
rsp.List[i] = adminUserItem{
ID: v.ID,
Nickname: v.Nickname,
Avatar: v.Avatar,
InviteCode: v.InviteCode,
InviterID: v.InviterID,
CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05"),
DouyinID: v.DouyinID,
ChannelName: v.ChannelName,
ChannelCode: v.ChannelCode,
CouponsCount: couponCounts[v.ID],
ItemCardsCount: cardCounts[v.ID],
TodayConsume: todayConsume[v.ID],
SevenDayConsume: sevenDayConsume[v.ID],
}
}
ctx.Payload(rsp)
}
}
type listInvitesRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type listInvitesResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []adminUserItem `json:"list"`
}
// ListUserInvites 查看用户邀请列表
// @Summary 查看用户邀请列表
// @Description 查看指定用户邀请的用户列表
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Success 200 {object} listInvitesResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/invites [get]
// @Security LoginVerifyToken
func (h *handler) ListUserInvites() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listInvitesRequest)
rsp := new(listInvitesResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
rows, total, err := h.user.ListInvites(ctx.RequestContext(), userID, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20103, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = make([]adminUserItem, len(rows))
for i, v := range rows {
rsp.List[i] = adminUserItem{ID: v.ID, Nickname: v.Nickname, Avatar: v.Avatar, InviteCode: v.InviteCode, InviterID: v.InviterID, CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05")}
}
ctx.Payload(rsp)
}
}
type listOrdersRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type listOrdersResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []*user.OrderWithItems `json:"list"`
}
type listInventoryRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type listInventoryResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []*user.InventoryWithProduct `json:"list"`
}
// ListUserOrders 查看用户订单列表
// @Summary 查看用户订单列表
// @Description 查看指定用户的订单记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Success 200 {object} listOrdersResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/orders [get]
// @Security LoginVerifyToken
func (h *handler) ListUserOrders() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listOrdersRequest)
rsp := new(listOrdersResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
items, total, err := h.user.ListOrdersWithItems(ctx.RequestContext(), userID, 0, nil, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20104, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = items
ctx.Payload(rsp)
}
}
// 查看用户资产列表
// @Summary 查看用户资产列表
// @Description 查看指定用户的资产记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Success 200 {object} listInventoryResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/inventory [get]
// @Security LoginVerifyToken
func (h *handler) ListUserInventory() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listInventoryRequest)
rsp := new(listInventoryResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
rows, total, err := h.user.ListInventoryWithProduct(ctx.RequestContext(), userID, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = rows
ctx.Payload(rsp)
}
}
type listUserItemCardsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type listUserItemCardsResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []*user.ItemCardWithTemplate `json:"list"`
}
// ListUserItemCards 查看用户道具卡列表
// @Summary 查看用户道具卡列表
// @Description 查看指定用户的道具卡持有记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Success 200 {object} listUserItemCardsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/item_cards [get]
// @Security LoginVerifyToken
func (h *handler) ListUserItemCards() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listUserItemCardsRequest)
rsp := new(listUserItemCardsResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
items, total, err := h.user.ListUserItemCardsWithTemplate(ctx.RequestContext(), userID, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20106, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = items
ctx.Payload(rsp)
}
}
type listCouponsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type adminUserCouponItem struct {
ID int64 `json:"id"`
CouponID int64 `json:"coupon_id"`
Status int32 `json:"status"`
UsedOrderID int64 `json:"used_order_id"`
UsedAt string `json:"used_at"`
ValidStart string `json:"valid_start"`
ValidEnd string `json:"valid_end"`
Name string `json:"name"`
ScopeType int32 `json:"scope_type"`
DiscountType int32 `json:"discount_type"`
DiscountValue int64 `json:"discount_value"`
MinSpend int64 `json:"min_spend"`
BalanceAmount int64 `json:"balance_amount"`
}
type listCouponsResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []adminUserCouponItem `json:"list"`
}
// ListUserCoupons 查看用户优惠券列表
// @Summary 查看用户优惠券列表
// @Description 查看指定用户持有的优惠券列表
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Success 200 {object} listCouponsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/coupons [get]
// @Security LoginVerifyToken
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, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
// 统计总数
base := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserCoupons.UserID.Eq(userID))
total, err := base.Count()
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
return
}
// 联表查询 system_coupons 获取优惠券模板信息
type row struct {
ID int64
CouponID int64
Status int32
UsedOrderID int64
UsedAt *string
ValidStart *string
ValidEnd *string
Name string
ScopeType int32
DiscountType int32
DiscountValue int64
MinSpend int64
BalanceAmount int64
}
q := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
LeftJoin(h.readDB.SystemCoupons, h.readDB.SystemCoupons.ID.EqCol(h.readDB.UserCoupons.CouponID)).
Select(
h.readDB.UserCoupons.ID, h.readDB.UserCoupons.CouponID, h.readDB.UserCoupons.Status,
h.readDB.UserCoupons.UsedOrderID, h.readDB.UserCoupons.UsedAt, h.readDB.UserCoupons.ValidStart, h.readDB.UserCoupons.ValidEnd,
h.readDB.SystemCoupons.Name, h.readDB.SystemCoupons.ScopeType, h.readDB.SystemCoupons.DiscountType,
h.readDB.SystemCoupons.DiscountValue, h.readDB.SystemCoupons.MinSpend,
h.readDB.UserCoupons.BalanceAmount,
).
Where(h.readDB.UserCoupons.UserID.Eq(userID)).
Order(h.readDB.UserCoupons.ID.Desc()).
Limit(req.PageSize).Offset((req.Page - 1) * req.PageSize)
var rows []row
if err := q.Scan(&rows); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = make([]adminUserCouponItem, len(rows))
for i, v := range rows {
rsp.List[i] = adminUserCouponItem{
ID: v.ID,
CouponID: v.CouponID,
Status: v.Status,
UsedOrderID: v.UsedOrderID,
UsedAt: nullableToString(v.UsedAt),
ValidStart: nullableToString(v.ValidStart),
ValidEnd: nullableToString(v.ValidEnd),
Name: v.Name,
ScopeType: v.ScopeType,
DiscountType: v.DiscountType,
DiscountValue: v.DiscountValue,
MinSpend: v.MinSpend,
BalanceAmount: v.BalanceAmount,
}
}
ctx.Payload(rsp)
}
}
func nullableToString(s *string) string {
if s == nil {
return ""
}
return *s
}
type listPointsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type listPointsResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []*model.UserPointsLedger `json:"list"`
}
// ListUserPoints 查看用户积分记录
// @Summary 查看用户积分记录
// @Description 查看指定用户的积分流水记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Success 200 {object} listPointsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/points [get]
// @Security LoginVerifyToken
func (h *handler) ListUserPoints() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listPointsRequest)
rsp := new(listPointsResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
items, total, err := h.user.ListPointsLedger(ctx.RequestContext(), userID, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20106, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
rsp.List = items
ctx.Payload(rsp)
}
}
type pointsBalanceResponse struct {
Balance int64 `json:"balance"`
}
type adminUserItem struct {
ID int64 `json:"id"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
InviteCode string `json:"invite_code"`
InviterID int64 `json:"inviter_id"`
CreatedAt string `json:"created_at"`
DouyinID string `json:"douyin_id"`
ChannelName string `json:"channel_name"`
ChannelCode string `json:"channel_code"`
CouponsCount int64 `json:"coupons_count"`
ItemCardsCount int64 `json:"item_cards_count"`
TodayConsume int64 `json:"today_consume"`
SevenDayConsume int64 `json:"seven_day_consume"`
}
// ListAppUsers 管理端用户列表GetUserPointsBalance 查看用户积分余额
// @Summary 查看用户积分余额
// @Description 查看指定用户当前积分余额(过滤过期)
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Success 200 {object} pointsBalanceResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/points/balance [get]
// @Security LoginVerifyToken
func (h *handler) GetUserPointsBalance() core.HandlerFunc {
return func(ctx core.Context) {
rsp := new(pointsBalanceResponse)
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
total, err := h.user.GetPointsBalance(ctx.RequestContext(), userID)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20107, err.Error()))
return
}
rsp.Balance = total
ctx.Payload(rsp)
}
}
type addPointsRequest struct {
Points int64 `json:"points" binding:"required"`
Kind string `json:"kind"`
Remark string `json:"remark"`
ValidDays *int `json:"valid_days"`
}
type addPointsResponse struct {
Success bool `json:"success"`
}
// AddUserPoints 给用户添加积分
// @Summary 给用户添加积分
// @Description 管理端为指定用户发放积分,支持设置有效期
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param RequestBody body addPointsRequest true "请求参数"
// @Success 200 {object} addPointsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/points/add [post]
// @Security LoginVerifyToken
func (h *handler) AddUserPoints() core.HandlerFunc {
return func(ctx core.Context) {
req := new(addPointsRequest)
rsp := new(addPointsResponse)
if err := ctx.ShouldBindJSON(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
var validStart *time.Time
var validEnd *time.Time
now := time.Now()
validStart = &now
if req.ValidDays != nil && *req.ValidDays > 0 {
ve := now.Add(time.Duration(*req.ValidDays) * 24 * time.Hour)
validEnd = &ve
}
if err := h.user.AddPoints(ctx.RequestContext(), userID, req.Points, req.Kind, req.Remark, validStart, validEnd); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20108, err.Error()))
return
}
rsp.Success = true
ctx.Payload(rsp)
}
}
type addCouponRequest struct {
CouponID int64 `json:"coupon_id" binding:"required"`
}
type addCouponResponse struct {
Success bool `json:"success"`
}
// AddUserCoupon 给用户添加优惠券
// @Summary 给用户添加优惠券
// @Description 管理端为指定用户发放优惠券
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param RequestBody body addCouponRequest true "请求参数"
// @Success 200 {object} addCouponResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/coupons/add [post]
// @Security LoginVerifyToken
func (h *handler) AddUserCoupon() core.HandlerFunc {
return func(ctx core.Context) {
req := new(addCouponRequest)
rsp := new(addCouponResponse)
if err := ctx.ShouldBindJSON(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
if ctx.SessionUserInfo().IsSuper != 1 {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
return
}
if err := h.user.AddCoupon(ctx.RequestContext(), userID, req.CouponID); err != nil {
msg := err.Error()
if msg == "unsupported data" {
msg = "发券失败:模板配额已满"
}
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, msg))
return
}
rsp.Success = true
ctx.Payload(rsp)
}
}
type voidUserCouponRequest struct {
}
func (h *handler) VoidUserCoupon() core.HandlerFunc {
return func(ctx core.Context) {
req := new(voidUserCouponRequest)
if err := ctx.ShouldBindJSON(req); err != nil && err.Error() != "EOF" {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
ucid, err := strconv.ParseInt(ctx.Param("user_coupon_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递持券ID"))
return
}
if ctx.SessionUserInfo().IsSuper != 1 {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
return
}
adminID := int64(ctx.SessionUserInfo().Id)
if err := h.user.VoidUserCoupon(ctx.RequestContext(), adminID, userID, ucid); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error()))
return
}
ctx.Payload(simpleMessageResponse{Message: "操作成功"})
}
}
type voidUserItemCardRequest struct {
}
func (h *handler) VoidUserItemCard() core.HandlerFunc {
return func(ctx core.Context) {
req := new(voidUserItemCardRequest)
if err := ctx.ShouldBindJSON(req); err != nil && err.Error() != "EOF" {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
icid, err := strconv.ParseInt(ctx.Param("user_item_card_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递道具卡持有ID"))
return
}
if ctx.SessionUserInfo().IsSuper != 1 {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
return
}
adminID := int64(ctx.SessionUserInfo().Id)
if err := h.user.VoidUserItemCard(ctx.RequestContext(), adminID, userID, icid); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error()))
return
}
ctx.Payload(simpleMessageResponse{Message: "操作成功"})
}
}
type voidUserInventoryRequest struct {
}
func (h *handler) VoidUserInventory() core.HandlerFunc {
return func(ctx core.Context) {
req := new(voidUserInventoryRequest)
if err := ctx.ShouldBindJSON(req); err != nil && err.Error() != "EOF" {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
invID, err := strconv.ParseInt(ctx.Param("inventory_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递资产ID"))
return
}
if ctx.SessionUserInfo().IsSuper != 1 {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
return
}
adminID := int64(ctx.SessionUserInfo().Id)
if err := h.user.VoidUserInventory(ctx.RequestContext(), adminID, userID, invID); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error()))
return
}
ctx.Payload(simpleMessageResponse{Message: "操作成功"})
}
}
type adminUserTitleItem struct {
ID int64 `json:"id"`
TitleID int64 `json:"title_id"`
Name string `json:"name"`
Description string `json:"description"`
ObtainedAt string `json:"obtained_at"`
ExpiresAt string `json:"expires_at"`
Status int32 `json:"status"`
}
type listUserTitlesResponse struct {
List []adminUserTitleItem `json:"list"`
}
func (h *handler) ListUserTitles() core.HandlerFunc {
return func(ctx core.Context) {
rsp := new(listUserTitlesResponse)
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
type row struct {
ID int64
TitleID int64
Active int32
ObtainedAt *string
ExpiresAt *string
Name string
Description string
}
q := h.readDB.UserTitles.WithContext(ctx.RequestContext()).ReadDB().
LeftJoin(h.readDB.SystemTitles, h.readDB.SystemTitles.ID.EqCol(h.readDB.UserTitles.TitleID)).
Select(
h.readDB.UserTitles.ID, h.readDB.UserTitles.TitleID, h.readDB.UserTitles.Active,
h.readDB.UserTitles.ObtainedAt, h.readDB.UserTitles.ExpiresAt,
h.readDB.SystemTitles.Name, h.readDB.SystemTitles.Description,
).
Where(h.readDB.UserTitles.UserID.Eq(userID)).
Order(h.readDB.UserTitles.ID.Desc())
var rows []row
if err := q.Scan(&rows); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20110, err.Error()))
return
}
rsp.List = make([]adminUserTitleItem, len(rows))
for i, v := range rows {
rsp.List[i] = adminUserTitleItem{
ID: v.ID,
TitleID: v.TitleID,
Name: v.Name,
Description: v.Description,
ObtainedAt: nullableToString(v.ObtainedAt),
ExpiresAt: nullableToString(v.ExpiresAt),
Status: v.Active,
}
}
ctx.Payload(rsp)
}
}
type listUserCouponUsageRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type adminUserCouponUsageItem struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
UserCouponID int64 `json:"user_coupon_id"`
ChangeAmount int64 `json:"change_amount"`
BalanceAfter int64 `json:"balance_after"`
OrderID int64 `json:"order_id"`
Action string `json:"action"`
CreatedAt string `json:"created_at"`
}
type listUserCouponUsageResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []adminUserCouponUsageItem `json:"list"`
}
func (h *handler) ListUserCouponUsage() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listUserCouponUsageRequest)
rsp := new(listUserCouponUsageResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 20
}
if req.PageSize > 100 {
req.PageSize = 100
}
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
return
}
ucid, err := strconv.ParseInt(ctx.Param("user_coupon_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递持券ID"))
return
}
var total int64
db := h.repo.GetDbR().Model(&model.UserCouponLedger{}).Where("user_id = ? AND user_coupon_id = ?", userID, ucid)
_ = db.Count(&total).Error
var list []model.UserCouponLedger
_ = db.Order("id DESC").Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find(&list).Error
rows := make([]adminUserCouponUsageItem, len(list))
for i, v := range list {
rows[i] = adminUserCouponUsageItem{
ID: v.ID,
UserID: v.UserID,
UserCouponID: v.UserCouponID,
ChangeAmount: v.ChangeAmount,
BalanceAfter: v.BalanceAfter,
OrderID: v.OrderID,
Action: v.Action,
CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05"),
}
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
if rows == nil {
rows = []adminUserCouponUsageItem{}
}
rsp.List = rows
ctx.Payload(rsp)
}
}