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"` } 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() // 应用搜索条件 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 } rows, err := q.Order(h.readDB.Users.ID.Desc()).Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find() if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 20102, 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 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, 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"` } 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 } 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, ). 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, } } 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"` } // 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 { ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error())) return } rsp.Success = true ctx.Payload(rsp) } } 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) } }