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
}