305 lines
8.7 KiB
Go
305 lines
8.7 KiB
Go
package admin
|
|
|
|
import (
|
|
"bindbox-game/internal/code"
|
|
"bindbox-game/internal/pkg/core"
|
|
"bindbox-game/internal/pkg/validation"
|
|
"bindbox-game/internal/service/douyin"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// ---------- 抖店配置 API ----------
|
|
|
|
type getDouyinConfigResponse struct {
|
|
Cookie string `json:"cookie"`
|
|
IntervalMinutes int `json:"interval_minutes"`
|
|
}
|
|
|
|
func (h *handler) GetDouyinConfig() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
cfg, err := h.douyinSvc.GetConfig(ctx.RequestContext())
|
|
if err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
ctx.Payload(getDouyinConfigResponse{
|
|
Cookie: cfg.Cookie,
|
|
IntervalMinutes: cfg.IntervalMinutes,
|
|
})
|
|
}
|
|
}
|
|
|
|
type saveDouyinConfigRequest struct {
|
|
Cookie string `json:"cookie"`
|
|
IntervalMinutes int `json:"interval_minutes"`
|
|
}
|
|
|
|
func (h *handler) SaveDouyinConfig() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
req := new(saveDouyinConfigRequest)
|
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
if err := h.douyinSvc.SaveConfig(ctx.RequestContext(), req.Cookie, req.IntervalMinutes); err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
ctx.Payload(pcSimpleMessage{Message: "保存成功"})
|
|
}
|
|
}
|
|
|
|
// ---------- 抖店订单列表 API ----------
|
|
|
|
type listDouyinOrdersRequest struct {
|
|
Page int `form:"page"`
|
|
PageSize int `form:"page_size"`
|
|
Status *int `form:"status"`
|
|
}
|
|
|
|
type douyinOrderItem struct {
|
|
ID int64 `json:"id"`
|
|
ShopOrderID string `json:"shop_order_id"`
|
|
OrderStatus int32 `json:"order_status"`
|
|
OrderStatusText string `json:"order_status_text"`
|
|
DouyinUserID string `json:"douyin_user_id"`
|
|
LocalUserID int64 `json:"local_user_id"`
|
|
LocalUserNickname string `json:"local_user_nickname"`
|
|
ActualReceiveAmount string `json:"actual_receive_amount"`
|
|
ActualPayAmount string `json:"actual_pay_amount"`
|
|
PayTypeDesc string `json:"pay_type_desc"`
|
|
Remark string `json:"remark"`
|
|
UserNickname string `json:"user_nickname"`
|
|
ProductCount int64 `json:"product_count"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
type listDouyinOrdersResponse struct {
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
Total int64 `json:"total"`
|
|
List []douyinOrderItem `json:"list"`
|
|
}
|
|
|
|
func (h *handler) ListDouyinOrders() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
req := new(listDouyinOrdersRequest)
|
|
if err := ctx.ShouldBindForm(req); err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
orders, total, err := h.douyinSvc.ListOrders(ctx.RequestContext(), req.Page, req.PageSize, req.Status)
|
|
if err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
// 获取匹配用户的昵称
|
|
userNicknameMap := make(map[int64]string)
|
|
var userIDs []int64
|
|
for _, o := range orders {
|
|
if uid, err := strconv.ParseInt(o.LocalUserID, 10, 64); err == nil && uid > 0 {
|
|
userIDs = append(userIDs, uid)
|
|
}
|
|
}
|
|
if len(userIDs) > 0 {
|
|
var users []struct {
|
|
ID int64
|
|
Nickname string
|
|
}
|
|
h.repo.GetDbR().Table("users").Where("id IN ?", userIDs).Select("id, nickname").Find(&users)
|
|
for _, u := range users {
|
|
userNicknameMap[u.ID] = u.Nickname
|
|
}
|
|
}
|
|
|
|
// 构建响应
|
|
list := make([]douyinOrderItem, len(orders))
|
|
for i, o := range orders {
|
|
uid, _ := strconv.ParseInt(o.LocalUserID, 10, 64)
|
|
list[i] = douyinOrderItem{
|
|
ID: o.ID,
|
|
ShopOrderID: o.ShopOrderID,
|
|
OrderStatus: o.OrderStatus,
|
|
OrderStatusText: getOrderStatusText(o.OrderStatus),
|
|
DouyinUserID: o.DouyinUserID,
|
|
LocalUserID: uid,
|
|
LocalUserNickname: userNicknameMap[uid],
|
|
ActualReceiveAmount: formatAmount(o.ActualReceiveAmount),
|
|
ActualPayAmount: formatAmount(o.ActualPayAmount),
|
|
PayTypeDesc: o.PayTypeDesc,
|
|
Remark: o.Remark,
|
|
UserNickname: o.UserNickname,
|
|
ProductCount: int64(o.ProductCount),
|
|
CreatedAt: o.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
}
|
|
}
|
|
|
|
ctx.Payload(listDouyinOrdersResponse{
|
|
Page: req.Page,
|
|
PageSize: req.PageSize,
|
|
Total: total,
|
|
List: list,
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------- 手动同步 API ----------
|
|
|
|
type syncDouyinOrdersResponse struct {
|
|
Message string `json:"message"`
|
|
TotalFetched int `json:"total_fetched"`
|
|
NewOrders int `json:"new_orders"`
|
|
MatchedUsers int `json:"matched_users"`
|
|
}
|
|
|
|
func (h *handler) SyncDouyinOrders() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
// 使用独立 Context 防止 HTTP 请求断开导致同步中断
|
|
// 设置 5 分钟超时,确保有足够时间完成全量同步
|
|
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
result, err := h.douyinSvc.FetchAndSyncOrders(bgCtx)
|
|
if err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
ctx.Payload(syncDouyinOrdersResponse{
|
|
Message: "同步成功",
|
|
TotalFetched: result.TotalFetched,
|
|
NewOrders: result.NewOrders,
|
|
MatchedUsers: result.MatchedUsers,
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------- 新增: 手动同步接口 (优化版) ----------
|
|
|
|
type manualSyncAllRequest struct {
|
|
DurationHours int `json:"duration_hours"` // 可选,默认1小时
|
|
}
|
|
|
|
type manualSyncAllResponse struct {
|
|
Message string `json:"message"`
|
|
DebugInfo string `json:"debug_info"`
|
|
}
|
|
|
|
// ManualSyncAll 手动全量订单同步
|
|
func (h *handler) ManualSyncAll() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
req := new(manualSyncAllRequest)
|
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
|
// 如果没有传参数,使用默认值
|
|
req.DurationHours = 1
|
|
}
|
|
|
|
if req.DurationHours <= 0 {
|
|
req.DurationHours = 1
|
|
}
|
|
|
|
// 使用独立 Context,设置 5 分钟超时
|
|
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
duration := time.Duration(req.DurationHours) * time.Hour
|
|
result, err := h.douyinSvc.SyncAllOrders(bgCtx, duration, true) // 管理后台手动同步使用代理
|
|
if err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
ctx.Payload(manualSyncAllResponse{
|
|
Message: fmt.Sprintf("全量同步成功 (同步范围: %d小时)", req.DurationHours),
|
|
DebugInfo: result.DebugInfo,
|
|
})
|
|
}
|
|
}
|
|
|
|
type manualSyncRefundResponse struct {
|
|
Message string `json:"message"`
|
|
RefundedCount int `json:"refunded_count"`
|
|
}
|
|
|
|
// ManualSyncRefund 手动退款状态同步
|
|
func (h *handler) ManualSyncRefund() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
// 使用独立 Context,设置 3 分钟超时
|
|
bgCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
|
defer cancel()
|
|
|
|
err := h.douyinSvc.SyncRefundStatus(bgCtx)
|
|
if err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
ctx.Payload(manualSyncRefundResponse{
|
|
Message: "退款状态同步成功",
|
|
RefundedCount: 0, // TODO: 可以从 SyncRefundStatus 返回实际退款数量
|
|
})
|
|
}
|
|
}
|
|
|
|
type manualGrantPrizesResponse struct {
|
|
Message string `json:"message"`
|
|
GrantedCount int `json:"granted_count"`
|
|
}
|
|
|
|
// ManualGrantPrizes 手动发放直播间奖品
|
|
func (h *handler) ManualGrantPrizes() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
// 使用独立 Context,设置 3 分钟超时
|
|
bgCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
|
defer cancel()
|
|
|
|
err := h.douyinSvc.GrantLivestreamPrizes(bgCtx)
|
|
if err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
|
|
return
|
|
}
|
|
|
|
ctx.Payload(manualGrantPrizesResponse{
|
|
Message: "直播奖品发放成功",
|
|
GrantedCount: 0, // TODO: 可以从 GrantLivestreamPrizes 返回实际发放数量
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------- 辅助函数 ----------
|
|
|
|
func getOrderStatusText(status int32) string {
|
|
switch status {
|
|
case 1:
|
|
return "待付款"
|
|
case 2:
|
|
return "待发货"
|
|
case 3:
|
|
return "已发货"
|
|
case 4:
|
|
return "已退款/已取消"
|
|
case 5:
|
|
return "已完成"
|
|
default:
|
|
return "未知"
|
|
}
|
|
}
|
|
|
|
func formatAmount(amountCents int64) string {
|
|
yuan := float64(amountCents) / 100.0
|
|
return fmt.Sprintf("%.2f", yuan)
|
|
}
|
|
|
|
// SetDouyinService 设置抖店服务 (用于 handler 初始化)
|
|
func (h *handler) SetDouyinService(svc douyin.Service) {
|
|
h.douyinSvc = svc
|
|
}
|