209 lines
6.1 KiB
Go
209 lines
6.1 KiB
Go
package luckygift
|
|
|
|
import (
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// ensureRequestRecord 创建或复用抽奖主记录,保证流程可恢复。
|
|
func (s *LuckyGiftService) ensureRequestRecord(
|
|
ctx context.Context,
|
|
req LuckyGiftDrawRequest,
|
|
requestJSON string,
|
|
walletEventID string,
|
|
) (*model.LuckyGiftDrawRequestRecord, error) {
|
|
record, err := s.findRequestRecord(ctx, req.BusinessID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now()
|
|
if record == nil {
|
|
id, err := utils.NextID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
record = &model.LuckyGiftDrawRequestRecord{
|
|
ID: id,
|
|
BusinessID: req.BusinessID,
|
|
ConsumeAssetRecordID: req.ConsumeAssetRecordID,
|
|
SysOrigin: req.SysOrigin,
|
|
StandardID: req.StandardID,
|
|
RoomID: req.RoomID,
|
|
SendUserID: req.SendUserID,
|
|
GiftID: req.GiftID,
|
|
GiftPrice: req.GiftPrice,
|
|
GiftNum: req.GiftNum,
|
|
GiftIsFree: req.GiftIsFree,
|
|
WalletEventID: walletEventID,
|
|
Status: luckyGiftDrawStatusPending,
|
|
RequestJSON: requestJSON,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
if err := s.repo.DB.WithContext(ctx).Create(record).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return record, nil
|
|
}
|
|
|
|
updates := map[string]any{
|
|
"consume_asset_record_id": req.ConsumeAssetRecordID,
|
|
"sys_origin": req.SysOrigin,
|
|
"standard_id": req.StandardID,
|
|
"room_id": req.RoomID,
|
|
"send_user_id": req.SendUserID,
|
|
"gift_id": req.GiftID,
|
|
"gift_price": req.GiftPrice,
|
|
"gift_num": req.GiftNum,
|
|
"gift_is_free": req.GiftIsFree,
|
|
"wallet_event_id": walletEventID,
|
|
"request_json": requestJSON,
|
|
"update_time": now,
|
|
}
|
|
if err := s.updateRequestRecord(ctx, req.BusinessID, updates); err != nil {
|
|
return nil, err
|
|
}
|
|
record.ConsumeAssetRecordID = req.ConsumeAssetRecordID
|
|
record.SysOrigin = req.SysOrigin
|
|
record.StandardID = req.StandardID
|
|
record.RoomID = req.RoomID
|
|
record.SendUserID = req.SendUserID
|
|
record.GiftID = req.GiftID
|
|
record.GiftPrice = req.GiftPrice
|
|
record.GiftNum = req.GiftNum
|
|
record.GiftIsFree = req.GiftIsFree
|
|
record.WalletEventID = walletEventID
|
|
record.RequestJSON = requestJSON
|
|
record.UpdateTime = now
|
|
return record, nil
|
|
}
|
|
|
|
// findRequestRecord 按业务 ID 读取抽奖主记录。
|
|
func (s *LuckyGiftService) findRequestRecord(ctx context.Context, businessID string) (*model.LuckyGiftDrawRequestRecord, error) {
|
|
var record model.LuckyGiftDrawRequestRecord
|
|
err := s.repo.DB.WithContext(ctx).Where("business_id = ?", businessID).First(&record).Error
|
|
if err == nil {
|
|
return &record, nil
|
|
}
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// loadSuccessfulResponse 读取已经成功完成的抽奖响应,用于幂等返回。
|
|
func (s *LuckyGiftService) loadSuccessfulResponse(ctx context.Context, businessID string) (*LuckyGiftDrawResponse, error) {
|
|
record, err := s.findRequestRecord(ctx, businessID)
|
|
if err != nil || record == nil {
|
|
return nil, err
|
|
}
|
|
if record.Status != luckyGiftDrawStatusSuccess || strings.TrimSpace(record.ResponseJSON) == "" {
|
|
return nil, nil
|
|
}
|
|
var resp LuckyGiftDrawResponse
|
|
if err := json.Unmarshal([]byte(record.ResponseJSON), &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// updateRequestRecord 局部更新主记录状态或响应信息。
|
|
func (s *LuckyGiftService) updateRequestRecord(ctx context.Context, businessID string, updates map[string]any) error {
|
|
return s.repo.DB.WithContext(ctx).
|
|
Model(&model.LuckyGiftDrawRequestRecord{}).
|
|
Where("business_id = ?", businessID).
|
|
Updates(updates).Error
|
|
}
|
|
|
|
// markRequestFailed 把主记录更新成失败态,并保存错误和三方回包。
|
|
func (s *LuckyGiftService) markRequestFailed(
|
|
ctx context.Context,
|
|
businessID string,
|
|
providerCode int,
|
|
rawResponse string,
|
|
message string,
|
|
) error {
|
|
updates := map[string]any{
|
|
"status": luckyGiftDrawStatusFailed,
|
|
"error_message": truncateLuckyGiftError(message),
|
|
"update_time": time.Now(),
|
|
}
|
|
if providerCode != 0 {
|
|
updates["provider_code"] = providerCode
|
|
}
|
|
if strings.TrimSpace(rawResponse) != "" {
|
|
updates["provider_response_json"] = rawResponse
|
|
}
|
|
return s.updateRequestRecord(ctx, businessID, updates)
|
|
}
|
|
|
|
// truncateLuckyGiftError 截断错误信息,避免超出字段长度。
|
|
func truncateLuckyGiftError(message string) string {
|
|
message = strings.TrimSpace(message)
|
|
if len(message) <= 1024 {
|
|
return message
|
|
}
|
|
return message[:1024]
|
|
}
|
|
|
|
// upsertOrderRecords 批量写入抽奖子单结果,保证同一 businessId 可以重放。
|
|
func (s *LuckyGiftService) upsertOrderRecords(ctx context.Context, businessID string, results []LuckyGiftDrawResult) error {
|
|
if len(results) == 0 {
|
|
return nil
|
|
}
|
|
now := time.Now()
|
|
rows := make([]model.LuckyGiftDrawOrderRecord, 0, len(results))
|
|
for index, item := range results {
|
|
id, err := utils.NextID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rows = append(rows, model.LuckyGiftDrawOrderRecord{
|
|
ID: id,
|
|
BusinessID: businessID,
|
|
OrderSeed: item.ID,
|
|
SortNo: index + 1,
|
|
AcceptUserID: item.AcceptUserID,
|
|
ProviderOrderID: item.OrderID,
|
|
IsWin: item.IsWin,
|
|
RewardNum: item.RewardNum,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
})
|
|
}
|
|
|
|
return s.repo.DB.WithContext(ctx).Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{
|
|
{Name: "business_id"},
|
|
{Name: "order_seed"},
|
|
},
|
|
DoUpdates: clause.AssignmentColumns([]string{
|
|
"sort_no",
|
|
"accept_user_id",
|
|
"provider_order_id",
|
|
"is_win",
|
|
"reward_num",
|
|
"update_time",
|
|
}),
|
|
}).Create(&rows).Error
|
|
}
|
|
|
|
// lockTTL 返回抽奖分布式锁过期时间。
|
|
func (s *LuckyGiftService) lockTTL() time.Duration {
|
|
timeout := s.httpClient.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 8 * time.Second
|
|
}
|
|
if timeout < 10*time.Second {
|
|
timeout = 10 * time.Second
|
|
}
|
|
return timeout + 5*time.Second
|
|
}
|