package luckygift import ( "chatapp3-golang/internal/config" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "net/url" "sort" "strconv" "strings" "time" ) // buildLuckyGiftProviderPayload 构造发往三方的请求体。 func buildLuckyGiftProviderPayload(account config.LuckyGiftProviderConfig, req LuckyGiftDrawRequest) (map[string]any, error) { orderIDs := make([]string, 0, len(req.Orders)) for _, order := range req.Orders { orderIDs = append(orderIDs, luckyGiftProviderOrderID(order.ID)) } payload := map[string]any{ "app_id": account.AppID, "app_channel": account.AppChannel, "room_id": strconv.FormatInt(req.RoomID, 10), "user_id": strconv.FormatInt(req.SendUserID, 10), "gift_id": req.GiftID, "gift_price": req.GiftPrice, "gift_num": req.GiftNum, "gift_is_free": func() int { if req.GiftIsFree { return 1 } return 0 }(), "order_id": orderIDs, "timestamp": time.Now().Unix(), } signature, err := buildLuckyGiftSignature(payload, account.AppKey) if err != nil { return nil, err } payload["signature"] = signature return payload, nil } // resolveLuckyGiftProviderEndpoint 校验并补齐三方接口地址。 func resolveLuckyGiftProviderEndpoint(rawURL string) (string, error) { trimmed := strings.TrimSpace(rawURL) if trimmed == "" { return "", fmt.Errorf("provider url is empty") } parsed, err := url.Parse(trimmed) if err != nil { return "", err } if parsed.Scheme == "" || parsed.Host == "" { return "", fmt.Errorf("provider url must include scheme and host") } const luckyGiftStartGamePath = "/lucky_gift/start_game" path := strings.TrimRight(parsed.Path, "/") if path == "" { parsed.Path = luckyGiftStartGamePath return parsed.String(), nil } if path == luckyGiftStartGamePath { parsed.Path = luckyGiftStartGamePath return parsed.String(), nil } parsed.Path = path + luckyGiftStartGamePath return parsed.String(), nil } // buildLuckyGiftSignature 按约定的 key 排序规则生成签名。 func buildLuckyGiftSignature(payload map[string]any, appKey string) (string, error) { keys := make([]string, 0, len(payload)) for key := range payload { if strings.EqualFold(key, "signature") { continue } keys = append(keys, key) } sort.Strings(keys) parts := make([]string, 0, len(keys)+1) for _, key := range keys { value, err := stringifyLuckyGiftSignatureValue(key, payload[key]) if err != nil { return "", err } parts = append(parts, key+"="+value) } parts = append(parts, "app_key="+strings.TrimSpace(appKey)) sum := md5.Sum([]byte(strings.Join(parts, "&"))) return hex.EncodeToString(sum[:]), nil } // stringifyLuckyGiftSignatureValue 把参与签名的字段值转换成稳定字符串。 func stringifyLuckyGiftSignatureValue(key string, value any) (string, error) { if key == "order_id" { raw, err := json.Marshal(value) if err != nil { return "", err } return url.QueryEscape(string(raw)), nil } switch typed := value.(type) { case string: return typed, nil case int: return strconv.Itoa(typed), nil case int64: return strconv.FormatInt(typed, 10), nil case float64: return strconv.FormatInt(int64(typed), 10), nil case json.Number: return typed.String(), nil default: return strings.TrimSpace(fmt.Sprint(typed)), nil } } // parseLuckyGiftProviderResponse 解析三方响应,并与本地订单列表做对齐校验。 func parseLuckyGiftProviderResponse(raw string, orders []LuckyGiftDrawOrderInput) (int, []LuckyGiftDrawResult, int64, error) { payload, err := decodeLuckyGiftJSON(raw) if err != nil { return 0, nil, 0, fmt.Errorf("decode provider response: %w", err) } code := int(readLuckyGiftInt64(payload["code"])) if code == luckyGiftProviderFuseCode { results := zeroLuckyGiftResults(orders) return code, results, 0, nil } if code != luckyGiftProviderSuccessCode { return code, nil, 0, fmt.Errorf("provider code=%d message=%s", code, readLuckyGiftMessage(payload)) } data := readLuckyGiftMap(payload["data"]) orderList := readLuckyGiftSlice(data["order_list"]) if len(orderList) == 0 { orderList = readLuckyGiftSlice(data["orderList"]) } if len(orderList) == 0 { orderList = readLuckyGiftSlice(data["OrderList"]) } if len(orderList) != len(orders) { return code, nil, 0, fmt.Errorf("provider order_list size mismatch: want=%d got=%d", len(orders), len(orderList)) } parsed := make(map[string]LuckyGiftDrawResult, len(orderList)) for _, item := range orderList { row := readLuckyGiftMap(item) providerOrderID := strings.TrimSpace(readLuckyGiftString(row["order_id"])) if providerOrderID == "" { providerOrderID = strings.TrimSpace(readLuckyGiftString(row["orderId"])) } if providerOrderID == "" { return code, nil, 0, fmt.Errorf("provider order_id is empty") } if _, exists := parsed[providerOrderID]; exists { return code, nil, 0, fmt.Errorf("provider order_id duplicated: %s", providerOrderID) } parsed[providerOrderID] = LuckyGiftDrawResult{ OrderID: providerOrderID, AcceptUserID: readLuckyGiftInt64(row["accept_user_id"]), IsWin: readLuckyGiftBool(row["is_win"], row["isWin"]), RewardNum: readLuckyGiftInt64(firstLuckyGiftValue(row["reward_num"], row["rewardNum"])), } } results := make([]LuckyGiftDrawResult, 0, len(orders)) var rewardTotal int64 for _, order := range orders { providerOrderID := luckyGiftProviderOrderID(order.ID) result, exists := parsed[providerOrderID] if !exists { return code, nil, 0, fmt.Errorf("provider order missing: %s", providerOrderID) } if result.AcceptUserID != 0 && result.AcceptUserID != order.AcceptUserID { return code, nil, 0, fmt.Errorf("provider accept_user_id mismatch for order %s", providerOrderID) } result.ID = order.ID result.AcceptUserID = order.AcceptUserID rewardTotal += result.RewardNum results = append(results, result) } return code, results, rewardTotal, nil } // zeroLuckyGiftResults 在三方熔断或未中奖场景下生成全空中奖结果。 func zeroLuckyGiftResults(orders []LuckyGiftDrawOrderInput) []LuckyGiftDrawResult { results := make([]LuckyGiftDrawResult, 0, len(orders)) for _, order := range orders { results = append(results, LuckyGiftDrawResult{ ID: order.ID, OrderID: luckyGiftProviderOrderID(order.ID), AcceptUserID: order.AcceptUserID, IsWin: false, RewardNum: 0, }) } return results } // luckyGiftProviderOrderID 生成发往三方的订单号。 func luckyGiftProviderOrderID(orderSeed string) string { return "LG_" + strings.TrimSpace(orderSeed) } // luckyGiftWalletEventID 构造钱包幂等事件 ID。 func luckyGiftWalletEventID(sysOrigin, businessID string) string { return fmt.Sprintf("%s:%s:%s", luckyGiftWalletOrigin, strings.ToUpper(strings.TrimSpace(sysOrigin)), strings.TrimSpace(businessID)) } // findLuckyGiftConfig 按档位 ID 选择对应的三方配置。 func findLuckyGiftConfig(configs []config.LuckyGiftProviderConfig, standardID int64) (config.LuckyGiftProviderConfig, bool) { for _, item := range configs { if item.StandardID == standardID { return item, true } } for _, item := range configs { if item.StandardID == 0 { return item, true } } return config.LuckyGiftProviderConfig{}, false } // decodeLuckyGiftJSON 把三方 JSON 报文解析成 map。 func decodeLuckyGiftJSON(raw string) (map[string]any, error) { decoder := json.NewDecoder(strings.NewReader(raw)) decoder.UseNumber() var payload map[string]any if err := decoder.Decode(&payload); err != nil { return nil, err } return payload, nil } // readLuckyGiftMap 安全读取 map 子对象。 func readLuckyGiftMap(value any) map[string]any { if typed, ok := value.(map[string]any); ok { return typed } return map[string]any{} } // readLuckyGiftSlice 安全读取数组子对象。 func readLuckyGiftSlice(value any) []any { if typed, ok := value.([]any); ok { return typed } return nil } // readLuckyGiftString 尽量宽松地读取字符串字段。 func readLuckyGiftString(value any) string { switch typed := value.(type) { case string: return strings.TrimSpace(typed) case json.Number: return typed.String() default: return strings.TrimSpace(fmt.Sprint(typed)) } } // readLuckyGiftInt64 尽量宽松地读取数值字段。 func readLuckyGiftInt64(value any) int64 { switch typed := value.(type) { case int: return int64(typed) case int64: return typed case float64: return int64(typed) case json.Number: parsed, _ := typed.Int64() return parsed case string: parsed, _ := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) return parsed default: parsed, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 64) return parsed } } // readLuckyGiftBool 尽量宽松地读取布尔字段。 func readLuckyGiftBool(values ...any) bool { for _, value := range values { switch typed := value.(type) { case bool: return typed case int: return typed != 0 case int64: return typed != 0 case float64: return typed != 0 case json.Number: parsed, _ := typed.Int64() return parsed != 0 case string: text := strings.TrimSpace(strings.ToLower(typed)) return text == "1" || text == "true" || text == "yes" } } return false } // readLuckyGiftMessage 从不同字段名里兜底读取错误消息。 func readLuckyGiftMessage(payload map[string]any) string { for _, key := range []string{"msg", "message", "errorMsg"} { if value := strings.TrimSpace(readLuckyGiftString(payload[key])); value != "" { return value } } return "" } // firstLuckyGiftValue 返回第一个非 nil 值,便于兼容三方多种字段名。 func firstLuckyGiftValue(values ...any) any { for _, value := range values { if value != nil { return value } } return nil }