1253 lines
41 KiB
Go
1253 lines
41 KiB
Go
package integration
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"chatapp3-golang/internal/config"
|
||
)
|
||
|
||
type Client struct {
|
||
cfg config.Config
|
||
httpClient *http.Client
|
||
regionConfigCacheMu sync.Mutex
|
||
regionConfigCacheMap map[string]regionConfigCacheEntry
|
||
}
|
||
|
||
type UserCredential struct {
|
||
UserID Int64Value `json:"userId"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
ExpireTime Int64Value `json:"expireTime"`
|
||
ReleaseTime Int64Value `json:"releaseTime"`
|
||
Version string `json:"version"`
|
||
Sign string `json:"sign"`
|
||
}
|
||
|
||
type ConsoleAccount struct {
|
||
ID Int64Value `json:"id"`
|
||
LoginName string `json:"loginName"`
|
||
Nickname string `json:"nickname"`
|
||
Token string `json:"token"`
|
||
}
|
||
|
||
type UserProfile struct {
|
||
ID Int64Value `json:"id"`
|
||
Account string `json:"account"`
|
||
UserAvatar string `json:"userAvatar"`
|
||
UserNickname string `json:"userNickname"`
|
||
CountryID Int64Value `json:"countryId"`
|
||
CountryCode string `json:"countryCode"`
|
||
CountryName string `json:"countryName"`
|
||
RegionCode string `json:"regionCode"`
|
||
OriginSys string `json:"originSys"`
|
||
AccountStatus string `json:"accountStatus"`
|
||
}
|
||
|
||
type UserRegion struct {
|
||
RegionID string `json:"regionId"`
|
||
RegionCode string `json:"regionCode"`
|
||
}
|
||
|
||
type RegionConfig struct {
|
||
RegionCode string `json:"regionCode"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
RegionName string `json:"regionName"`
|
||
CountryCodes string `json:"countryCodes"`
|
||
Del bool `json:"del"`
|
||
}
|
||
|
||
type regionConfigCacheEntry struct {
|
||
expiresAt time.Time
|
||
items []RegionConfig
|
||
}
|
||
|
||
const regionConfigCacheTTL = time.Minute
|
||
|
||
type RoomContributionActivityCount struct {
|
||
ID string `json:"id"`
|
||
RoomID Int64Value `json:"roomId"`
|
||
ContributionValue AmountValue `json:"contributionValue"`
|
||
DateNumber int `json:"dateNumber"`
|
||
Received bool `json:"received"`
|
||
RewardCoinsSent bool `json:"rewardCoinsSent"`
|
||
}
|
||
|
||
type RoomProfile struct {
|
||
ID Int64Value `json:"id"`
|
||
RoomAccount string `json:"roomAccount"`
|
||
UserID Int64Value `json:"userId"`
|
||
RoomCover string `json:"roomCover"`
|
||
RoomName string `json:"roomName"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
CountryCode string `json:"countryCode"`
|
||
CountryName string `json:"countryName"`
|
||
}
|
||
|
||
type GoldReceiptCommand struct {
|
||
ReceiptType string `json:"receiptType"`
|
||
UserID int64 `json:"userId"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
EventID string `json:"eventId"`
|
||
Remark string `json:"remark,omitempty"`
|
||
Amount PennyAmountPayload `json:"amount"`
|
||
CloseDelayAsset bool `json:"closeDelayAsset"`
|
||
OpUserType string `json:"opUserType"`
|
||
Origin string `json:"origin,omitempty"`
|
||
CustomizeOrigin string `json:"customizeOrigin,omitempty"`
|
||
CustomizeOriginDesc string `json:"customizeOriginDesc,omitempty"`
|
||
}
|
||
|
||
type PennyAmountPayload struct {
|
||
PennyAmount int64 `json:"pennyAmount,string"`
|
||
DollarAmount int64 `json:"dollarAmount"`
|
||
}
|
||
|
||
type TestGoldReceiptCommand struct {
|
||
ReceiptType string `json:"receiptType"`
|
||
UserID int64 `json:"userId"`
|
||
EventType string `json:"eventType"`
|
||
EventDesc string `json:"eventDesc"`
|
||
EventID string `json:"eventId"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
Remark string `json:"remark,omitempty"`
|
||
Amount int64 `json:"amount"`
|
||
CloseDelayAsset bool `json:"closeDelayAsset"`
|
||
}
|
||
|
||
type InviteCode struct {
|
||
UserID Int64Value `json:"userId"`
|
||
InviteCode string `json:"inviteCode"`
|
||
HasBound bool `json:"hasBound"`
|
||
}
|
||
|
||
type GrantGoldRequest struct {
|
||
UserID int64 `json:"userId"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
EventID string `json:"eventId"`
|
||
Origin string `json:"origin"`
|
||
GoldAmount int64 `json:"goldAmount"`
|
||
Remark string `json:"remark"`
|
||
}
|
||
|
||
type GrantPropsRequest struct {
|
||
TrackID int64 `json:"trackId"`
|
||
AcceptUserID int64 `json:"acceptUserId"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
Origin string `json:"origin"`
|
||
SourceGroupID int64 `json:"sourceGroupId"`
|
||
}
|
||
|
||
type GivePropsBackpackRequest struct {
|
||
AcceptUserID int64 `json:"acceptUserId"`
|
||
PropsID int64 `json:"propsId"`
|
||
Type string `json:"type"`
|
||
Origin string `json:"origin"`
|
||
OriginDesc string `json:"originDesc"`
|
||
Days int `json:"days"`
|
||
UseProps bool `json:"useProps"`
|
||
AllowGive bool `json:"allowGive"`
|
||
OpUser int64 `json:"opUser,omitempty"`
|
||
}
|
||
|
||
type PropsNobleVIPAbility struct {
|
||
ID Int64Value `json:"id"`
|
||
VIPType string `json:"vipType"`
|
||
VIPLevel int `json:"vipLevel"`
|
||
AdminNumber int `json:"adminNumber"`
|
||
RoomMaxMember int `json:"roomMaxMember"`
|
||
AvatarFrameID Int64Value `json:"avatarFrameId"`
|
||
CarID Int64Value `json:"carId"`
|
||
ChatBubbleID Int64Value `json:"chatBubbleId"`
|
||
DataCardID Int64Value `json:"dataCardId"`
|
||
}
|
||
|
||
type PunishAccountRequest struct {
|
||
BeOptUserID int64 `json:"beOptUserId"`
|
||
OptUserID int64 `json:"optUserId"`
|
||
Status string `json:"status"`
|
||
Description string `json:"description"`
|
||
}
|
||
|
||
type UnblockAccountRequest struct {
|
||
BeOptUserID int64 `json:"beOptUserId"`
|
||
OptUserID int64 `json:"optUserId"`
|
||
Description string `json:"description"`
|
||
}
|
||
|
||
type SendActivityRewardRequest struct {
|
||
TrackID int64 `json:"trackId"`
|
||
Origin string `json:"origin"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
SourceGroupID int64 `json:"sourceGroupId"`
|
||
AcceptUserID int64 `json:"acceptUserId"`
|
||
}
|
||
|
||
type TeamPolicyReleaseQuery struct {
|
||
SysOrigin string
|
||
Region string
|
||
PolicyType string
|
||
CountryCode string
|
||
}
|
||
|
||
type TeamPolicyRelease struct {
|
||
ID Int64Value `json:"id"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
Region string `json:"region"`
|
||
CountryCode string `json:"countryCode"`
|
||
Type string `json:"type"`
|
||
HistoryRelease bool `json:"historyRelease"`
|
||
Release bool `json:"release"`
|
||
Title string `json:"title"`
|
||
Policy []TeamPolicyItem `json:"policy"`
|
||
PolicyType string `json:"policyType"`
|
||
}
|
||
|
||
type TeamPolicyItem struct {
|
||
Level int `json:"level"`
|
||
OnlineTime Int64Value `json:"onlineTime"`
|
||
EffectiveDay int `json:"effectiveDay"`
|
||
Target Int64Value `json:"target"`
|
||
MemberSalary DecimalString `json:"memberSalary"`
|
||
OwnSalary DecimalString `json:"ownSalary"`
|
||
TotalSalary DecimalString `json:"totalSalary"`
|
||
PropsRewards any `json:"propsRewards"`
|
||
}
|
||
|
||
// OfficialNoticeCustomizeRequest 表示发送自定义官方通知的请求体。
|
||
type OfficialNoticeCustomizeRequest struct {
|
||
ToAccounts []int64 `json:"toAccounts"`
|
||
LangCode string `json:"langCode,omitempty"`
|
||
NoticeType string `json:"noticeType"`
|
||
Expand any `json:"expand,omitempty"`
|
||
Title string `json:"title"`
|
||
Content string `json:"content"`
|
||
}
|
||
|
||
type RewardGroupItem struct {
|
||
ID Int64Value `json:"id"`
|
||
Type string `json:"type"`
|
||
DetailType string `json:"detailType"`
|
||
Name string `json:"name"`
|
||
Content string `json:"content"`
|
||
Quantity Int64Value `json:"quantity"`
|
||
Cover string `json:"cover"`
|
||
SourceURL string `json:"sourceUrl"`
|
||
Remark string `json:"remark"`
|
||
}
|
||
|
||
type RewardGroupDetail struct {
|
||
ID Int64Value `json:"id"`
|
||
Name string `json:"name"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
RewardConfigList []RewardGroupItem `json:"rewardConfigList"`
|
||
}
|
||
|
||
type UserTotalRecharge struct {
|
||
ID Int64Value `json:"id"`
|
||
UserID Int64Value `json:"userId"`
|
||
Type string `json:"type"`
|
||
Amount DecimalString `json:"amount"`
|
||
CreateTime StringValue `json:"createTime"`
|
||
UpdateTime StringValue `json:"updateTime"`
|
||
}
|
||
|
||
type FreightSellerPage struct {
|
||
Records []FreightSellerRecord `json:"records"`
|
||
Total int64 `json:"total"`
|
||
Current int `json:"current"`
|
||
Size int `json:"size"`
|
||
}
|
||
|
||
type FreightSellerRecord struct {
|
||
ID Int64Value `json:"id"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
UserID Int64Value `json:"userId"`
|
||
EarnPoints DecimalString `json:"earnPoints"`
|
||
ConsumptionPoints DecimalString `json:"consumptionPoints"`
|
||
Balance DecimalString `json:"balance"`
|
||
Close bool `json:"close"`
|
||
Display bool `json:"display"`
|
||
Dealer bool `json:"dealer"`
|
||
SuperDealer bool `json:"superDealer"`
|
||
SupportedCountries string `json:"supportedCountries"`
|
||
ContactInfo string `json:"contactInfo"`
|
||
H5Display bool `json:"h5Display"`
|
||
SellerQuantity int `json:"sellerQuantity"`
|
||
RealSellerQuantity int64 `json:"realSellerQuantity"`
|
||
BecomeDays int `json:"becomeDays"`
|
||
TransactionCount int64 `json:"transactionCount"`
|
||
IsFollow bool `json:"isFollow"`
|
||
UserBaseInfo FreightSellerProfile `json:"userBaseInfo"`
|
||
NationalFlag []string `json:"nationalFlag"`
|
||
OwnNationalFlag string `json:"ownNationalFlag"`
|
||
}
|
||
|
||
type FreightSellerProfile struct {
|
||
ID Int64Value `json:"id"`
|
||
Account string `json:"account"`
|
||
UserAvatar string `json:"userAvatar"`
|
||
UserNickname string `json:"userNickname"`
|
||
CountryCode string `json:"countryCode"`
|
||
CountryName string `json:"countryName"`
|
||
RegionCode string `json:"regionCode"`
|
||
OriginSys string `json:"originSys"`
|
||
}
|
||
|
||
type FreightBalanceChangeRequest struct {
|
||
SysOrigin string `json:"sysOrigin"`
|
||
UserID int64 `json:"userId"`
|
||
AcceptUserID int64 `json:"acceptUserId"`
|
||
Type string `json:"type"`
|
||
AcceptAmount string `json:"acceptAmount"`
|
||
OrderAmount string `json:"orderAmount"`
|
||
USDAmount string `json:"usdAmount"`
|
||
Origin string `json:"origin"`
|
||
Remark string `json:"remark"`
|
||
RechargeType string `json:"rechargeType"`
|
||
CreateUser int64 `json:"createUser"`
|
||
}
|
||
|
||
type FreightAgentReviewRequest struct {
|
||
SysOrigin string `json:"sysOrigin"`
|
||
UserID int64 `json:"userId"`
|
||
}
|
||
|
||
type freightSellerPageResponse struct {
|
||
Status *bool `json:"status"`
|
||
ErrorMsg string `json:"errorMsg"`
|
||
Message string `json:"message"`
|
||
Body *FreightSellerPage `json:"body"`
|
||
Data *FreightSellerPage `json:"data"`
|
||
FreightSellerPage
|
||
}
|
||
|
||
type resultResponse[T any] struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Body T `json:"body"`
|
||
Success *bool `json:"success"`
|
||
}
|
||
|
||
type Int64Value int64
|
||
|
||
func (v *Int64Value) UnmarshalJSON(data []byte) error {
|
||
raw := strings.TrimSpace(string(data))
|
||
if raw == "" || raw == "null" {
|
||
*v = 0
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
|
||
raw = strings.Trim(raw, "\"")
|
||
}
|
||
parsed, err := strconv.ParseInt(raw, 10, 64)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
*v = Int64Value(parsed)
|
||
return nil
|
||
}
|
||
|
||
type AmountValue int64
|
||
|
||
func (v *AmountValue) UnmarshalJSON(data []byte) error {
|
||
raw := strings.TrimSpace(string(data))
|
||
if raw == "" || raw == "null" {
|
||
*v = 0
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
|
||
raw = strings.Trim(raw, "\"")
|
||
}
|
||
if raw == "" {
|
||
*v = 0
|
||
return nil
|
||
}
|
||
floatRaw := raw
|
||
if dot := strings.Index(raw, "."); dot >= 0 {
|
||
raw = raw[:dot]
|
||
}
|
||
if parsed, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
||
*v = AmountValue(parsed)
|
||
return nil
|
||
}
|
||
parsed, err := strconv.ParseFloat(floatRaw, 64)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
*v = AmountValue(int64(parsed))
|
||
return nil
|
||
}
|
||
|
||
type DecimalString string
|
||
|
||
func (v *DecimalString) UnmarshalJSON(data []byte) error {
|
||
raw := strings.TrimSpace(string(data))
|
||
if raw == "" || raw == "null" {
|
||
*v = ""
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
|
||
var parsed string
|
||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||
return err
|
||
}
|
||
*v = DecimalString(parsed)
|
||
return nil
|
||
}
|
||
*v = DecimalString(raw)
|
||
return nil
|
||
}
|
||
|
||
type StringValue string
|
||
|
||
func (v *StringValue) UnmarshalJSON(data []byte) error {
|
||
raw := strings.TrimSpace(string(data))
|
||
if raw == "" || raw == "null" {
|
||
*v = ""
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
|
||
var parsed string
|
||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||
return err
|
||
}
|
||
*v = StringValue(parsed)
|
||
return nil
|
||
}
|
||
*v = StringValue(raw)
|
||
return nil
|
||
}
|
||
|
||
func New(cfg config.Config) *Client {
|
||
return &Client{
|
||
cfg: cfg,
|
||
httpClient: &http.Client{
|
||
Timeout: cfg.HTTP.Timeout,
|
||
},
|
||
regionConfigCacheMap: map[string]regionConfigCacheEntry{},
|
||
}
|
||
}
|
||
|
||
func (c *Client) AuthenticateToken(ctx context.Context, token string) (UserCredential, error) {
|
||
tokenCredential, err := parseUserCredentialToken(token)
|
||
if err != nil {
|
||
return UserCredential{}, err
|
||
}
|
||
|
||
endpoint := c.cfg.Java.AuthBaseURL + "/auth/client/getUserCredential?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(int64(tokenCredential.UserID), 10))
|
||
var resp resultResponse[UserCredential]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return UserCredential{}, err
|
||
}
|
||
if int64(resp.Body.UserID) == 0 {
|
||
return UserCredential{}, fmt.Errorf("empty credential response")
|
||
}
|
||
if strings.TrimSpace(resp.Body.Sign) == "" {
|
||
return UserCredential{}, fmt.Errorf("empty credential sign")
|
||
}
|
||
if !strings.EqualFold(strings.TrimSpace(resp.Body.Sign), strings.TrimSpace(tokenCredential.Sign)) {
|
||
return UserCredential{}, fmt.Errorf("credential sign mismatch")
|
||
}
|
||
if strings.TrimSpace(resp.Body.SysOrigin) != "" && !strings.EqualFold(resp.Body.SysOrigin, tokenCredential.SysOrigin) {
|
||
return UserCredential{}, fmt.Errorf("credential sysOrigin mismatch")
|
||
}
|
||
return tokenCredential, nil
|
||
}
|
||
|
||
func (c *Client) CreateIfAbsentUserCredentialToken(ctx context.Context, userID int64) (string, error) {
|
||
endpoint := c.cfg.Java.AuthBaseURL + "/auth/client/createIfAbsentUserCredentialToken?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[string]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return "", err
|
||
}
|
||
if strings.TrimSpace(resp.Body) == "" {
|
||
return "", fmt.Errorf("empty token response")
|
||
}
|
||
return strings.TrimSpace(resp.Body), nil
|
||
}
|
||
|
||
// AuthenticateConsoleToken 校验后台 console Bearer token,并返回管理员基础信息。
|
||
func (c *Client) AuthenticateConsoleToken(ctx context.Context, authorization string) (ConsoleAccount, error) {
|
||
authorization = strings.TrimSpace(authorization)
|
||
if authorization == "" {
|
||
return ConsoleAccount{}, fmt.Errorf("authorization is blank")
|
||
}
|
||
|
||
endpoint := strings.TrimRight(c.cfg.Java.ConsoleBaseURL, "/") + "/account/info"
|
||
headers := javaConsoleHeaders(authorization)
|
||
|
||
var resp resultResponse[ConsoleAccount]
|
||
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
|
||
return ConsoleAccount{}, err
|
||
}
|
||
if int64(resp.Body.ID) == 0 {
|
||
return ConsoleAccount{}, fmt.Errorf("empty console account response")
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func javaConsoleHeaders(authorization string) http.Header {
|
||
headers := http.Header{}
|
||
headers.Set("Authorization", strings.TrimSpace(authorization))
|
||
headers.Set("Req-Sys-Origin", "origin=ops;originChild=ops;")
|
||
headers.Set("Req-Client", "Ops")
|
||
headers.Set("Req-App-Intel", "version=1.0.0;build=1;channel=ops;")
|
||
headers.Set("Req-Version", "v2")
|
||
headers.Set("Req-Lang", "zh-CN")
|
||
headers.Set("Req-Zone", "Asia/Shanghai")
|
||
return headers
|
||
}
|
||
|
||
func parseUserCredentialToken(token string) (UserCredential, error) {
|
||
token = strings.TrimSpace(token)
|
||
if token == "" {
|
||
return UserCredential{}, fmt.Errorf("token is blank")
|
||
}
|
||
|
||
parts := strings.SplitN(token, ".", 2)
|
||
if len(parts) != 2 {
|
||
return UserCredential{}, fmt.Errorf("invalid token format")
|
||
}
|
||
|
||
sign := strings.TrimSpace(parts[0])
|
||
if sign == "" {
|
||
return UserCredential{}, fmt.Errorf("invalid token sign")
|
||
}
|
||
|
||
decoded, err := base64.StdEncoding.DecodeString(parts[1])
|
||
if err != nil {
|
||
return UserCredential{}, fmt.Errorf("decode token payload: %w", err)
|
||
}
|
||
|
||
payload, err := url.QueryUnescape(string(decoded))
|
||
if err != nil {
|
||
return UserCredential{}, fmt.Errorf("unescape token payload: %w", err)
|
||
}
|
||
|
||
items := strings.Split(payload, ":")
|
||
if len(items) != 5 {
|
||
return UserCredential{}, fmt.Errorf("invalid token payload")
|
||
}
|
||
|
||
userID, err := strconv.ParseInt(strings.TrimSpace(items[1]), 10, 64)
|
||
if err != nil {
|
||
return UserCredential{}, fmt.Errorf("invalid token userId: %w", err)
|
||
}
|
||
expireTime, err := strconv.ParseInt(strings.TrimSpace(items[3]), 10, 64)
|
||
if err != nil {
|
||
return UserCredential{}, fmt.Errorf("invalid token expireTime: %w", err)
|
||
}
|
||
releaseTime, err := strconv.ParseInt(strings.TrimSpace(items[4]), 10, 64)
|
||
if err != nil {
|
||
return UserCredential{}, fmt.Errorf("invalid token releaseTime: %w", err)
|
||
}
|
||
if expireTime <= time.Now().UnixMilli() {
|
||
return UserCredential{}, fmt.Errorf("token expired")
|
||
}
|
||
|
||
return UserCredential{
|
||
UserID: Int64Value(userID),
|
||
SysOrigin: strings.TrimSpace(items[2]),
|
||
ExpireTime: Int64Value(expireTime),
|
||
ReleaseTime: Int64Value(releaseTime),
|
||
Version: strings.TrimSpace(items[0]),
|
||
Sign: sign,
|
||
}, nil
|
||
}
|
||
|
||
func (c *Client) GetDeviceFingerprint(ctx context.Context, userID int64) (string, error) {
|
||
endpoint := c.cfg.Java.DeviceBaseURL + "/user/device/fingerprint?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[string]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return "", err
|
||
}
|
||
return strings.TrimSpace(resp.Body), nil
|
||
}
|
||
|
||
func (c *Client) EnsureInviteCode(ctx context.Context, authorization string) (InviteCode, error) {
|
||
endpoint := c.cfg.Java.AppBaseURL + "/activity/invite/user/my/invite/code"
|
||
headers := javaAppHeadersFromAuthorization(authorization)
|
||
if authorization != "" {
|
||
headers.Set("Authorization", authorization)
|
||
}
|
||
var resp resultResponse[InviteCode]
|
||
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
|
||
return InviteCode{}, err
|
||
}
|
||
if strings.TrimSpace(resp.Body.InviteCode) == "" {
|
||
return InviteCode{}, fmt.Errorf("invite code response is empty")
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func javaAppHeadersFromAuthorization(authorization string) http.Header {
|
||
headers := http.Header{}
|
||
token := trimBearerToken(authorization)
|
||
sysOrigin := "LIKEI"
|
||
var userID int64
|
||
if credential, err := parseUserCredentialToken(token); err == nil {
|
||
userID = int64(credential.UserID)
|
||
if strings.TrimSpace(credential.SysOrigin) != "" {
|
||
sysOrigin = strings.TrimSpace(credential.SysOrigin)
|
||
}
|
||
}
|
||
|
||
headers.Set("Req-Sys-Origin", "origin="+sysOrigin+";originChild="+sysOrigin)
|
||
headers.Set("Req-Client", "Android")
|
||
headers.Set("Req-App-Intel", "version=1.0.0;build=100;channel=local;model=go-service;sysVersion=go")
|
||
headers.Set("Req-Zone", "Asia/Shanghai")
|
||
headers.Set("Req-Imei", "go-service")
|
||
if userID > 0 {
|
||
headers.Set("Req-Inner-Internal", fmt.Sprintf("time=%d;uid=%d;", time.Now().UnixMilli(), userID))
|
||
} else {
|
||
headers.Set("Req-Inner-Internal", fmt.Sprintf("time=%d;", time.Now().UnixMilli()))
|
||
}
|
||
return headers
|
||
}
|
||
|
||
func trimBearerToken(authorization string) string {
|
||
token := strings.TrimSpace(authorization)
|
||
if len(token) >= 7 && strings.EqualFold(token[:7], "Bearer ") {
|
||
return strings.TrimSpace(token[7:])
|
||
}
|
||
return token
|
||
}
|
||
|
||
func (c *Client) GrantGold(ctx context.Context, req GrantGoldRequest) error {
|
||
return c.postJSON(ctx, c.rewardBridgeBaseURL()+"/internal/resident-activity/invite/grant-gold", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) GrantProps(ctx context.Context, req GrantPropsRequest) error {
|
||
return c.postJSON(ctx, c.rewardBridgeBaseURL()+"/internal/resident-activity/invite/grant-props", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) SendActivityReward(ctx context.Context, req SendActivityRewardRequest) error {
|
||
return c.postJSON(ctx, c.cfg.Java.OtherBaseURL+"/props-activity-cnf/client/sendActivityReward", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) GivePropsBackpack(ctx context.Context, req GivePropsBackpackRequest) error {
|
||
return c.postJSON(ctx, c.cfg.Java.OtherBaseURL+"/props/client/giveProps", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) IncrGiftBackpack(ctx context.Context, userID int64, giftID int64, quantity int) error {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/gift-backpack/client/incrGift?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10)) +
|
||
"&giftId=" + url.QueryEscape(strconv.FormatInt(giftID, 10)) +
|
||
"&quantity=" + url.QueryEscape(strconv.Itoa(quantity))
|
||
return c.getJSON(ctx, endpoint, nil, nil)
|
||
}
|
||
|
||
func (c *Client) GetNobleVIPAbility(ctx context.Context, sourceID int64) (PropsNobleVIPAbility, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/props/noble-vip/client/getAbilityDTO?sourceId=" +
|
||
url.QueryEscape(strconv.FormatInt(sourceID, 10))
|
||
var resp resultResponse[PropsNobleVIPAbility]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return PropsNobleVIPAbility{}, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) GetUserMaxNobleVIPAbility(ctx context.Context, userID int64) (PropsNobleVIPAbility, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/props/noble-vip/client/getUserMaxAbilityDTO?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[PropsNobleVIPAbility]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return PropsNobleVIPAbility{}, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) SwitchUseProps(ctx context.Context, userID int64, propsID int64) error {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/props/client/switch/use-props?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10)) +
|
||
"&propsId=" + url.QueryEscape(strconv.FormatInt(propsID, 10))
|
||
return c.getJSON(ctx, endpoint, nil, nil)
|
||
}
|
||
|
||
func (c *Client) ActivateTemporaryBadge(ctx context.Context, userID int64, badgeID int64, days int) error {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/user-badge/client/activationTemporaryAndUse?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10)) +
|
||
"&badgeId=" + url.QueryEscape(strconv.FormatInt(badgeID, 10)) +
|
||
"&days=" + url.QueryEscape(strconv.Itoa(days))
|
||
var resp resultResponse[bool]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return err
|
||
}
|
||
if !resp.Body {
|
||
return fmt.Errorf("activate temporary badge returned false")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SendOfficialNoticeCustomize 调用 external 服务发送自定义官方通知。
|
||
func (c *Client) SendOfficialNoticeCustomize(ctx context.Context, req OfficialNoticeCustomizeRequest) error {
|
||
return c.postJSON(ctx, c.cfg.Java.ExternalBaseURL+"/official-notice/client/send/customize/template", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) GetRewardGroupDetail(ctx context.Context, groupID int64) (RewardGroupDetail, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/props-activity/reward/group/client/getByGroupId?id=" + url.QueryEscape(strconv.FormatInt(groupID, 10))
|
||
var resp resultResponse[RewardGroupDetail]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return RewardGroupDetail{}, err
|
||
}
|
||
if int64(resp.Body.ID) == 0 {
|
||
return RewardGroupDetail{}, fmt.Errorf("empty reward group response")
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) MapUserTotalRecharge(ctx context.Context, userIDs []int64) (map[int64][]UserTotalRecharge, error) {
|
||
if len(userIDs) == 0 {
|
||
return map[int64][]UserTotalRecharge{}, nil
|
||
}
|
||
endpoint := c.cfg.Java.OrderBaseURL + "/total/recharge/client/mapGroup"
|
||
var resp resultResponse[map[string][]UserTotalRecharge]
|
||
if err := c.postJSON(ctx, endpoint, userIDs, nil, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
result := make(map[int64][]UserTotalRecharge, len(resp.Body))
|
||
for key, value := range resp.Body {
|
||
userID, err := strconv.ParseInt(strings.TrimSpace(key), 10, 64)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
result[userID] = value
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (c *Client) GetThisMonthTotalPersonalRecharge(ctx context.Context, userID int64) (DecimalString, error) {
|
||
if userID <= 0 {
|
||
return DecimalString("0"), nil
|
||
}
|
||
endpoint := c.cfg.Java.OrderBaseURL + "/user-recharge-count/client/getThisMonthTotalPersonalRecharge?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[DecimalString]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return "", err
|
||
}
|
||
if strings.TrimSpace(string(resp.Body)) == "" {
|
||
return DecimalString("0"), nil
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) PageFreightSellers(ctx context.Context, authorization string, sysOrigin string, cursor int, limit int) (FreightSellerPage, error) {
|
||
values := url.Values{}
|
||
values.Set("sysOrigin", strings.TrimSpace(sysOrigin))
|
||
values.Set("dealer", "true")
|
||
values.Set("cursor", strconv.Itoa(cursor))
|
||
values.Set("limit", strconv.Itoa(limit))
|
||
|
||
endpoint := strings.TrimRight(c.cfg.Java.OtherBaseURL, "/") + "/user/freight-balance/page?" + values.Encode()
|
||
headers := javaAppHeadersFromAuthorization(authorization)
|
||
if strings.TrimSpace(authorization) != "" {
|
||
headers.Set("Authorization", strings.TrimSpace(authorization))
|
||
}
|
||
|
||
var resp freightSellerPageResponse
|
||
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
|
||
return FreightSellerPage{}, err
|
||
}
|
||
if resp.Status != nil && !*resp.Status {
|
||
message := strings.TrimSpace(resp.ErrorMsg)
|
||
if message == "" {
|
||
message = strings.TrimSpace(resp.Message)
|
||
}
|
||
if message == "" {
|
||
message = "freight seller page failed"
|
||
}
|
||
return FreightSellerPage{}, errors.New(message)
|
||
}
|
||
if resp.Body != nil {
|
||
return *resp.Body, nil
|
||
}
|
||
if resp.Data != nil {
|
||
return *resp.Data, nil
|
||
}
|
||
return resp.FreightSellerPage, nil
|
||
}
|
||
|
||
func (c *Client) ExistsFreightBalance(ctx context.Context, userID int64) (bool, error) {
|
||
if userID <= 0 {
|
||
return false, nil
|
||
}
|
||
values := url.Values{}
|
||
values.Set("userId", strconv.FormatInt(userID, 10))
|
||
endpoint := strings.TrimRight(c.cfg.Java.WalletBaseURL, "/") + "/freight-gold/client/existsBalance?" + values.Encode()
|
||
var resp resultResponse[bool]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return false, err
|
||
}
|
||
if resp.Success != nil && !*resp.Success {
|
||
return false, errors.New(strings.TrimSpace(resp.Message))
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) ExistsFreightSeller(ctx context.Context, userID int64) (bool, error) {
|
||
if userID <= 0 {
|
||
return false, nil
|
||
}
|
||
values := url.Values{}
|
||
values.Set("userId", strconv.FormatInt(userID, 10))
|
||
endpoint := strings.TrimRight(c.cfg.Java.WalletBaseURL, "/") + "/freight-gold/client/existsSeller?" + values.Encode()
|
||
var resp resultResponse[bool]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return false, err
|
||
}
|
||
if resp.Success != nil && !*resp.Success {
|
||
return false, errors.New(strings.TrimSpace(resp.Message))
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) ChangeFreightBalance(ctx context.Context, req FreightBalanceChangeRequest) error {
|
||
endpoint := strings.TrimRight(c.cfg.Java.WalletBaseURL, "/") + "/freight-gold/client/changeBalance"
|
||
var resp resultResponse[any]
|
||
if err := c.postJSON(ctx, endpoint, req, nil, &resp); err != nil {
|
||
return err
|
||
}
|
||
if resp.Success != nil && !*resp.Success {
|
||
message := strings.TrimSpace(resp.Message)
|
||
if message == "" {
|
||
message = "freight balance change failed"
|
||
}
|
||
return errors.New(message)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (c *Client) AddFreightAgentReview(ctx context.Context, req FreightAgentReviewRequest) error {
|
||
endpoint := strings.TrimRight(c.cfg.Java.WalletBaseURL, "/") + "/freight-gold/client/addFreightAgentReview"
|
||
var resp resultResponse[any]
|
||
if err := c.postJSON(ctx, endpoint, req, nil, &resp); err != nil {
|
||
return err
|
||
}
|
||
if resp.Success != nil && !*resp.Success {
|
||
message := strings.TrimSpace(resp.Message)
|
||
if message == "" {
|
||
message = "freight agent review add failed"
|
||
}
|
||
return errors.New(message)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (c *Client) IncreaseFreightRechargeRecord(ctx context.Context, userID int64, amount string) error {
|
||
if userID <= 0 {
|
||
return fmt.Errorf("userId is required")
|
||
}
|
||
values := url.Values{}
|
||
values.Set("userId", strconv.FormatInt(userID, 10))
|
||
values.Set("amount", strings.TrimSpace(amount))
|
||
endpoint := strings.TrimRight(c.cfg.Java.OrderBaseURL, "/") + "/user/freight/recharge-record/client/inrNowMonthAmount?" + values.Encode()
|
||
var resp resultResponse[DecimalString]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return err
|
||
}
|
||
if resp.Success != nil && !*resp.Success {
|
||
message := strings.TrimSpace(resp.Message)
|
||
if message == "" {
|
||
message = "freight recharge record increase failed"
|
||
}
|
||
return errors.New(message)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByUserId?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[UserProfile]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return UserProfile{}, err
|
||
}
|
||
if int64(resp.Body.ID) == 0 {
|
||
return UserProfile{}, fmt.Errorf("empty user profile response")
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) GetUserRegion(ctx context.Context, userID int64, authorization string) (UserRegion, error) {
|
||
if userID <= 0 {
|
||
return UserRegion{}, fmt.Errorf("userId is required")
|
||
}
|
||
endpoint := c.cfg.Java.AppBaseURL + "/user/user-region?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
headers := javaAppHeadersFromAuthorization(authorization)
|
||
if strings.TrimSpace(authorization) != "" {
|
||
headers.Set("Authorization", authorization)
|
||
}
|
||
var resp resultResponse[UserRegion]
|
||
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
|
||
return UserRegion{}, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) ResolveRegionCodeByCountryCode(ctx context.Context, sysOrigin string, countryCode string) (string, error) {
|
||
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
|
||
if countryCode == "" {
|
||
return "", nil
|
||
}
|
||
configs, err := c.ListRegionConfigs(ctx, sysOrigin)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
for _, item := range configs {
|
||
if item.Del {
|
||
continue
|
||
}
|
||
if !regionConfigContainsCountryCode(item.CountryCodes, countryCode) {
|
||
continue
|
||
}
|
||
regionCode := strings.TrimSpace(item.RegionCode)
|
||
if regionCode != "" {
|
||
return regionCode, nil
|
||
}
|
||
}
|
||
return "", nil
|
||
}
|
||
|
||
func (c *Client) ListRegionConfigs(ctx context.Context, sysOrigin string) ([]RegionConfig, error) {
|
||
sysOrigin = strings.ToUpper(strings.TrimSpace(sysOrigin))
|
||
if sysOrigin == "" {
|
||
sysOrigin = "LIKEI"
|
||
}
|
||
now := time.Now()
|
||
c.regionConfigCacheMu.Lock()
|
||
if entry, ok := c.regionConfigCacheMap[sysOrigin]; ok && now.Before(entry.expiresAt) {
|
||
items := append([]RegionConfig(nil), entry.items...)
|
||
c.regionConfigCacheMu.Unlock()
|
||
return items, nil
|
||
}
|
||
c.regionConfigCacheMu.Unlock()
|
||
|
||
endpoint := strings.TrimRight(c.cfg.Java.OtherBaseURL, "/") + "/region-config/client/listByCondition"
|
||
var resp resultResponse[[]RegionConfig]
|
||
if err := c.postJSON(ctx, endpoint, map[string]string{"sysOrigin": sysOrigin}, nil, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
items := append([]RegionConfig(nil), resp.Body...)
|
||
c.regionConfigCacheMu.Lock()
|
||
if c.regionConfigCacheMap == nil {
|
||
c.regionConfigCacheMap = map[string]regionConfigCacheEntry{}
|
||
}
|
||
c.regionConfigCacheMap[sysOrigin] = regionConfigCacheEntry{
|
||
expiresAt: now.Add(regionConfigCacheTTL),
|
||
items: append([]RegionConfig(nil), items...),
|
||
}
|
||
c.regionConfigCacheMu.Unlock()
|
||
return items, nil
|
||
}
|
||
|
||
func regionConfigContainsCountryCode(countryCodes string, countryCode string) bool {
|
||
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
|
||
if countryCode == "" {
|
||
return false
|
||
}
|
||
for _, item := range strings.Split(countryCodes, ",") {
|
||
if strings.ToUpper(strings.TrimSpace(item)) == countryCode {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (c *Client) GetUserProfileByAccount(ctx context.Context, sysOrigin, account string) (UserProfile, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByAccount?sysOrigin=" +
|
||
url.QueryEscape(strings.TrimSpace(sysOrigin)) + "&account=" + url.QueryEscape(strings.TrimSpace(account))
|
||
var resp resultResponse[UserProfile]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return UserProfile{}, err
|
||
}
|
||
if int64(resp.Body.ID) == 0 {
|
||
return UserProfile{}, fmt.Errorf("empty user profile response")
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) GetUserProfileByAccountOne(ctx context.Context, account string) (UserProfile, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByAccountOne?account=" +
|
||
url.QueryEscape(strings.TrimSpace(account))
|
||
var resp resultResponse[UserProfile]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return UserProfile{}, err
|
||
}
|
||
if int64(resp.Body.ID) == 0 {
|
||
return UserProfile{}, fmt.Errorf("empty user profile response")
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) GetTeamPolicyRelease(ctx context.Context, query TeamPolicyReleaseQuery) (TeamPolicyRelease, error) {
|
||
values := url.Values{}
|
||
values.Set("sysOrigin", strings.TrimSpace(query.SysOrigin))
|
||
values.Set("region", strings.TrimSpace(query.Region))
|
||
values.Set("policyType", strings.TrimSpace(query.PolicyType))
|
||
values.Set("countryCode", strings.TrimSpace(query.CountryCode))
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/team-policy/client/getReleaseByRegionAndTypeAndPolicyType?" + values.Encode()
|
||
var resp resultResponse[TeamPolicyRelease]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return TeamPolicyRelease{}, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) ListCurrentWeekRoomContribution(ctx context.Context, limit int) ([]RoomContributionActivityCount, error) {
|
||
if limit <= 0 {
|
||
limit = 200
|
||
}
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/room-contribution-activity-count/client/listCurrentWeek?limit=" +
|
||
url.QueryEscape(strconv.Itoa(limit))
|
||
var resp resultResponse[[]RoomContributionActivityCount]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) GetCurrentWeekRoomContribution(ctx context.Context, roomID int64) (RoomContributionActivityCount, error) {
|
||
if roomID <= 0 {
|
||
return RoomContributionActivityCount{}, nil
|
||
}
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/room-contribution-activity-count/client/currentWeek?roomId=" +
|
||
url.QueryEscape(strconv.FormatInt(roomID, 10))
|
||
var resp resultResponse[RoomContributionActivityCount]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return RoomContributionActivityCount{}, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) MarkRoomTurnoverRewardCoinsSent(ctx context.Context, id string) (bool, error) {
|
||
id = strings.TrimSpace(id)
|
||
if id == "" {
|
||
return false, fmt.Errorf("empty room contribution id")
|
||
}
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/room-contribution-activity-count/client/rewardCoinsSent?id=" + url.QueryEscape(id)
|
||
var resp resultResponse[bool]
|
||
if err := c.postJSON(ctx, endpoint, nil, nil, &resp); err != nil {
|
||
return false, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) GetRoomProfileByUserID(ctx context.Context, userID int64) (RoomProfile, error) {
|
||
if userID <= 0 {
|
||
return RoomProfile{}, nil
|
||
}
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/room-manager/client/getProfileByUserId?userId=" +
|
||
url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[RoomProfile]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return RoomProfile{}, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) MapRoomProfiles(ctx context.Context, roomIDs []int64) (map[int64]RoomProfile, error) {
|
||
if len(roomIDs) == 0 {
|
||
return map[int64]RoomProfile{}, nil
|
||
}
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/room-manager/client/mapProfileByRoomIds"
|
||
var resp resultResponse[map[string]RoomProfile]
|
||
if err := c.postJSON(ctx, endpoint, roomIDs, nil, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
result := make(map[int64]RoomProfile, len(resp.Body))
|
||
for key, value := range resp.Body {
|
||
roomID, err := strconv.ParseInt(strings.TrimSpace(key), 10, 64)
|
||
if err != nil {
|
||
roomID = int64(value.ID)
|
||
}
|
||
if roomID <= 0 {
|
||
continue
|
||
}
|
||
result[roomID] = value
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (c *Client) rewardBridgeBaseURL() string {
|
||
if baseURL := strings.TrimRight(strings.TrimSpace(c.cfg.Java.ConsoleBaseURL), "/"); baseURL != "" {
|
||
return baseURL
|
||
}
|
||
return strings.TrimRight(strings.TrimSpace(c.cfg.Java.BridgeBaseURL), "/")
|
||
}
|
||
|
||
func (c *Client) GetUserLanguage(ctx context.Context, userID int64) (string, error) {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getLanguage?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
var resp resultResponse[string]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return "", err
|
||
}
|
||
return strings.TrimSpace(resp.Body), nil
|
||
}
|
||
|
||
func (c *Client) RemoveUserProfileCacheAll(ctx context.Context, userID int64) error {
|
||
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/removeCacheAll?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
||
return c.getJSON(ctx, endpoint, nil, nil)
|
||
}
|
||
|
||
func (c *Client) PunishAccount(ctx context.Context, req PunishAccountRequest) error {
|
||
return c.postJSON(ctx, c.cfg.Java.OtherBaseURL+"/app-user/account/punishment", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) UnblockAccount(ctx context.Context, req UnblockAccountRequest) error {
|
||
return c.postJSON(ctx, c.cfg.Java.OtherBaseURL+"/app-user/account/unblock", req, nil, nil)
|
||
}
|
||
|
||
func (c *Client) MapGoldBalance(ctx context.Context, userIDs []int64) (map[int64]int64, error) {
|
||
endpoint := c.cfg.Java.WalletBaseURL + "/wallet/gold/client/mapBalance"
|
||
var resp resultResponse[map[string]int64]
|
||
if err := c.postJSON(ctx, endpoint, userIDs, nil, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
result := make(map[int64]int64, len(resp.Body))
|
||
for key, value := range resp.Body {
|
||
userID, err := strconv.ParseInt(key, 10, 64)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
result[userID] = value
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (c *Client) ExistsGoldEvent(ctx context.Context, eventID string) (bool, error) {
|
||
endpoint := c.cfg.Java.WalletBaseURL + "/wallet/gold/client/existsEventId?eventId=" + url.QueryEscape(eventID)
|
||
var resp resultResponse[bool]
|
||
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
|
||
return false, err
|
||
}
|
||
return resp.Body, nil
|
||
}
|
||
|
||
func (c *Client) ChangeGoldBalance(ctx context.Context, cmd GoldReceiptCommand) error {
|
||
endpoint := c.cfg.Java.WalletBaseURL + "/wallet/gold/client/balance/change"
|
||
if err := c.postJSON(ctx, endpoint, cmd, nil, nil); err == nil {
|
||
return nil
|
||
} else if !shouldFallbackToGoldTestEndpoint(err) {
|
||
return err
|
||
}
|
||
|
||
// Some local environments only expose the lighter test endpoint payload.
|
||
testCmd := TestGoldReceiptCommand{
|
||
ReceiptType: cmd.ReceiptType,
|
||
UserID: cmd.UserID,
|
||
EventType: firstNonEmpty(cmd.CustomizeOrigin, cmd.Origin),
|
||
EventDesc: cmd.CustomizeOriginDesc,
|
||
EventID: cmd.EventID,
|
||
SysOrigin: cmd.SysOrigin,
|
||
Remark: cmd.Remark,
|
||
Amount: cmd.Amount.DollarAmount,
|
||
CloseDelayAsset: cmd.CloseDelayAsset,
|
||
}
|
||
return c.postJSON(ctx, c.cfg.Java.WalletBaseURL+"/wallet/gold/client/balance/change/test", testCmd, nil, nil)
|
||
}
|
||
|
||
func firstNonEmpty(values ...string) string {
|
||
for _, value := range values {
|
||
if strings.TrimSpace(value) != "" {
|
||
return value
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func NewPennyAmountPayloadFromDollar(amount int64) PennyAmountPayload {
|
||
return PennyAmountPayload{
|
||
PennyAmount: amount * 100,
|
||
DollarAmount: amount,
|
||
}
|
||
}
|
||
|
||
func shouldFallbackToGoldTestEndpoint(err error) bool {
|
||
if err == nil {
|
||
return false
|
||
}
|
||
message := strings.ToLower(strings.TrimSpace(err.Error()))
|
||
switch {
|
||
case strings.Contains(message, "status=404"),
|
||
strings.Contains(message, "status=405"),
|
||
strings.Contains(message, "status=501"),
|
||
strings.Contains(message, "connection refused"),
|
||
strings.Contains(message, "no such host"),
|
||
strings.Contains(message, "unsupported protocol"):
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func (c *Client) getJSON(ctx context.Context, endpoint string, headers http.Header, out any) error {
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
applyHeaders(req.Header, headers)
|
||
resp, err := c.httpClient.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
return decodeResponse(resp, out)
|
||
}
|
||
|
||
func (c *Client) postJSON(ctx context.Context, endpoint string, payload any, headers http.Header, out any) error {
|
||
data, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(data))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req.Header.Set("Content-Type", "application/json")
|
||
applyHeaders(req.Header, headers)
|
||
resp, err := c.httpClient.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
return decodeResponse(resp, out)
|
||
}
|
||
|
||
func decodeResponse(resp *http.Response, out any) error {
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if resp.StatusCode >= http.StatusBadRequest {
|
||
return fmt.Errorf("java api failed: status=%d body=%s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||
}
|
||
if out == nil || len(body) == 0 {
|
||
return nil
|
||
}
|
||
if err := json.Unmarshal(body, out); err != nil {
|
||
return fmt.Errorf("decode response failed: %w body=%s", err, strings.TrimSpace(string(body)))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func applyHeaders(dst http.Header, src http.Header) {
|
||
for key, values := range src {
|
||
for _, value := range values {
|
||
dst.Add(key, value)
|
||
}
|
||
}
|
||
}
|