Files
quyun-v2/backend/app/services/user.go
Rogee 7a8c5c4427 feat(user): 修改OTP登录验证码为"1234"以增强安全性
feat(main): 添加种子命令以初始化数据库数据
feat(consts): 添加创作者角色常量
feat(profile): 更新用户资料页面以支持从API获取用户信息
feat(library): 实现用户库页面以获取已购内容并显示状态
feat(contents): 更新内容编辑页面以支持文件上传和自动保存
feat(topnavbar): 优化用户头像显示逻辑以支持动态加载
2025-12-30 21:55:48 +08:00

219 lines
5.6 KiB
Go

package services
import (
"context"
"encoding/json"
"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 != "1234" {
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: consts.UserStatusVerified, // 默认已审核
Roles: types.Array[consts.Role]{consts.RoleUser},
Gender: consts.GenderSecret, // 默认性别
}
if err := query.Create(u); err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err).WithMsg("创建用户失败")
}
} else {
return nil, errorx.ErrDatabaseError.WithCause(err).WithMsg("查询用户失败")
}
}
// 3. 检查状态
if u.Status == 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.WithCause(err)
}
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.WithCause(err)
}
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)
// Mock Verification
if len(form.IDCard) != 18 {
return errorx.ErrBadRequest.WithMsg("身份证号格式错误")
}
if form.Realname == "" {
return errorx.ErrBadRequest.WithMsg("真实姓名不能为空")
}
tbl, query := models.UserQuery.QueryContext(ctx)
u, err := query.Where(tbl.ID.Eq(uid)).First()
if err != nil {
return errorx.ErrRecordNotFound
}
var metaMap map[string]interface{}
if len(u.Metas) > 0 {
_ = json.Unmarshal(u.Metas, &metaMap)
}
if metaMap == nil {
metaMap = make(map[string]interface{})
}
// Mock encryption
metaMap["real_name"] = form.Realname
metaMap["id_card"] = "ENC:" + form.IDCard
b, _ := json.Marshal(metaMap)
_, err = query.Where(tbl.ID.Eq(uid)).Updates(&models.User{
IsRealNameVerified: true,
VerifiedAt: time.Now(),
Metas: types.JSON(b),
})
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
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.WithCause(err)
}
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: u.Gender, // Direct assignment, types match
Bio: u.Bio,
Balance: float64(u.Balance) / 100.0,
Points: u.Points,
IsRealNameVerified: u.IsRealNameVerified,
}
}