已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
@@ -13,14 +13,16 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
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"`
|
||||
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"`
|
||||
Consents []loginConsent `json:"consents" binding:"max=100"` // 新客户端提交本次明确同意的内容版本;旧客户端兼容省略。
|
||||
}
|
||||
|
||||
// Login 支持密码和一次性验证码两种登录模式。
|
||||
@@ -43,6 +45,10 @@ func Login(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrPassword)
|
||||
return
|
||||
}
|
||||
if err := recordLoginConsents(account.ID, account.Identity, request.Consents); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
accessToken, err := common.IssueToken(account.Identity, "user_app", "user", map[string]string{"phone": account.Phone})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
@@ -146,14 +152,23 @@ func UpdateProfile(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
Avatar string `json:"avatar" binding:"max=512"`
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
Avatar *string `json:"avatar" binding:"omitempty,max=512"`
|
||||
}
|
||||
if ctx.ShouldBindJSON(&request) != nil {
|
||||
if ctx.ShouldBindJSON(&request) != nil || strings.TrimSpace(request.Name) == "" {
|
||||
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 {
|
||||
updates := map[string]any{"name": strings.TrimSpace(request.Name)}
|
||||
if request.Avatar != nil {
|
||||
// 兼容保留旧头像或显式清空;替换必须引用当前用户刚上传的受控文件。
|
||||
if *request.Avatar != "" && *request.Avatar != account.Avatar && !upload.OwnsAvatar("user_app", account.Identity, *request.Avatar) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
updates["avatar"] = *request.Avatar
|
||||
}
|
||||
if err := impl.DBService.Model(&account).Updates(updates).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
@@ -180,8 +195,15 @@ func ChangePassword(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&account).Update("password_hash", hash).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
// 仅替换刚验证过的密码版本,避免并发改密覆盖已经生效的新密码。
|
||||
// 密码散列不进入开发环境SQL日志。
|
||||
result := impl.DBService.Session(&gorm.Session{Logger: logger.Default.LogMode(logger.Silent)}).Model(&account).Where("password_hash = ?", account.PasswordHash).Update("password_hash", hash)
|
||||
if result.Error != nil {
|
||||
infra.Response.Error(ctx, result.Error)
|
||||
return
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
infra.Response.Error(ctx, errcode.ErrPassword)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"changed": true})
|
||||
|
||||
Reference in New Issue
Block a user