763 lines
24 KiB
Go
763 lines
24 KiB
Go
package integration
|
||
|
||
import (
|
||
"bytes"
|
||
"chatapp3-golang/internal/config"
|
||
"context"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
type Client struct {
|
||
cfg config.Config
|
||
httpClient *http.Client
|
||
}
|
||
|
||
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"`
|
||
}
|
||
|
||
type UserRegion struct {
|
||
RegionID string `json:"regionId"`
|
||
RegionCode string `json:"regionCode"`
|
||
}
|
||
|
||
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"`
|
||
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 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"`
|
||
Name string `json:"name"`
|
||
Content string `json:"content"`
|
||
Quantity Int64Value `json:"quantity"`
|
||
Cover string `json:"cover"`
|
||
Remark string `json:"remark"`
|
||
}
|
||
|
||
type RewardGroupDetail struct {
|
||
ID Int64Value `json:"id"`
|
||
Name string `json:"name"`
|
||
SysOrigin string `json:"sysOrigin"`
|
||
RewardConfigList []RewardGroupItem `json:"rewardConfigList"`
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
func New(cfg config.Config) *Client {
|
||
return &Client{
|
||
cfg: cfg,
|
||
httpClient: &http.Client{
|
||
Timeout: cfg.HTTP.Timeout,
|
||
},
|
||
}
|
||
}
|
||
|
||
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 := http.Header{}
|
||
headers.Set("Authorization", 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 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)
|
||
}
|
||
|
||
// 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) 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) 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) 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) 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: cmd.CustomizeOrigin,
|
||
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 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)
|
||
}
|
||
}
|
||
}
|