feat(api): add client app service endpoints
This commit is contained in:
211
backend/api/internal/logic/client/user/auth.go
Normal file
211
backend/api/internal/logic/client/user/auth.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// Package user 实现用户 App 的服务端业务接口。
|
||||
package user
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type loginRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Mode string `json:"mode" binding:"required,oneof=password verification_code"`
|
||||
Password string `json:"password"`
|
||||
Code string `json:"code"`
|
||||
RequestIdentity string `json:"request_identity"`
|
||||
}
|
||||
|
||||
// Login 支持密码和一次性验证码两种登录模式。
|
||||
func Login(ctx *gin.Context) {
|
||||
var request loginRequest
|
||||
if ctx.ShouldBindJSON(&request) != nil || !clientcommon.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 {
|
||||
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)
|
||||
}
|
||||
if !valid {
|
||||
infra.Response.Error(ctx, errcode.ErrPassword)
|
||||
return
|
||||
}
|
||||
accessToken, err := clientcommon.IssueToken(account.Identity, "user_app", "user", map[string]string{"phone": account.Phone})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"access_token": accessToken, "token_type": "JWT", "identity": account.Identity})
|
||||
}
|
||||
|
||||
// Register 创建普通或邀请用户;邀请归属和默认地址在同一事务内完成。
|
||||
func Register(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
Address string `json:"address" binding:"required,max=255"`
|
||||
Longitude string `json:"longitude"`
|
||||
Latitude string `json:"latitude"`
|
||||
GasIdentity string `json:"gas_identity"`
|
||||
DeliveryIdentity string `json:"delivery_identity"`
|
||||
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) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
hash, err := base.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),
|
||||
Phone: strings.TrimSpace(request.Phone), PasswordHash: hash, Name: strings.TrimSpace(request.Name),
|
||||
}
|
||||
err = impl.DBService.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
address := models.UserAddress{
|
||||
Entity: base.NewEntity(base.StatusEnable), UserAccountID: account.ID, Address: request.Address,
|
||||
Longitude: request.Longitude, Latitude: request.Latitude, IsDefault: true,
|
||||
}
|
||||
if err := tx.Create(&address).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if request.GasIdentity == "" {
|
||||
if request.DeliveryIdentity != "" {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var gas models.GasBasic
|
||||
if err := tx.Where("identity = ? AND status = ?", request.GasIdentity, base.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 {
|
||||
return err
|
||||
}
|
||||
deliveryID = delivery.ID
|
||||
}
|
||||
return tx.Create(&models.UserServiceRelation{
|
||||
Entity: base.NewEntity(base.StatusEnable), UserAccountID: account.ID,
|
||||
GasBasicID: gas.ID, DeliveryBasicID: deliveryID,
|
||||
}).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"identity": account.Identity})
|
||||
}
|
||||
|
||||
// Profile 返回当前用户的脱敏资料。
|
||||
func Profile(ctx *gin.Context) {
|
||||
account, ok := clientcommon.UserAccount(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"identity": account.Identity, "name": account.Name, "phone": account.Phone, "avatar": account.Avatar, "real_name": account.RealName})
|
||||
}
|
||||
|
||||
// UpdateProfile 只允许修改非认证资料。
|
||||
func UpdateProfile(ctx *gin.Context) {
|
||||
account, ok := clientcommon.UserAccount(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
Avatar string `json:"avatar" binding:"max=512"`
|
||||
}
|
||||
if ctx.ShouldBindJSON(&request) != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&account).Updates(map[string]any{"name": strings.TrimSpace(request.Name), "avatar": request.Avatar}).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
// ChangePassword 使用当前密码修改登录密码。
|
||||
func ChangePassword(ctx *gin.Context) {
|
||||
account, ok := clientcommon.UserAccount(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
CurrentPassword string `json:"current_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
if ctx.ShouldBindJSON(&request) != nil || !base.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)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&account).Update("password_hash", hash).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"changed": true})
|
||||
}
|
||||
|
||||
// ResetPassword 使用限定用途的手机号验证码重置登录密码。
|
||||
func ResetPassword(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
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) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
hash, err := base.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).
|
||||
Update("password_hash", hash)
|
||||
if result.Error != nil {
|
||||
infra.Response.Error(ctx, result.Error)
|
||||
return
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"changed": true})
|
||||
}
|
||||
Reference in New Issue
Block a user