2026-04-27 23:27:37 +08:00

763 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}
}