302 lines
9.9 KiB
Go
302 lines
9.9 KiB
Go
package admin
|
||
|
||
import (
|
||
"errors"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.echol.cn/loser/Go-Web-Template/server/global"
|
||
appModel "git.echol.cn/loser/Go-Web-Template/server/model/app"
|
||
"git.echol.cn/loser/Go-Web-Template/server/model/common/response"
|
||
appSvc "git.echol.cn/loser/Go-Web-Template/server/service/app"
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type AppInviteCodesApi struct{}
|
||
|
||
type appInviteCodeListItem struct {
|
||
ID uint `json:"id"`
|
||
CreatedByUserID uint `json:"createdByUserId"`
|
||
CodeLast4 string `json:"codeLast4"`
|
||
Status appModel.InviteCodeStatus `json:"status"`
|
||
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
|
||
UsedByUserID *uint `json:"usedByUserId,omitempty"`
|
||
UsedAt *time.Time `json:"usedAt,omitempty"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
type appInviteCodeDetailResponse struct {
|
||
Code appModel.AppInviteCode `json:"code"`
|
||
Inviter struct {
|
||
ID uint `json:"id"`
|
||
Username string `json:"username"`
|
||
} `json:"inviter"`
|
||
Invitee *struct {
|
||
ID uint `json:"id"`
|
||
Username string `json:"username"`
|
||
} `json:"invitee,omitempty"`
|
||
Relation *appModel.AppUserInviteRelation `json:"relation,omitempty"`
|
||
}
|
||
|
||
type issueInviteCodeRequest struct {
|
||
UserID uint `json:"userId" binding:"required"`
|
||
ExpireHours *int `json:"expireHours"`
|
||
}
|
||
|
||
type issueInviteCodeResponse struct {
|
||
Code string `json:"code"`
|
||
CodeLast4 string `json:"codeLast4"`
|
||
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
|
||
}
|
||
|
||
type clearUnusedRequest struct {
|
||
UserID uint `json:"userId" binding:"required"`
|
||
Scope string `json:"scope"` // active|all
|
||
}
|
||
|
||
// GetAppInviteCodesList
|
||
// @Tags AppInviteCodesAdmin
|
||
// @Summary 后台获取玩家邀请码列表
|
||
// @Security ApiKeyAuth
|
||
// @Produce application/json
|
||
// @Param page query int false "页码" default(1)
|
||
// @Param pageSize query int false "每页大小" default(10)
|
||
// @Param status query string false "状态 unused|used|revoked|expired"
|
||
// @Param createdByUserId query int false "邀请人用户ID"
|
||
// @Param usedByUserId query int false "使用人用户ID"
|
||
// @Param codeLast4 query string false "末4位"
|
||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||
// @Router /admin/app-invite-codes [get]
|
||
func (a *AppInviteCodesApi) GetAppInviteCodesList(c *gin.Context) {
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
if pageSize <= 0 || pageSize > 200 {
|
||
pageSize = 10
|
||
}
|
||
|
||
status := strings.TrimSpace(c.Query("status"))
|
||
createdByUserID, _ := strconv.Atoi(c.Query("createdByUserId"))
|
||
usedByUserID, _ := strconv.Atoi(c.Query("usedByUserId"))
|
||
codeLast4 := strings.TrimSpace(c.Query("codeLast4"))
|
||
|
||
db := global.GVA_DB.Model(&appModel.AppInviteCode{})
|
||
if status != "" && status != "expired" {
|
||
db = db.Where("status = ?", status)
|
||
}
|
||
if createdByUserID > 0 {
|
||
db = db.Where("created_by_user_id = ?", createdByUserID)
|
||
}
|
||
if usedByUserID > 0 {
|
||
db = db.Where("used_by_user_id = ?", usedByUserID)
|
||
}
|
||
if codeLast4 != "" {
|
||
db = db.Where("code_last4 = ?", codeLast4)
|
||
}
|
||
|
||
// 仅查询“已过期”视图:expires_at < now AND status=unused
|
||
if status == "expired" {
|
||
now := time.Now()
|
||
db = db.Where("status = ?", appModel.InviteCodeStatusUnused).Where("expires_at IS NOT NULL AND expires_at <= ?", now)
|
||
}
|
||
|
||
var total int64
|
||
if err := db.Count(&total).Error; err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
var list []appModel.AppInviteCode
|
||
if err := db.Order("id desc").Limit(pageSize).Offset((page - 1) * pageSize).Find(&list).Error; err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
items := make([]appInviteCodeListItem, 0, len(list))
|
||
for _, rec := range list {
|
||
items = append(items, appInviteCodeListItem{
|
||
ID: rec.ID,
|
||
CreatedByUserID: rec.CreatedByUserID,
|
||
CodeLast4: rec.CodeLast4,
|
||
Status: rec.Status,
|
||
ExpiresAt: rec.ExpiresAt,
|
||
UsedByUserID: rec.UsedByUserID,
|
||
UsedAt: rec.UsedAt,
|
||
CreatedAt: rec.CreatedAt,
|
||
})
|
||
}
|
||
|
||
response.OkWithDetailed(response.PageResult{
|
||
List: items,
|
||
Total: total,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
}, "获取成功", c)
|
||
}
|
||
|
||
// GetAppInviteCodeById
|
||
// @Tags AppInviteCodesAdmin
|
||
// @Summary 后台获取玩家邀请码详情
|
||
// @Security ApiKeyAuth
|
||
// @Produce application/json
|
||
// @Param id path int true "邀请码ID"
|
||
// @Success 200 {object} response.Response{data=appInviteCodeDetailResponse,msg=string} "获取成功"
|
||
// @Router /admin/app-invite-codes/{id} [get]
|
||
func (a *AppInviteCodesApi) GetAppInviteCodeById(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
if id <= 0 {
|
||
response.FailWithMessage("参数错误", c)
|
||
return
|
||
}
|
||
|
||
var code appModel.AppInviteCode
|
||
if err := global.GVA_DB.First(&code, id).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
response.FailWithMessage("记录不存在", c)
|
||
return
|
||
}
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
var inviter appModel.AppUser
|
||
_ = global.GVA_DB.Select("id, username").First(&inviter, code.CreatedByUserID).Error
|
||
|
||
var relation appModel.AppUserInviteRelation
|
||
relErr := global.GVA_DB.Where("invite_code_id = ?", code.ID).First(&relation).Error
|
||
var invitee *appModel.AppUser = nil
|
||
if relErr == nil {
|
||
var u appModel.AppUser
|
||
if err := global.GVA_DB.Select("id, username").First(&u, relation.InviteeUserID).Error; err == nil {
|
||
invitee = &u
|
||
}
|
||
}
|
||
|
||
payload := appInviteCodeDetailResponse{Code: code}
|
||
payload.Inviter.ID = inviter.ID
|
||
payload.Inviter.Username = inviter.Username
|
||
if invitee != nil {
|
||
payload.Invitee = &struct {
|
||
ID uint `json:"id"`
|
||
Username string `json:"username"`
|
||
}{ID: invitee.ID, Username: invitee.Username}
|
||
}
|
||
if relErr == nil {
|
||
payload.Relation = &relation
|
||
}
|
||
|
||
response.OkWithData(payload, c)
|
||
}
|
||
|
||
// RevokeAppInviteCode
|
||
// @Tags AppInviteCodesAdmin
|
||
// @Summary 作废玩家邀请码
|
||
// @Security ApiKeyAuth
|
||
// @Produce application/json
|
||
// @Param id path int true "邀请码ID"
|
||
// @Success 200 {object} response.Response{msg=string} "作废成功"
|
||
// @Router /admin/app-invite-codes/{id}/revoke [post]
|
||
func (a *AppInviteCodesApi) RevokeAppInviteCode(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
if id <= 0 {
|
||
response.FailWithMessage("参数错误", c)
|
||
return
|
||
}
|
||
|
||
var code appModel.AppInviteCode
|
||
if err := global.GVA_DB.First(&code, id).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
response.FailWithMessage("记录不存在", c)
|
||
return
|
||
}
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
if code.Status == appModel.InviteCodeStatusUsed {
|
||
response.FailWithMessage("已使用的邀请码不可作废", c)
|
||
return
|
||
}
|
||
if code.Status == appModel.InviteCodeStatusRevoked {
|
||
response.OkWithMessage("已作废", c)
|
||
return
|
||
}
|
||
if err := global.GVA_DB.Model(&code).Update("status", appModel.InviteCodeStatusRevoked).Error; err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
response.OkWithMessage("作废成功", c)
|
||
}
|
||
|
||
// IssueAppInviteCodeForUser
|
||
// @Tags AppInviteCodesAdmin
|
||
// @Summary 后台代用户生成邀请码(返回明文一次性)
|
||
// @Security ApiKeyAuth
|
||
// @Produce application/json
|
||
// @Param data body issueInviteCodeRequest true "用户ID"
|
||
// @Success 200 {object} response.Response{data=issueInviteCodeResponse,msg=string} "生成成功"
|
||
// @Router /admin/app-invite-codes/issue [post]
|
||
func (a *AppInviteCodesApi) IssueAppInviteCodeForUser(c *gin.Context) {
|
||
var req issueInviteCodeRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
// 先强制作废该用户所有 active unused
|
||
now := time.Now()
|
||
revokeDB := global.GVA_DB.Model(&appModel.AppInviteCode{}).
|
||
Where("created_by_user_id = ? AND status = ?", req.UserID, appModel.InviteCodeStatusUnused).
|
||
Where(global.GVA_DB.Where("expires_at IS NULL").Or("expires_at > ?", now)).
|
||
Update("status", appModel.InviteCodeStatusRevoked)
|
||
if revokeDB.Error != nil {
|
||
response.FailWithMessage(revokeDB.Error.Error(), c)
|
||
return
|
||
}
|
||
|
||
// 复用现有生成逻辑(按系统参数 expireHours),若你传了 expireHours,本期先忽略,后续可扩展为临时覆盖。
|
||
res, err := appSvc.AppInviteCodeServiceApp.Generate(req.UserID)
|
||
if err != nil {
|
||
// 若生成被“已有未使用码”拦住,说明 revoke 未覆盖(例如过期时间为空/逻辑差异),直接返回错误即可。
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
response.OkWithData(issueInviteCodeResponse{Code: res.Code, CodeLast4: res.CodeLast4, ExpiresAt: res.ExpiresAt}, c)
|
||
}
|
||
|
||
// ClearUnusedInviteCodes
|
||
// @Tags AppInviteCodesAdmin
|
||
// @Summary 强制清空用户未使用邀请码(批量作废)
|
||
// @Security ApiKeyAuth
|
||
// @Produce application/json
|
||
// @Param data body clearUnusedRequest true "用户ID"
|
||
// @Success 200 {object} response.Response{msg=string} "清空成功"
|
||
// @Router /admin/app-invite-codes/clear-unused [post]
|
||
func (a *AppInviteCodesApi) ClearUnusedInviteCodes(c *gin.Context) {
|
||
var req clearUnusedRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
scope := strings.TrimSpace(req.Scope)
|
||
if scope == "" {
|
||
scope = "active"
|
||
}
|
||
|
||
db := global.GVA_DB.Model(&appModel.AppInviteCode{}).
|
||
Where("created_by_user_id = ? AND status = ?", req.UserID, appModel.InviteCodeStatusUnused)
|
||
if scope == "active" {
|
||
now := time.Now()
|
||
db = db.Where(global.GVA_DB.Where("expires_at IS NULL").Or("expires_at > ?", now))
|
||
}
|
||
|
||
if err := db.Update("status", appModel.InviteCodeStatusRevoked).Error; err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
response.OkWithMessage("清空成功", c)
|
||
}
|