305 lines
9.0 KiB
Go
305 lines
9.0 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"chatapp3-golang/internal/config"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Client struct {
|
|
cfg config.Config
|
|
httpClient *http.Client
|
|
}
|
|
|
|
type UserCredential struct {
|
|
UserID Int64Value `json:"userId"`
|
|
SysOrigin string `json:"sysOrigin"`
|
|
}
|
|
|
|
type UserProfile struct {
|
|
ID Int64Value `json:"id"`
|
|
UserAvatar string `json:"userAvatar"`
|
|
UserNickname string `json:"userNickname"`
|
|
}
|
|
|
|
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 int64 `json:"amount"`
|
|
CloseDelayAsset bool `json:"closeDelayAsset"`
|
|
OpUserType string `json:"opUserType"`
|
|
CustomizeOrigin string `json:"customizeOrigin,omitempty"`
|
|
CustomizeOriginDesc string `json:"customizeOriginDesc,omitempty"`
|
|
}
|
|
|
|
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 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
|
|
}
|
|
|
|
func New(cfg config.Config) *Client {
|
|
return &Client{
|
|
cfg: cfg,
|
|
httpClient: &http.Client{
|
|
Timeout: cfg.Timeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *Client) AuthenticateToken(ctx context.Context, token string) (UserCredential, error) {
|
|
endpoint := c.cfg.JavaAuthBaseURL + "/auth/client/getUserCredentialByToken?token=" + url.QueryEscape(token)
|
|
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")
|
|
}
|
|
return resp.Body, nil
|
|
}
|
|
|
|
func (c *Client) GetDeviceFingerprint(ctx context.Context, userID int64) (string, error) {
|
|
endpoint := c.cfg.JavaDeviceBaseURL + "/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.JavaAppBaseURL + "/activity/invite/user/my/invite/code"
|
|
headers := http.Header{}
|
|
if authorization != "" {
|
|
headers.Set("Authorization", authorization)
|
|
}
|
|
var resp InviteCode
|
|
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
|
|
return InviteCode{}, err
|
|
}
|
|
if strings.TrimSpace(resp.InviteCode) == "" {
|
|
return InviteCode{}, fmt.Errorf("invite code response is empty")
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *Client) GrantGold(ctx context.Context, req GrantGoldRequest) error {
|
|
return c.postJSON(ctx, c.cfg.JavaBridgeBaseURL+"/internal/resident-activity/invite/grant-gold", req, nil, nil)
|
|
}
|
|
|
|
func (c *Client) GrantProps(ctx context.Context, req GrantPropsRequest) error {
|
|
return c.postJSON(ctx, c.cfg.JavaBridgeBaseURL+"/internal/resident-activity/invite/grant-props", req, nil, nil)
|
|
}
|
|
|
|
func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile, error) {
|
|
endpoint := c.cfg.JavaOtherBaseURL + "/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) GetUserLanguage(ctx context.Context, userID int64) (string, error) {
|
|
endpoint := c.cfg.JavaOtherBaseURL + "/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.JavaWalletBaseURL + "/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.JavaWalletBaseURL + "/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.JavaWalletBaseURL + "/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,
|
|
CloseDelayAsset: cmd.CloseDelayAsset,
|
|
}
|
|
return c.postJSON(ctx, c.cfg.JavaWalletBaseURL+"/wallet/gold/client/balance/change/test", testCmd, nil, nil)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|