From 362069e2d4f76113eb099b1c689db845aa582c39 Mon Sep 17 00:00:00 2001 From: hy001 Date: Mon, 27 Apr 2026 20:56:49 +0800 Subject: [PATCH] fix invite rule id precision --- internal/service/invite/invite_test.go | 29 ++++++++++++++++ internal/service/invite/types.go | 46 ++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/internal/service/invite/invite_test.go b/internal/service/invite/invite_test.go index 0c4cf8d..d8ee8de 100644 --- a/internal/service/invite/invite_test.go +++ b/internal/service/invite/invite_test.go @@ -5,6 +5,7 @@ import ( "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "context" + "encoding/json" "errors" "strings" "testing" @@ -526,3 +527,31 @@ func TestDispatchGoldReturnsGrantError(t *testing.T) { t.Fatalf("reward log status = %s, want %s", log.Status, rewardLogStatusFailed) } } + +func TestInviteRuleIDJSONUsesStringForSnowflakeIDs(t *testing.T) { + const ruleID int64 = 2048743937920790530 + payload, err := json.Marshal(HomeResponse{ + ValidCountTasks: []TaskView{{RuleID: ruleID, RuleType: ruleTypeInviterValidCount}}, + InviterBindReward: RewardView{ + RuleID: ruleID, + RuleType: ruleTypeInviterBind, + }, + }) + if err != nil { + t.Fatalf("json.Marshal() error = %v", err) + } + raw := string(payload) + if !strings.Contains(raw, `"ruleId":"2048743937920790530"`) { + t.Fatalf("ruleId was not encoded as a string: %s", raw) + } +} + +func TestTaskClaimRequestAcceptsStringRuleID(t *testing.T) { + var req TaskClaimRequest + if err := json.Unmarshal([]byte(`{"ruleId":"2048743937920790530"}`), &req); err != nil { + t.Fatalf("json.Unmarshal() error = %v", err) + } + if req.RuleID != 2048743937920790530 { + t.Fatalf("RuleID = %d, want 2048743937920790530", req.RuleID) + } +} diff --git a/internal/service/invite/types.go b/internal/service/invite/types.go index c9c4f21..cedc700 100644 --- a/internal/service/invite/types.go +++ b/internal/service/invite/types.go @@ -1,12 +1,17 @@ package invite import ( + "bytes" "chatapp3-golang/internal/common" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "context" + "encoding/json" "errors" + "fmt" + "strconv" + "strings" "github.com/redis/go-redis/v9" "gorm.io/gorm" @@ -43,7 +48,7 @@ var NewAppError = common.NewAppError // RewardView 是邀请活动奖励的通用展示结构。 type RewardView struct { - RuleID int64 `json:"ruleId,omitempty"` + RuleID int64 `json:"ruleId,string,omitempty"` RuleType string `json:"ruleType,omitempty"` Threshold int64 `json:"threshold,omitempty"` GoldAmount int64 `json:"goldAmount"` @@ -53,7 +58,7 @@ type RewardView struct { // TaskView 是邀请任务进度及领取状态的展示结构。 type TaskView struct { - RuleID int64 `json:"ruleId"` + RuleID int64 `json:"ruleId,string"` RuleType string `json:"ruleType"` Threshold int64 `json:"threshold"` Progress int64 `json:"progress"` @@ -171,11 +176,46 @@ type TaskClaimRequest struct { RuleID int64 `json:"ruleId"` } +// UnmarshalJSON 允许 H5 按字符串提交 Snowflake 规则 ID,避免 JS Number 精度丢失。 +func (r *TaskClaimRequest) UnmarshalJSON(data []byte) error { + var payload struct { + RuleID any `json:"ruleId"` + } + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + if err := decoder.Decode(&payload); err != nil { + return err + } + ruleID, err := parseClaimRuleID(payload.RuleID) + if err != nil { + return err + } + r.RuleID = ruleID + return nil +} + +func parseClaimRuleID(value any) (int64, error) { + switch typed := value.(type) { + case nil: + return 0, nil + case json.Number: + return typed.Int64() + case string: + raw := strings.TrimSpace(typed) + if raw == "" { + return 0, nil + } + return strconv.ParseInt(raw, 10, 64) + default: + return 0, fmt.Errorf("ruleId must be string or number") + } +} + // TaskClaimResponse 是任务领取结果。 type TaskClaimResponse struct { Claimed bool `json:"claimed"` AlreadyClaimed bool `json:"alreadyClaimed"` - RuleID int64 `json:"ruleId"` + RuleID int64 `json:"ruleId,string"` RuleType string `json:"ruleType"` Progress int64 `json:"progress"` Threshold int64 `json:"threshold"`