新增几大中心功能

This commit is contained in:
Administrator
2026-04-23 15:29:07 +08:00
parent 5c2a146a44
commit c6354ee065
123 changed files with 13074 additions and 229 deletions

View File

@@ -0,0 +1,528 @@
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"
"git.echol.cn/loser/Go-Web-Template/server/utils"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type AppWithdrawOrdersApi struct{}
type appWithdrawOrderListItem struct {
ID uint `json:"id"`
AppUserID uint `json:"appUserId"`
Username string `json:"username"`
AmountLi int64 `json:"amountLi"`
FeeLi int64 `json:"feeLi"`
NetLi int64 `json:"netLi"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
OperatorUserID uint `json:"operatorUserId"`
ApprovedBy uint `json:"approvedBy"`
ApprovedAt *time.Time `json:"approvedAt"`
PaidBy uint `json:"paidBy"`
PaidAt *time.Time `json:"paidAt"`
}
// GetAppWithdrawOrdersList
// @Tags AppWithdrawOrdersAdmin
// @Summary 后台获取提现订单列表(单位:厘)
// @Security ApiKeyAuth
// @Produce application/json
// @Param page query int false "页码" default(1)
// @Param pageSize query int false "每页大小" default(10)
// @Param userId query int false "玩家ID"
// @Param username query string false "用户名(模糊)"
// @Param status query string false "状态pending/approved/rejected/paid/failed"
// @Param startAt query string false "开始时间(ISO8601/RFC3339)"
// @Param endAt query string false "结束时间(ISO8601/RFC3339)"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
// @Router /admin/app-withdraw-orders [get]
func (a *AppWithdrawOrdersApi) GetAppWithdrawOrdersList(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
}
var userID uint
if raw := strings.TrimSpace(c.Query("userId")); raw != "" {
if v, err := strconv.Atoi(raw); err == nil && v > 0 {
userID = uint(v)
}
}
username := strings.TrimSpace(c.Query("username"))
status := strings.TrimSpace(c.Query("status"))
var startAt *time.Time
if raw := strings.TrimSpace(c.Query("startAt")); raw != "" {
if t, err := time.Parse(time.RFC3339, raw); err == nil {
startAt = &t
}
}
var endAt *time.Time
if raw := strings.TrimSpace(c.Query("endAt")); raw != "" {
if t, err := time.Parse(time.RFC3339, raw); err == nil {
endAt = &t
}
}
db := global.GVA_DB.Model(&appModel.AppWithdrawOrder{})
if userID > 0 {
db = db.Where("app_user_id = ?", userID)
}
if username != "" {
db = db.Where("username LIKE ?", "%"+username+"%")
}
if status != "" {
db = db.Where("status = ?", status)
}
if startAt != nil {
db = db.Where("created_at >= ?", *startAt)
}
if endAt != nil {
db = db.Where("created_at <= ?", *endAt)
}
var total int64
if err := db.Count(&total).Error; err != nil {
response.FailWithMessage("获取失败:"+err.Error(), c)
return
}
var list []appWithdrawOrderListItem
if err := db.
Order("id desc").
Limit(pageSize).
Offset((page - 1) * pageSize).
Select("id, app_user_id, username, amount_li, fee_li, net_li, status, created_at, operator_user_id, approved_by, approved_at, paid_by, paid_at").
Scan(&list).Error; err != nil {
response.FailWithMessage("获取失败:"+err.Error(), c)
return
}
response.OkWithData(response.PageResult{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
}, c)
}
// GetAppWithdrawOrderById
// @Tags AppWithdrawOrdersAdmin
// @Summary 后台获取提现订单详情(单位:厘)
// @Security ApiKeyAuth
// @Produce application/json
// @Param id path int true "订单ID"
// @Success 200 {object} response.Response{data=appModel.AppWithdrawOrder,msg=string} "获取成功"
// @Router /admin/app-withdraw-orders/{id} [get]
func (a *AppWithdrawOrdersApi) GetAppWithdrawOrderById(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
response.FailWithMessage("参数错误", c)
return
}
var rec appModel.AppWithdrawOrder
if err := global.GVA_DB.First(&rec, uint(id)).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.FailWithMessage("订单不存在", c)
return
}
response.FailWithMessage("获取失败:"+err.Error(), c)
return
}
response.OkWithData(rec, c)
}
type createWithdrawOrderRequest struct {
AppUserID uint `json:"appUserId" binding:"required"`
AmountLi int64 `json:"amountLi" binding:"required"`
FeeLi int64 `json:"feeLi"`
Channel string `json:"channel"`
PayeeAccount string `json:"payeeAccount"`
PayeeRealName string `json:"payeeRealName"`
ApplyRemark string `json:"applyRemark"`
}
// CreateAppWithdrawOrder
// @Tags AppWithdrawOrdersAdmin
// @Summary 后台创建提现订单(会冻结账户余额)
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param body body createWithdrawOrderRequest true "参数"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /admin/app-withdraw-orders [post]
func (a *AppWithdrawOrdersApi) CreateAppWithdrawOrder(c *gin.Context) {
var req createWithdrawOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage("参数错误", c)
return
}
if req.AmountLi <= 0 {
response.FailWithMessage("amountLi 必须 > 0", c)
return
}
if req.FeeLi < 0 {
response.FailWithMessage("feeLi 不合法", c)
return
}
net := req.AmountLi - req.FeeLi
if net <= 0 {
response.FailWithMessage("手续费不能大于等于提现金额", c)
return
}
operatorID := utils.GetUserID(c)
if operatorID == 0 {
response.FailWithMessage("未登录", c)
return
}
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var user appModel.AppUser
if err := tx.First(&user, req.AppUserID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("玩家不存在")
}
return err
}
var asset appModel.AppUserAsset
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", user.ID).First(&asset).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
asset = appModel.AppUserAsset{AppUserID: user.ID}
if err := tx.Create(&asset).Error; err != nil {
return err
}
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", user.ID).First(&asset).Error; err != nil {
return err
}
} else if err != nil {
return err
}
available := asset.AccountBalanceLi - asset.AccountFrozenLi
if available < req.AmountLi {
return errors.New("可提现余额不足")
}
asset.AccountFrozenLi += req.AmountLi
if err := tx.Model(&asset).Select("account_frozen_li").Updates(&asset).Error; err != nil {
return err
}
order := appModel.AppWithdrawOrder{
AppUserID: user.ID,
Username: user.Username,
AmountLi: req.AmountLi,
FeeLi: req.FeeLi,
NetLi: net,
Status: appModel.AppWithdrawOrderStatusPending,
Channel: strings.TrimSpace(req.Channel),
PayeeAccount: strings.TrimSpace(req.PayeeAccount),
PayeeRealName: strings.TrimSpace(req.PayeeRealName),
ApplyRemark: strings.TrimSpace(req.ApplyRemark),
OperatorUserID: operatorID,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
return tx.Create(&order).Error
}); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.OkWithMessage("创建成功", c)
}
type auditWithdrawOrderRequest struct {
AuditRemark string `json:"auditRemark"`
RejectReason string `json:"rejectReason"`
}
// ApproveAppWithdrawOrder
// @Tags AppWithdrawOrdersAdmin
// @Summary 审核通过提现订单
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param id path int true "订单ID"
// @Param body body auditWithdrawOrderRequest false "参数"
// @Success 200 {object} response.Response{msg=string} "操作成功"
// @Router /admin/app-withdraw-orders/{id}/approve [post]
func (a *AppWithdrawOrdersApi) ApproveAppWithdrawOrder(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
response.FailWithMessage("参数错误", c)
return
}
operatorID := utils.GetUserID(c)
if operatorID == 0 {
response.FailWithMessage("未登录", c)
return
}
var req auditWithdrawOrderRequest
_ = c.ShouldBindJSON(&req)
now := time.Now()
if err := global.GVA_DB.Model(&appModel.AppWithdrawOrder{}).
Where("id = ? AND status = ?", uint(id), appModel.AppWithdrawOrderStatusPending).
Updates(map[string]any{
"status": appModel.AppWithdrawOrderStatusApproved,
"audit_remark": strings.TrimSpace(req.AuditRemark),
"approved_by": operatorID,
"approved_at": &now,
"operator_user_id": operatorID,
}).Error; err != nil {
response.FailWithMessage("操作失败:"+err.Error(), c)
return
}
response.OkWithMessage("已审核通过", c)
}
// RejectAppWithdrawOrder
// @Tags AppWithdrawOrdersAdmin
// @Summary 审核拒绝提现订单(会解冻)
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param id path int true "订单ID"
// @Param body body auditWithdrawOrderRequest true "参数"
// @Success 200 {object} response.Response{msg=string} "操作成功"
// @Router /admin/app-withdraw-orders/{id}/reject [post]
func (a *AppWithdrawOrdersApi) RejectAppWithdrawOrder(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
response.FailWithMessage("参数错误", c)
return
}
operatorID := utils.GetUserID(c)
if operatorID == 0 {
response.FailWithMessage("未登录", c)
return
}
var req auditWithdrawOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage("参数错误", c)
return
}
reason := strings.TrimSpace(req.RejectReason)
if reason == "" {
response.FailWithMessage("rejectReason 不能为空", c)
return
}
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var order appModel.AppWithdrawOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, uint(id)).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("订单不存在")
}
return err
}
if order.Status != appModel.AppWithdrawOrderStatusPending {
return errors.New("当前状态不可拒绝")
}
var asset appModel.AppUserAsset
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", order.AppUserID).First(&asset).Error; err != nil {
return err
}
if asset.AccountFrozenLi < order.AmountLi {
return errors.New("冻结余额异常")
}
asset.AccountFrozenLi -= order.AmountLi
if err := tx.Model(&asset).Select("account_frozen_li").Updates(&asset).Error; err != nil {
return err
}
now := time.Now()
return tx.Model(&order).Updates(map[string]any{
"status": appModel.AppWithdrawOrderStatusRejected,
"reject_reason": reason,
"audit_remark": strings.TrimSpace(req.AuditRemark),
"approved_by": operatorID,
"approved_at": &now,
"operator_user_id": operatorID,
}).Error
}); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.OkWithMessage("已拒绝并解冻", c)
}
type payWithdrawOrderRequest struct {
ExternalTxID string `json:"externalTxId"`
FailureReason string `json:"failureReason"`
}
// MarkPaidAppWithdrawOrder
// @Tags AppWithdrawOrdersAdmin
// @Summary 确认已打款(会扣减余额并解冻)
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param id path int true "订单ID"
// @Param body body payWithdrawOrderRequest false "参数"
// @Success 200 {object} response.Response{msg=string} "操作成功"
// @Router /admin/app-withdraw-orders/{id}/mark-paid [post]
func (a *AppWithdrawOrdersApi) MarkPaidAppWithdrawOrder(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
response.FailWithMessage("参数错误", c)
return
}
operatorID := utils.GetUserID(c)
if operatorID == 0 {
response.FailWithMessage("未登录", c)
return
}
var req payWithdrawOrderRequest
_ = c.ShouldBindJSON(&req)
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var order appModel.AppWithdrawOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, uint(id)).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("订单不存在")
}
return err
}
if order.Status != appModel.AppWithdrawOrderStatusApproved {
return errors.New("当前状态不可打款确认")
}
var asset appModel.AppUserAsset
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", order.AppUserID).First(&asset).Error; err != nil {
return err
}
if asset.AccountFrozenLi < order.AmountLi {
return errors.New("冻结余额异常")
}
before := asset.AccountBalanceLi
if before < order.AmountLi {
return errors.New("账户余额不足")
}
asset.AccountFrozenLi -= order.AmountLi
asset.AccountBalanceLi -= order.AmountLi
if err := tx.Model(&asset).Select("account_frozen_li", "account_balance_li").Updates(&asset).Error; err != nil {
return err
}
after := asset.AccountBalanceLi
txRec := appModel.AppAssetTransaction{
AppUserID: order.AppUserID,
AssetType: appModel.AppAssetTypeAccount,
RechargeType: appModel.AppRechargeType("withdraw_paid"),
DeltaLi: -order.AmountLi,
BeforeLi: before,
AfterLi: after,
Reason: "提现打款(订单#" + strconv.Itoa(int(order.ID)) + "",
OperatorUserID: operatorID,
CreatedAt: time.Now(),
}
if err := tx.Create(&txRec).Error; err != nil {
return err
}
now := time.Now()
return tx.Model(&order).Updates(map[string]any{
"status": appModel.AppWithdrawOrderStatusPaid,
"paid_by": operatorID,
"paid_at": &now,
"external_tx_id": strings.TrimSpace(req.ExternalTxID),
"operator_user_id": operatorID,
}).Error
}); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.OkWithMessage("已确认打款", c)
}
// MarkFailedAppWithdrawOrder
// @Tags AppWithdrawOrdersAdmin
// @Summary 标记打款失败(会解冻)
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param id path int true "订单ID"
// @Param body body payWithdrawOrderRequest true "参数"
// @Success 200 {object} response.Response{msg=string} "操作成功"
// @Router /admin/app-withdraw-orders/{id}/mark-failed [post]
func (a *AppWithdrawOrdersApi) MarkFailedAppWithdrawOrder(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
response.FailWithMessage("参数错误", c)
return
}
operatorID := utils.GetUserID(c)
if operatorID == 0 {
response.FailWithMessage("未登录", c)
return
}
var req payWithdrawOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage("参数错误", c)
return
}
reason := strings.TrimSpace(req.FailureReason)
if reason == "" {
response.FailWithMessage("failureReason 不能为空", c)
return
}
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var order appModel.AppWithdrawOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, uint(id)).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("订单不存在")
}
return err
}
if order.Status != appModel.AppWithdrawOrderStatusApproved {
return errors.New("当前状态不可标记失败")
}
var asset appModel.AppUserAsset
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", order.AppUserID).First(&asset).Error; err != nil {
return err
}
if asset.AccountFrozenLi < order.AmountLi {
return errors.New("冻结余额异常")
}
asset.AccountFrozenLi -= order.AmountLi
if err := tx.Model(&asset).Select("account_frozen_li").Updates(&asset).Error; err != nil {
return err
}
return tx.Model(&order).Updates(map[string]any{
"status": appModel.AppWithdrawOrderStatusFailed,
"failure_reason": reason,
"external_tx_id": strings.TrimSpace(req.ExternalTxID),
"operator_user_id": operatorID,
}).Error
}); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.OkWithMessage("已标记失败并解冻", c)
}