- Added SendOTP method for simulating OTP sending. - Implemented LoginWithOTP method for user login/registration via OTP, including user creation if not found. - Added Me method to retrieve current user information. - Implemented Update method for updating user profile details. - Added RealName method for real-name verification. - Implemented GetNotifications method to fetch user notifications. - Created user_test.go for comprehensive unit tests covering login, profile retrieval, updates, real-name verification, and notifications. - Updated database models to use appropriate consts for fields like gender, status, and roles.
191 lines
4.9 KiB
Go
191 lines
4.9 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"quyun/v2/app/errorx"
|
|
auth_dto "quyun/v2/app/http/v1/dto"
|
|
user_dto "quyun/v2/app/http/v1/dto"
|
|
"quyun/v2/database/models"
|
|
"quyun/v2/pkg/consts"
|
|
"quyun/v2/providers/jwt"
|
|
|
|
"github.com/spf13/cast"
|
|
"go.ipao.vip/gen/types"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// @provider
|
|
type user struct {
|
|
jwt *jwt.JWT
|
|
}
|
|
|
|
// SendOTP 发送验证码
|
|
// 当前仅模拟发送,实际应对接短信服务
|
|
func (s *user) SendOTP(ctx context.Context, phone string) error {
|
|
// TODO: 对接短信服务
|
|
// 模拟发送成功
|
|
return nil
|
|
}
|
|
|
|
// LoginWithOTP 手机号验证码登录/注册
|
|
func (s *user) LoginWithOTP(ctx context.Context, phone, otp string) (*auth_dto.LoginResponse, error) {
|
|
// 1. 校验验证码 (模拟:固定 123456)
|
|
if otp != "123456" {
|
|
return nil, errorx.ErrInvalidCredentials.WithMsg("验证码错误")
|
|
}
|
|
|
|
// 2. 查询或创建用户
|
|
tbl, query := models.UserQuery.QueryContext(ctx)
|
|
u, err := query.Where(tbl.Phone.Eq(phone)).First()
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// 创建新用户
|
|
u = &models.User{
|
|
Phone: phone,
|
|
Username: phone, // 默认用户名 = 手机号
|
|
Password: "", // 免密登录
|
|
Nickname: "User_" + phone[len(phone)-4:],
|
|
Status: string(consts.UserStatusVerified), // 默认已审核?需确认业务逻辑
|
|
Roles: types.Array[consts.Role]{consts.RoleUser},
|
|
}
|
|
if err := query.Create(u); err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithMsg("创建用户失败")
|
|
}
|
|
} else {
|
|
return nil, errorx.ErrDatabaseError.WithMsg("查询用户失败")
|
|
}
|
|
}
|
|
|
|
// 3. 检查状态
|
|
if u.Status == string(consts.UserStatusBanned) {
|
|
return nil, errorx.ErrAccountDisabled
|
|
}
|
|
|
|
// 4. 生成 Token
|
|
token, err := s.jwt.CreateToken(s.jwt.CreateClaims(jwt.BaseClaims{
|
|
UserID: u.ID,
|
|
// TenantID: 0, // 初始登录无租户上下文
|
|
}))
|
|
if err != nil {
|
|
return nil, errorx.ErrInternalError.WithMsg("生成令牌失败")
|
|
}
|
|
|
|
return &auth_dto.LoginResponse{
|
|
Token: token,
|
|
User: s.toAuthUserDTO(u),
|
|
}, nil
|
|
}
|
|
|
|
// Me 获取当前用户信息
|
|
func (s *user) Me(ctx context.Context) (*auth_dto.User, error) {
|
|
userID := ctx.Value(consts.CtxKeyUser)
|
|
if userID == nil {
|
|
return nil, errorx.ErrUnauthorized
|
|
}
|
|
|
|
uid := cast.ToInt64(userID)
|
|
tbl, query := models.UserQuery.QueryContext(ctx)
|
|
u, err := query.Where(tbl.ID.Eq(uid)).First()
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errorx.ErrRecordNotFound
|
|
}
|
|
return nil, errorx.ErrDatabaseError
|
|
}
|
|
|
|
return s.toAuthUserDTO(u), nil
|
|
}
|
|
|
|
// Update 更新用户信息
|
|
func (s *user) Update(ctx context.Context, form *user_dto.UserUpdate) error {
|
|
userID := ctx.Value(consts.CtxKeyUser)
|
|
if userID == nil {
|
|
return errorx.ErrUnauthorized
|
|
}
|
|
uid := cast.ToInt64(userID)
|
|
|
|
tbl, query := models.UserQuery.QueryContext(ctx)
|
|
_, err := query.Where(tbl.ID.Eq(uid)).Updates(&models.User{
|
|
Nickname: form.Nickname,
|
|
Avatar: form.Avatar,
|
|
Gender: form.Gender,
|
|
Bio: form.Bio,
|
|
// Birthday: form.Birthday, // 类型转换需处理
|
|
})
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RealName 实名认证
|
|
func (s *user) RealName(ctx context.Context, form *user_dto.RealNameForm) error {
|
|
userID := ctx.Value(consts.CtxKeyUser)
|
|
if userID == nil {
|
|
return errorx.ErrUnauthorized
|
|
}
|
|
uid := cast.ToInt64(userID)
|
|
|
|
// TODO: 调用实名认证接口校验
|
|
|
|
tbl, query := models.UserQuery.QueryContext(ctx)
|
|
_, err := query.Where(tbl.ID.Eq(uid)).Updates(&models.User{
|
|
IsRealNameVerified: true,
|
|
// RealName: form.Realname, // 需在 user 表添加字段? payout_accounts 有 realname
|
|
})
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetNotifications 获取通知
|
|
func (s *user) GetNotifications(ctx context.Context, typeArg string) ([]user_dto.Notification, error) {
|
|
userID := ctx.Value(consts.CtxKeyUser)
|
|
if userID == nil {
|
|
return nil, errorx.ErrUnauthorized
|
|
}
|
|
uid := cast.ToInt64(userID)
|
|
|
|
tbl, query := models.NotificationQuery.QueryContext(ctx)
|
|
query = query.Where(tbl.UserID.Eq(uid))
|
|
if typeArg != "" && typeArg != "all" {
|
|
query = query.Where(tbl.Type.Eq(typeArg))
|
|
}
|
|
|
|
list, err := query.Order(tbl.CreatedAt.Desc()).Find()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError
|
|
}
|
|
|
|
result := make([]user_dto.Notification, len(list))
|
|
for i, v := range list {
|
|
result[i] = user_dto.Notification{
|
|
ID: cast.ToString(v.ID),
|
|
Type: v.Type,
|
|
Title: v.Title,
|
|
Content: v.Content,
|
|
Read: v.IsRead,
|
|
Time: v.CreatedAt.Format(time.RFC3339),
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *user) toAuthUserDTO(u *models.User) *auth_dto.User {
|
|
return &auth_dto.User{
|
|
ID: cast.ToString(u.ID),
|
|
Phone: u.Phone,
|
|
Nickname: u.Nickname,
|
|
Avatar: u.Avatar,
|
|
Gender: consts.Gender(u.Gender),
|
|
Bio: u.Bio,
|
|
Balance: float64(u.Balance) / 100.0,
|
|
Points: u.Points,
|
|
IsRealNameVerified: u.IsRealNameVerified,
|
|
}
|
|
} |