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" "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, tenantID int64, 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. 校验租户成员关系(租户上下文下仅允许成员登录)。 if err := s.ensureTenantMember(ctx, tenantID, u.ID); err != nil { return nil, err } // 5. 生成 Token token, err := s.jwt.CreateToken(s.jwt.CreateClaims(jwt.BaseClaims{ UserID: u.ID, TenantID: tenantID, })) if err != nil { return nil, errorx.ErrInternalError.WithMsg("生成令牌失败") } return &auth_dto.LoginResponse{ Token: token, User: s.ToAuthUserDTO(u), }, nil } func (s *user) ensureTenantMember(ctx context.Context, tenantID, userID int64) error { if tenantID <= 0 { return nil } // 校验租户存在,避免非法租户ID绕过校验。 tblTenant, tenantQuery := models.TenantQuery.QueryContext(ctx) tenant, err := tenantQuery.Where(tblTenant.ID.Eq(tenantID)).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errorx.ErrRecordNotFound.WithCause(err).WithMsg("租户不存在") } return errorx.ErrDatabaseError.WithCause(err) } if tenant.UserID == userID { return nil } tbl, q := models.TenantUserQuery.QueryContext(ctx) exists, err := q.Where( tbl.TenantID.Eq(tenantID), tbl.UserID.Eq(userID), tbl.Status.Eq(consts.UserStatusVerified), ).Exists() if err != nil { return errorx.ErrDatabaseError.WithCause(err) } if !exists { return errorx.ErrForbidden.WithMsg("未加入该租户") } return nil } // GetModelByID 获取指定 ID 的用户model func (s *user) GetModelByID(ctx context.Context, userID int64) (*models.User, error) { tbl, query := models.UserQuery.QueryContext(ctx) u, err := query.Where(tbl.ID.Eq(userID)).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errorx.ErrRecordNotFound } return nil, errorx.ErrDatabaseError.WithCause(err) } return u, nil } // Me 获取当前用户信息 func (s *user) Me(ctx context.Context, userID int64) (*auth_dto.User, error) { u, err := s.GetModelByID(ctx, userID) if err != nil { return nil, err } return s.ToAuthUserDTO(u), nil } // Update 更新用户信息 func (s *user) Update(ctx context.Context, userID int64, form *user_dto.UserUpdate) error { tbl, query := models.UserQuery.QueryContext(ctx) _, err := query.Where(tbl.ID.Eq(userID)).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, userID int64, form *user_dto.RealNameForm) error { // 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(userID)).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(userID)).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, tenantID, userID int64, typeArg string) ([]user_dto.Notification, error) { tbl, query := models.NotificationQuery.QueryContext(ctx) query = query.Where(tbl.UserID.Eq(userID)) if tenantID > 0 { query = query.Where(tbl.TenantID.Eq(tenantID)) } 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: 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: 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, } }