fix(wallet): enforce consistent withdrawal accounting

This commit is contained in:
david
2026-07-31 09:54:17 +08:00
parent 36a5ced1c0
commit 42a2322c8e
23 changed files with 1405 additions and 250 deletions

View File

@@ -7,8 +7,7 @@ import (
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
clientcommon "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/client/common"
base "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
common "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
@@ -26,24 +25,24 @@ type loginRequest struct {
// Login 支持密码和一次性验证码两种登录模式。
func Login(ctx *gin.Context) {
var request loginRequest
if ctx.ShouldBindJSON(&request) != nil || !clientcommon.ValidPhone(request.Phone) {
if ctx.ShouldBindJSON(&request) != nil || !common.ValidPhone(request.Phone) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var account models.UserAccount
if impl.DBService.Where("phone = ? AND status = ?", strings.TrimSpace(request.Phone), base.StatusEnable).First(&account).Error != nil {
if impl.DBService.Where("phone = ? AND status = ?", strings.TrimSpace(request.Phone), common.StatusEnable).First(&account).Error != nil {
infra.Response.Error(ctx, errcode.ErrPassword)
return
}
valid := request.Mode == "password" && bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(request.Password)) == nil
if request.Mode == "verification_code" {
valid = clientcommon.VerifyCode("user_app", account.Phone, "login", request.RequestIdentity, request.Code)
valid = common.VerifyCode("user_app", account.Phone, "login", request.RequestIdentity, request.Code)
}
if !valid {
infra.Response.Error(ctx, errcode.ErrPassword)
return
}
accessToken, err := clientcommon.IssueToken(account.Identity, "user_app", "user", map[string]string{"phone": account.Phone})
accessToken, err := common.IssueToken(account.Identity, "user_app", "user", map[string]string{"phone": account.Phone})
if err != nil {
infra.Response.Error(ctx, err)
return
@@ -65,19 +64,19 @@ func Register(ctx *gin.Context) {
Code string `json:"code" binding:"required"`
RequestIdentity string `json:"request_identity" binding:"required"`
}
if ctx.ShouldBindJSON(&request) != nil || !clientcommon.ValidPhone(request.Phone) ||
!base.IsValidAccountPassword(request.Password) ||
!clientcommon.VerifyCode("user_app", request.Phone, "register", request.RequestIdentity, request.Code) {
if ctx.ShouldBindJSON(&request) != nil || !common.ValidPhone(request.Phone) ||
!common.IsValidAccountPassword(request.Password) ||
!common.VerifyCode("user_app", request.Phone, "register", request.RequestIdentity, request.Code) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, err := base.PasswordHash(request.Password)
hash, err := common.PasswordHash(request.Password)
if err != nil {
infra.Response.Error(ctx, err)
return
}
account := models.UserAccount{
Entity: base.NewEntity(base.StatusEnable), Username: strings.TrimSpace(request.Phone),
Entity: common.NewEntity(common.StatusEnable), Username: strings.TrimSpace(request.Phone),
Phone: strings.TrimSpace(request.Phone), PasswordHash: hash, Name: strings.TrimSpace(request.Name),
}
err = impl.DBService.Transaction(func(tx *gorm.DB) error {
@@ -85,7 +84,7 @@ func Register(ctx *gin.Context) {
return err
}
address := models.UserAddress{
Entity: base.NewEntity(base.StatusEnable), UserAccountID: account.ID, Address: request.Address,
Entity: common.NewEntity(common.StatusEnable), UserAccountID: account.ID, Address: request.Address,
Longitude: request.Longitude, Latitude: request.Latitude, IsDefault: true,
}
if err := tx.Create(&address).Error; err != nil {
@@ -98,19 +97,19 @@ func Register(ctx *gin.Context) {
return nil
}
var gas models.GasBasic
if err := tx.Where("identity = ? AND status = ?", request.GasIdentity, base.StatusEnable).First(&gas).Error; err != nil {
if err := tx.Where("identity = ? AND status = ?", request.GasIdentity, common.StatusEnable).First(&gas).Error; err != nil {
return err
}
var deliveryID uint64
if request.DeliveryIdentity != "" {
var delivery models.DeliveryBasic
if err := tx.Where("identity = ? AND gas_basic_id = ? AND status = ?", request.DeliveryIdentity, gas.ID, base.StatusEnable).First(&delivery).Error; err != nil {
if err := tx.Where("identity = ? AND gas_basic_id = ? AND status = ?", request.DeliveryIdentity, gas.ID, common.StatusEnable).First(&delivery).Error; err != nil {
return err
}
deliveryID = delivery.ID
}
return tx.Create(&models.UserServiceRelation{
Entity: base.NewEntity(base.StatusEnable), UserAccountID: account.ID,
Entity: common.NewEntity(common.StatusEnable), UserAccountID: account.ID,
GasBasicID: gas.ID, DeliveryBasicID: deliveryID,
}).Error
})
@@ -123,7 +122,7 @@ func Register(ctx *gin.Context) {
// Profile 返回当前用户的脱敏资料。
func Profile(ctx *gin.Context) {
account, ok := clientcommon.UserAccount(ctx)
account, ok := common.UserAccount(ctx)
if !ok {
return
}
@@ -132,7 +131,7 @@ func Profile(ctx *gin.Context) {
// UpdateProfile 只允许修改非认证资料。
func UpdateProfile(ctx *gin.Context) {
account, ok := clientcommon.UserAccount(ctx)
account, ok := common.UserAccount(ctx)
if !ok {
return
}
@@ -153,7 +152,7 @@ func UpdateProfile(ctx *gin.Context) {
// ChangePassword 使用当前密码修改登录密码。
func ChangePassword(ctx *gin.Context) {
account, ok := clientcommon.UserAccount(ctx)
account, ok := common.UserAccount(ctx)
if !ok {
return
}
@@ -161,12 +160,12 @@ func ChangePassword(ctx *gin.Context) {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
if ctx.ShouldBindJSON(&request) != nil || !base.IsValidAccountPassword(request.NewPassword) ||
if ctx.ShouldBindJSON(&request) != nil || !common.IsValidAccountPassword(request.NewPassword) ||
bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(request.CurrentPassword)) != nil {
infra.Response.Error(ctx, errcode.ErrPassword)
return
}
hash, err := base.PasswordHash(request.NewPassword)
hash, err := common.PasswordHash(request.NewPassword)
if err != nil {
infra.Response.Error(ctx, err)
return
@@ -186,18 +185,18 @@ func ResetPassword(ctx *gin.Context) {
Code string `json:"code" binding:"required"`
RequestIdentity string `json:"request_identity" binding:"required"`
}
if ctx.ShouldBindJSON(&request) != nil || !base.IsValidAccountPassword(request.NewPassword) ||
!clientcommon.VerifyCode("user_app", request.Phone, "reset_login_password", request.RequestIdentity, request.Code) {
if ctx.ShouldBindJSON(&request) != nil || !common.IsValidAccountPassword(request.NewPassword) ||
!common.VerifyCode("user_app", request.Phone, "reset_login_password", request.RequestIdentity, request.Code) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, err := base.PasswordHash(request.NewPassword)
hash, err := common.PasswordHash(request.NewPassword)
if err != nil {
infra.Response.Error(ctx, err)
return
}
result := impl.DBService.Model(&models.UserAccount{}).
Where("phone = ? AND status = ?", strings.TrimSpace(request.Phone), base.StatusEnable).
Where("phone = ? AND status = ?", strings.TrimSpace(request.Phone), common.StatusEnable).
Update("password_hash", hash)
if result.Error != nil {
infra.Response.Error(ctx, result.Error)