Compare commits

...

3 Commits

Author SHA1 Message Date
shanshanzhong147 7d1c71ad5b fix: allow clearing user remark in basic info update
Co-authored-by: multica-agent <github@multica.ai>
2026-05-25 20:54:28 -07:00
shanshanzhong147 7f2db02da9 merge: include HIF-36 user basic info zero-value guard 2026-05-25 20:52:10 -07:00
shanshanzhong147 6d9263ceef fix: guard RefererId and monetary fields against zero-value overwrite in updateUserBasicInfo
- Change RefererId, Balance, GiftAmount, Commission from int64 to *int64 in UpdateUserBasiceInfoRequest
- When field is nil (not sent by client), skip update entirely — prevents zero-value from clobbering existing data
- When field is non-nil, dereference and apply as before (including explicit 0 to clear a value)
- Mirrors the existing guard pattern already used for Avatar, ReferCode, Enable, IsAdmin, etc.

Fixes: RefererId unconditional overwrite at line 123 (P02)
Fixes: Balance/GiftAmount/Commission triggering adjustment to 0 when omitted (P03)
Co-authored-by: multica-agent <github@multica.ai>
2026-05-25 19:52:28 -07:00
4 changed files with 83 additions and 44 deletions
+14 -14
View File
@@ -38,20 +38,20 @@ type (
Id int64 `form:"id" validate:"required"` Id int64 `form:"id" validate:"required"`
} }
UpdateUserBasiceInfoRequest { UpdateUserBasiceInfoRequest {
UserId int64 `json:"user_id" validate:"required"` UserId int64 `json:"user_id" validate:"required"`
Password string `json:"password"` Password string `json:"password"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
Balance int64 `json:"balance"` Balance *int64 `json:"balance"`
Commission int64 `json:"commission"` Commission *int64 `json:"commission"`
ReferralPercentage uint8 `json:"referral_percentage"` ReferralPercentage uint8 `json:"referral_percentage"`
OnlyFirstPurchase *bool `json:"only_first_purchase"` OnlyFirstPurchase *bool `json:"only_first_purchase"`
GiftAmount int64 `json:"gift_amount"` GiftAmount *int64 `json:"gift_amount"`
Telegram int64 `json:"telegram"` Telegram int64 `json:"telegram"`
ReferCode string `json:"refer_code"` ReferCode string `json:"refer_code"`
RefererId int64 `json:"referer_id"` RefererId *int64 `json:"referer_id"`
Enable *bool `json:"enable"` Enable *bool `json:"enable"`
IsAdmin *bool `json:"is_admin"` IsAdmin *bool `json:"is_admin"`
Remark string `json:"remark"` Remark *string `json:"remark"`
} }
UpdateUserNotifySettingRequest { UpdateUserNotifySettingRequest {
UserId int64 `json:"user_id" validate:"required"` UserId int64 `json:"user_id" validate:"required"`
@@ -46,13 +46,13 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
} }
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error { err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
if userInfo.Balance != req.Balance { if req.Balance != nil && userInfo.Balance != *req.Balance {
change := req.Balance - userInfo.Balance change := *req.Balance - userInfo.Balance
balanceLog := log.Balance{ balanceLog := log.Balance{
Type: log.BalanceTypeAdjust, Type: log.BalanceTypeAdjust,
Amount: change, Amount: change,
OrderNo: "", OrderNo: "",
Balance: req.Balance, Balance: *req.Balance,
Timestamp: time.Now().UnixMilli(), Timestamp: time.Now().UnixMilli(),
} }
content, _ := balanceLog.Marshal() content, _ := balanceLog.Marshal()
@@ -66,14 +66,14 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
if err != nil { if err != nil {
return err return err
} }
userInfo.Balance = req.Balance userInfo.Balance = *req.Balance
} }
if userInfo.GiftAmount != req.GiftAmount { if req.GiftAmount != nil && userInfo.GiftAmount != *req.GiftAmount {
change := req.GiftAmount - userInfo.GiftAmount change := *req.GiftAmount - userInfo.GiftAmount
if change != 0 { if change != 0 {
var changeType uint16 var changeType uint16
if userInfo.GiftAmount < req.GiftAmount { if userInfo.GiftAmount < *req.GiftAmount {
changeType = log.GiftTypeIncrease changeType = log.GiftTypeIncrease
} else { } else {
changeType = log.GiftTypeReduce changeType = log.GiftTypeReduce
@@ -81,7 +81,7 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
giftLog := log.Gift{ giftLog := log.Gift{
Type: changeType, Type: changeType,
Amount: change, Amount: change,
Balance: req.GiftAmount, Balance: *req.GiftAmount,
Remark: "Admin adjustment", Remark: "Admin adjustment",
Timestamp: time.Now().UnixMilli(), Timestamp: time.Now().UnixMilli(),
} }
@@ -96,23 +96,27 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
if err != nil { if err != nil {
return err return err
} }
userInfo.GiftAmount = req.GiftAmount userInfo.GiftAmount = *req.GiftAmount
} }
} }
if req.Commission != userInfo.Commission { if req.Commission != nil && *req.Commission != userInfo.Commission {
if isWithdrawalScene(req.Remark) { remark := ""
if req.Remark != nil {
remark = *req.Remark
}
if isWithdrawalScene(remark) {
logWithdrawalGuard(l.Logger, userInfo.Id) logWithdrawalGuard(l.Logger, userInfo.Id)
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "commission overwrite is blocked in withdrawal scene") return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "commission overwrite is blocked in withdrawal scene")
} }
change := req.Commission - userInfo.Commission change := *req.Commission - userInfo.Commission
if err = l.svcCtx.UserModel.UpdateCommission(l.ctx, userInfo.Id, change, tx); err != nil { if err = l.svcCtx.UserModel.UpdateCommission(l.ctx, userInfo.Id, change, tx); err != nil {
return err return err
} }
if err = logicCommon.WriteCommissionLog(tx, userInfo.Id, log.CommissionTypeAdjust, change, ""); err != nil { if err = logicCommon.WriteCommissionLog(tx, userInfo.Id, log.CommissionTypeAdjust, change, ""); err != nil {
return err return err
} }
userInfo.Commission = req.Commission userInfo.Commission = *req.Commission
} }
if req.Avatar != "" { if req.Avatar != "" {
userInfo.Avatar = req.Avatar userInfo.Avatar = req.Avatar
@@ -120,15 +124,17 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
if req.ReferCode != "" { if req.ReferCode != "" {
userInfo.ReferCode = req.ReferCode userInfo.ReferCode = req.ReferCode
} }
userInfo.RefererId = req.RefererId if req.RefererId != nil {
userInfo.RefererId = *req.RefererId
}
if req.Enable != nil { if req.Enable != nil {
userInfo.Enable = req.Enable userInfo.Enable = req.Enable
} }
if req.IsAdmin != nil { if req.IsAdmin != nil {
userInfo.IsAdmin = req.IsAdmin userInfo.IsAdmin = req.IsAdmin
} }
if req.Remark != "" { if req.Remark != nil {
userInfo.Remark = req.Remark userInfo.Remark = *req.Remark
} }
if req.OnlyFirstPurchase != nil { if req.OnlyFirstPurchase != nil {
userInfo.OnlyFirstPurchase = req.OnlyFirstPurchase userInfo.OnlyFirstPurchase = req.OnlyFirstPurchase
+14 -14
View File
@@ -3223,20 +3223,20 @@ type UpdateUserAuthMethodRequest struct {
} }
type UpdateUserBasiceInfoRequest struct { type UpdateUserBasiceInfoRequest struct {
UserId int64 `json:"user_id" validate:"required"` UserId int64 `json:"user_id" validate:"required"`
Password string `json:"password"` Password string `json:"password"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
Balance int64 `json:"balance"` Balance *int64 `json:"balance"`
Commission int64 `json:"commission"` Commission *int64 `json:"commission"`
ReferralPercentage uint8 `json:"referral_percentage"` ReferralPercentage uint8 `json:"referral_percentage"`
OnlyFirstPurchase *bool `json:"only_first_purchase"` OnlyFirstPurchase *bool `json:"only_first_purchase"`
GiftAmount int64 `json:"gift_amount"` GiftAmount *int64 `json:"gift_amount"`
Telegram int64 `json:"telegram"` Telegram int64 `json:"telegram"`
ReferCode string `json:"refer_code"` ReferCode string `json:"refer_code"`
RefererId int64 `json:"referer_id"` RefererId *int64 `json:"referer_id"`
Enable *bool `json:"enable"` Enable *bool `json:"enable"`
IsAdmin *bool `json:"is_admin"` IsAdmin *bool `json:"is_admin"`
Remark string `json:"remark"` Remark *string `json:"remark"`
} }
type UpdateUserNotifyRequest struct { type UpdateUserNotifyRequest struct {
+33
View File
@@ -0,0 +1,33 @@
package types
import (
"encoding/json"
"testing"
)
func TestUpdateUserBasiceInfoRequestDistinguishesOmittedAndEmptyRemark(t *testing.T) {
var omitted UpdateUserBasiceInfoRequest
if err := json.Unmarshal([]byte(`{"user_id":1001}`), &omitted); err != nil {
t.Fatalf("unmarshal omitted remark: %v", err)
}
if omitted.Remark != nil {
t.Fatalf("omitted remark should stay nil, got %q", *omitted.Remark)
}
if omitted.RefererId != nil {
t.Fatalf("omitted referer_id should stay nil, got %d", *omitted.RefererId)
}
if omitted.Balance != nil || omitted.GiftAmount != nil || omitted.Commission != nil {
t.Fatalf("omitted money fields should stay nil, got balance=%v gift=%v commission=%v", omitted.Balance, omitted.GiftAmount, omitted.Commission)
}
var cleared UpdateUserBasiceInfoRequest
if err := json.Unmarshal([]byte(`{"user_id":1001,"remark":""}`), &cleared); err != nil {
t.Fatalf("unmarshal empty remark: %v", err)
}
if cleared.Remark == nil {
t.Fatal("explicit empty remark should be present")
}
if *cleared.Remark != "" {
t.Fatalf("explicit empty remark should decode to empty string, got %q", *cleared.Remark)
}
}