|
|
@@ -12,8 +12,10 @@ import (
|
|
|
"io"
|
|
|
"math/big"
|
|
|
"net/http"
|
|
|
+ "net/smtp"
|
|
|
"net/url"
|
|
|
"regexp"
|
|
|
+ "strconv"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
@@ -32,9 +34,10 @@ func generateInviteCode() string {
|
|
|
return string(code)
|
|
|
}
|
|
|
|
|
|
-// generateUid 生成用户UID
|
|
|
+// generateUid 生成用户UID(时间戳+随机数,防止并发碰撞)
|
|
|
func generateUid() string {
|
|
|
- return fmt.Sprintf("DT%d", time.Now().UnixNano()/1000000)
|
|
|
+ n, _ := rand.Int(rand.Reader, big.NewInt(9999))
|
|
|
+ return fmt.Sprintf("DT%d%04d", time.Now().UnixNano()/1000000, n.Int64())
|
|
|
}
|
|
|
|
|
|
// sendSmsBao 调用短信宝发送短信
|
|
|
@@ -86,7 +89,35 @@ func sendSmsBao(user, pass, sign, phone, code string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// SendSmsCode 发送短信验证码
|
|
|
+// sendEmail 发送邮件验证码
|
|
|
+func sendEmail(host string, port int, user, pass, fromName, toEmail, code string) error {
|
|
|
+ from := user
|
|
|
+ subject := "Verification Code"
|
|
|
+ body := fmt.Sprintf(`
|
|
|
+ <div style="padding:20px;font-family:Arial,sans-serif;">
|
|
|
+ <h2 style="color:#ffc300;">Vitiens</h2>
|
|
|
+ <p>Your verification code is:</p>
|
|
|
+ <h1 style="color:#ffc300;font-size:36px;letter-spacing:8px;">%s</h1>
|
|
|
+ <p>This code will expire in 5 minutes.</p>
|
|
|
+ <p style="color:#999;font-size:12px;">If you did not request this code, please ignore this email.</p>
|
|
|
+ </div>
|
|
|
+ `, code)
|
|
|
+
|
|
|
+ // 构建邮件内容(固定header顺序)
|
|
|
+ msg := fmt.Sprintf("From: %s <%s>\r\n", fromName, from)
|
|
|
+ msg += fmt.Sprintf("To: %s\r\n", toEmail)
|
|
|
+ msg += fmt.Sprintf("Subject: %s\r\n", subject)
|
|
|
+ msg += "MIME-Version: 1.0\r\n"
|
|
|
+ msg += "Content-Type: text/html; charset=UTF-8\r\n"
|
|
|
+ msg += "\r\n" + body
|
|
|
+
|
|
|
+ addr := fmt.Sprintf("%s:%d", host, port)
|
|
|
+ auth := smtp.PlainAuth("", user, pass, host)
|
|
|
+
|
|
|
+ return smtp.SendMail(addr, auth, from, []string{toEmail}, []byte(msg))
|
|
|
+}
|
|
|
+
|
|
|
+// SendSmsCode 发送短信/邮件验证码
|
|
|
func (s *Server) SendSmsCode(c *gin.Context) {
|
|
|
ctx := s.FromContext(c)
|
|
|
db := s.DB()
|
|
|
@@ -103,29 +134,39 @@ func (s *Server) SendSmsCode(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 目前只支持手机号
|
|
|
- if req.Type != "phone" {
|
|
|
+ // 验证类型
|
|
|
+ if req.Type != "phone" && req.Type != "email" {
|
|
|
ctx.Fail("unsupported_type")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 验证手机号格式
|
|
|
- phoneRegex := regexp.MustCompile(`^\d{9,15}$`)
|
|
|
- if !phoneRegex.MatchString(req.Account) {
|
|
|
- ctx.Fail("invalid_phone")
|
|
|
- return
|
|
|
- }
|
|
|
+ var accountKey string // 用于缓存的key
|
|
|
|
|
|
- // 完整手机号(带区号)
|
|
|
- fullPhone := req.Account
|
|
|
- if req.AreaCode != "" {
|
|
|
- fullPhone = req.AreaCode + req.Account
|
|
|
+ if req.Type == "phone" {
|
|
|
+ // 验证手机号格式
|
|
|
+ phoneRegex := regexp.MustCompile(`^\d{9,15}$`)
|
|
|
+ if !phoneRegex.MatchString(req.Account) {
|
|
|
+ ctx.Fail("invalid_phone")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ accountKey = req.Account
|
|
|
+ if req.AreaCode != "" {
|
|
|
+ accountKey = req.AreaCode + req.Account
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 验证邮箱格式
|
|
|
+ emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
|
|
+ if !emailRegex.MatchString(req.Account) {
|
|
|
+ ctx.Fail("invalid_email")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ accountKey = req.Account
|
|
|
}
|
|
|
|
|
|
// 防止重复发送
|
|
|
- cacheKey := fmt.Sprintf("sms:%s:%s", req.Scene, fullPhone)
|
|
|
+ cacheKey := fmt.Sprintf("code:%s:%s", req.Scene, accountKey)
|
|
|
if !ctx.RepeatFilter(cacheKey, 60*time.Second) {
|
|
|
- ctx.Fail("sms_send_too_fast")
|
|
|
+ ctx.Fail("code_send_too_fast")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -133,36 +174,71 @@ func (s *Server) SendSmsCode(c *gin.Context) {
|
|
|
n, _ := rand.Int(rand.Reader, big.NewInt(900000))
|
|
|
code := fmt.Sprintf("%06d", n.Int64()+100000)
|
|
|
|
|
|
- // 获取短信宝配置
|
|
|
- var smsUser, smsPass, smsSign string
|
|
|
- var config entity.DtConfig
|
|
|
- if err := db.Where("`key` = ?", entity.ConfigKeySmsUser).First(&config).Error; err == nil {
|
|
|
- smsUser = config.Value
|
|
|
- }
|
|
|
- if err := db.Where("`key` = ?", entity.ConfigKeySmsPass).First(&config).Error; err == nil {
|
|
|
- smsPass = config.Value
|
|
|
- }
|
|
|
- if err := db.Where("`key` = ?", entity.ConfigKeySmsSign).First(&config).Error; err == nil {
|
|
|
- smsSign = config.Value
|
|
|
- }
|
|
|
-
|
|
|
- // 调用短信宝发送验证码
|
|
|
- if smsUser != "" && smsPass != "" {
|
|
|
- if err := sendSmsBao(smsUser, smsPass, smsSign, fullPhone, code); err != nil {
|
|
|
- ctx.Fail("sms_send_failed")
|
|
|
- return
|
|
|
+ if req.Type == "phone" {
|
|
|
+ // 发送短信验证码
|
|
|
+ var smsUser, smsPass, smsSign string
|
|
|
+ var config entity.DtConfig
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmsUser).First(&config).Error; err == nil {
|
|
|
+ smsUser = config.Value
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmsPass).First(&config).Error; err == nil {
|
|
|
+ smsPass = config.Value
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmsSign).First(&config).Error; err == nil {
|
|
|
+ smsSign = config.Value
|
|
|
+ }
|
|
|
+ if smsUser != "" && smsPass != "" {
|
|
|
+ if err := sendSmsBao(smsUser, smsPass, smsSign, accountKey, code); err != nil {
|
|
|
+ ctx.Fail("sms_send_failed")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ fmt.Printf("[DEV] SMS Code for %s: %s\n", accountKey, code)
|
|
|
}
|
|
|
} else {
|
|
|
- // 开发模式:打印验证码到日志
|
|
|
- fmt.Printf("[DEV] SMS Code for %s: %s\n", fullPhone, code)
|
|
|
+ // 发送邮件验证码
|
|
|
+ var smtpHost, smtpPortStr, smtpUser, smtpPass, smtpFrom string
|
|
|
+ var config entity.DtConfig
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmtpHost).First(&config).Error; err == nil {
|
|
|
+ smtpHost = config.Value
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmtpPort).First(&config).Error; err == nil {
|
|
|
+ smtpPortStr = config.Value
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmtpUser).First(&config).Error; err == nil {
|
|
|
+ smtpUser = config.Value
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmtpPass).First(&config).Error; err == nil {
|
|
|
+ smtpPass = config.Value
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeySmtpFrom).First(&config).Error; err == nil {
|
|
|
+ smtpFrom = config.Value
|
|
|
+ }
|
|
|
+ if smtpFrom == "" {
|
|
|
+ smtpFrom = "Vitiens"
|
|
|
+ }
|
|
|
+
|
|
|
+ if smtpHost != "" && smtpUser != "" && smtpPass != "" {
|
|
|
+ smtpPort, _ := strconv.Atoi(smtpPortStr)
|
|
|
+ if smtpPort == 0 {
|
|
|
+ smtpPort = 587
|
|
|
+ }
|
|
|
+ if err := sendEmail(smtpHost, smtpPort, smtpUser, smtpPass, smtpFrom, req.Account, code); err != nil {
|
|
|
+ fmt.Printf("[ERROR] Send email failed: %v\n", err)
|
|
|
+ ctx.Fail("email_send_failed")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ fmt.Printf("[DEV] Email Code for %s: %s\n", req.Account, code)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 存储验证码到Redis
|
|
|
- codeKey := fmt.Sprintf("smscode:%s:%s", req.Scene, fullPhone)
|
|
|
- redisclient.DefaultClient().Set(c, codeKey, code, 5*time.Minute)
|
|
|
+ codeKey2 := fmt.Sprintf("verifycode:%s:%s", req.Scene, accountKey)
|
|
|
+ redisclient.DefaultClient().Set(c, codeKey2, code, 5*time.Minute)
|
|
|
|
|
|
ctx.OK(gin.H{
|
|
|
- "message": "sms_sent",
|
|
|
+ "message": "code_sent",
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -185,31 +261,49 @@ func (s *Server) Register(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 目前只支持手机号注册
|
|
|
- if req.Type != "phone" {
|
|
|
+ // 验证类型
|
|
|
+ if req.Type != "phone" && req.Type != "email" {
|
|
|
ctx.Fail("unsupported_type")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 完整手机号(带区号)
|
|
|
- fullPhone := req.Account
|
|
|
- if req.AreaCode != "" {
|
|
|
- fullPhone = req.AreaCode + req.Account
|
|
|
+ // 账号Key
|
|
|
+ accountKey := req.Account
|
|
|
+ if req.Type == "phone" && req.AreaCode != "" {
|
|
|
+ accountKey = req.AreaCode + req.Account
|
|
|
}
|
|
|
|
|
|
- // 验证短信验证码
|
|
|
- codeKey := fmt.Sprintf("smscode:register:%s", fullPhone)
|
|
|
+ // 检查验证码尝试次数(防暴力破解,5次锁定15分钟)
|
|
|
+ failKey := fmt.Sprintf("code_fail:register:%s", accountKey)
|
|
|
+ failCount, _ := redisclient.DefaultClient().Get(c, failKey).Int()
|
|
|
+ if failCount >= 5 {
|
|
|
+ ctx.Fail("too_many_attempts")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证验证码
|
|
|
+ codeKey := fmt.Sprintf("verifycode:register:%s", accountKey)
|
|
|
storedCode, err := redisclient.DefaultClient().Get(c, codeKey).Result()
|
|
|
if err != nil || storedCode != req.Code {
|
|
|
+ redisclient.DefaultClient().Incr(c, failKey)
|
|
|
+ redisclient.DefaultClient().Expire(c, failKey, 15*time.Minute)
|
|
|
ctx.Fail("invalid_code")
|
|
|
return
|
|
|
}
|
|
|
+ redisclient.DefaultClient().Del(c, failKey)
|
|
|
|
|
|
- // 检查手机号是否已注册
|
|
|
+ // 检查是否已注册
|
|
|
var existUser entity.DtUser
|
|
|
- if err := db.Where("phone = ?", fullPhone).First(&existUser).Error; err == nil {
|
|
|
- ctx.Fail("phone_registered")
|
|
|
- return
|
|
|
+ if req.Type == "phone" {
|
|
|
+ if err := db.Where("phone = ?", accountKey).First(&existUser).Error; err == nil {
|
|
|
+ ctx.Fail("phone_registered")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if err := db.Where("email = ?", accountKey).First(&existUser).Error; err == nil {
|
|
|
+ ctx.Fail("email_registered")
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 查找邀请人
|
|
|
@@ -232,17 +326,29 @@ func (s *Server) Register(c *gin.Context) {
|
|
|
var defaultLevel entity.DtUserLevel
|
|
|
db.Where("is_default = ?", 1).First(&defaultLevel)
|
|
|
|
|
|
+ // 昵称
|
|
|
+ nickname := ctx.I18n("user_prefix")
|
|
|
+ if len(req.Account) >= 4 {
|
|
|
+ nickname += req.Account[len(req.Account)-4:]
|
|
|
+ } else {
|
|
|
+ nickname += req.Account
|
|
|
+ }
|
|
|
+
|
|
|
// 创建用户
|
|
|
user := &entity.DtUser{
|
|
|
Uid: generateUid(),
|
|
|
- Phone: fullPhone,
|
|
|
Password: string(hashedPassword),
|
|
|
- Nickname: "用户" + req.Account[len(req.Account)-4:],
|
|
|
+ Nickname: nickname,
|
|
|
ParentId: parentId,
|
|
|
LevelId: defaultLevel.Id,
|
|
|
InviteCode: generateInviteCode(),
|
|
|
Status: 1,
|
|
|
}
|
|
|
+ if req.Type == "phone" {
|
|
|
+ user.Phone = accountKey
|
|
|
+ } else {
|
|
|
+ user.Email = accountKey
|
|
|
+ }
|
|
|
|
|
|
tx := db.Begin()
|
|
|
|
|
|
@@ -256,15 +362,15 @@ func (s *Server) Register(c *gin.Context) {
|
|
|
if parentId > 0 {
|
|
|
tx.Model(&entity.DtUser{}).Where("id = ?", parentId).
|
|
|
Updates(map[string]interface{}{
|
|
|
- "direct_invite_count": db.Raw("direct_invite_count + 1"),
|
|
|
- "team_count": db.Raw("team_count + 1"),
|
|
|
+ "direct_invite_count": tx.Raw("direct_invite_count + 1"),
|
|
|
+ "team_count": tx.Raw("team_count + 1"),
|
|
|
})
|
|
|
|
|
|
// 更新上级的团队人数(多级)
|
|
|
var parent entity.DtUser
|
|
|
if err := tx.Where("id = ?", parentId).First(&parent).Error; err == nil && parent.ParentId > 0 {
|
|
|
tx.Model(&entity.DtUser{}).Where("id = ?", parent.ParentId).
|
|
|
- Update("team_count", db.Raw("team_count + 1"))
|
|
|
+ Update("team_count", tx.Raw("team_count + 1"))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -288,6 +394,7 @@ func (s *Server) Register(c *gin.Context) {
|
|
|
"nickname": user.Nickname,
|
|
|
"avatar": user.Avatar,
|
|
|
"phone": user.Phone,
|
|
|
+ "email": user.Email,
|
|
|
},
|
|
|
})
|
|
|
}
|
|
|
@@ -309,17 +416,30 @@ func (s *Server) LoginByPassword(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 完整手机号(带区号)
|
|
|
- fullPhone := req.Account
|
|
|
- if req.AreaCode != "" {
|
|
|
- fullPhone = req.AreaCode + req.Account
|
|
|
+ // 检查登录尝试次数(防暴力破解,5次锁定15分钟)
|
|
|
+ loginFailKey := fmt.Sprintf("login_fail:%s", req.Account)
|
|
|
+ loginFailCount, _ := redisclient.DefaultClient().Get(c, loginFailKey).Int()
|
|
|
+ if loginFailCount >= 5 {
|
|
|
+ ctx.Fail("too_many_attempts")
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// 查找用户
|
|
|
var user entity.DtUser
|
|
|
- if err := db.Where("phone = ?", fullPhone).First(&user).Error; err != nil {
|
|
|
- ctx.Fail("user_not_found")
|
|
|
- return
|
|
|
+ if req.Type == "email" {
|
|
|
+ if err := db.Where("email = ?", req.Account).First(&user).Error; err != nil {
|
|
|
+ ctx.Fail("user_not_found")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ fullPhone := req.Account
|
|
|
+ if req.AreaCode != "" {
|
|
|
+ fullPhone = req.AreaCode + req.Account
|
|
|
+ }
|
|
|
+ if err := db.Where("phone = ?", fullPhone).First(&user).Error; err != nil {
|
|
|
+ ctx.Fail("user_not_found")
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 检查用户状态
|
|
|
@@ -330,13 +450,16 @@ func (s *Server) LoginByPassword(c *gin.Context) {
|
|
|
|
|
|
// 验证密码
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
|
|
+ redisclient.DefaultClient().Incr(c, loginFailKey)
|
|
|
+ redisclient.DefaultClient().Expire(c, loginFailKey, 15*time.Minute)
|
|
|
ctx.Fail("invalid_password")
|
|
|
return
|
|
|
}
|
|
|
+ redisclient.DefaultClient().Del(c, loginFailKey)
|
|
|
|
|
|
// 更新登录时间
|
|
|
db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
|
|
|
- Update("last_login_at", time.Now().Unix())
|
|
|
+ Update("last_login_time", time.Now().Unix())
|
|
|
|
|
|
// 生成Token
|
|
|
token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
|
|
|
@@ -353,6 +476,7 @@ func (s *Server) LoginByPassword(c *gin.Context) {
|
|
|
"nickname": user.Nickname,
|
|
|
"avatar": user.Avatar,
|
|
|
"phone": user.Phone,
|
|
|
+ "email": user.Email,
|
|
|
},
|
|
|
})
|
|
|
}
|
|
|
@@ -379,13 +503,24 @@ func (s *Server) LoginBySms(c *gin.Context) {
|
|
|
fullPhone = req.AreaCode + req.Phone
|
|
|
}
|
|
|
|
|
|
+ // 检查验证码尝试次数(防暴力破解,5次锁定15分钟)
|
|
|
+ failKey := fmt.Sprintf("code_fail:login:%s", fullPhone)
|
|
|
+ failCount, _ := redisclient.DefaultClient().Get(c, failKey).Int()
|
|
|
+ if failCount >= 5 {
|
|
|
+ ctx.Fail("too_many_attempts")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
// 验证短信验证码
|
|
|
- codeKey := fmt.Sprintf("smscode:login:%s", fullPhone)
|
|
|
+ codeKey := fmt.Sprintf("verifycode:login:%s", fullPhone)
|
|
|
storedCode, err := redisclient.DefaultClient().Get(c, codeKey).Result()
|
|
|
if err != nil || storedCode != req.Code {
|
|
|
+ redisclient.DefaultClient().Incr(c, failKey)
|
|
|
+ redisclient.DefaultClient().Expire(c, failKey, 15*time.Minute)
|
|
|
ctx.Fail("invalid_code")
|
|
|
return
|
|
|
}
|
|
|
+ redisclient.DefaultClient().Del(c, failKey)
|
|
|
|
|
|
// 查找用户
|
|
|
var user entity.DtUser
|
|
|
@@ -402,7 +537,7 @@ func (s *Server) LoginBySms(c *gin.Context) {
|
|
|
|
|
|
// 更新登录时间
|
|
|
db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
|
|
|
- Update("last_login_at", time.Now().Unix())
|
|
|
+ Update("last_login_time", time.Now().Unix())
|
|
|
|
|
|
// 删除验证码
|
|
|
redisclient.DefaultClient().Del(c, codeKey)
|
|
|
@@ -422,6 +557,7 @@ func (s *Server) LoginBySms(c *gin.Context) {
|
|
|
"nickname": user.Nickname,
|
|
|
"avatar": user.Avatar,
|
|
|
"phone": user.Phone,
|
|
|
+ "email": user.Email,
|
|
|
},
|
|
|
})
|
|
|
}
|
|
|
@@ -444,25 +580,43 @@ func (s *Server) ResetPassword(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 完整手机号(带区号)
|
|
|
- fullPhone := req.Account
|
|
|
- if req.AreaCode != "" {
|
|
|
- fullPhone = req.AreaCode + req.Account
|
|
|
+ // 账号Key
|
|
|
+ accountKey := req.Account
|
|
|
+ if req.Type == "phone" && req.AreaCode != "" {
|
|
|
+ accountKey = req.AreaCode + req.Account
|
|
|
}
|
|
|
|
|
|
- // 验证短信验证码
|
|
|
- codeKey := fmt.Sprintf("smscode:reset:%s", fullPhone)
|
|
|
+ // 检查验证码尝试次数(防暴力破解,5次锁定15分钟)
|
|
|
+ failKey := fmt.Sprintf("code_fail:reset:%s", accountKey)
|
|
|
+ failCount, _ := redisclient.DefaultClient().Get(c, failKey).Int()
|
|
|
+ if failCount >= 5 {
|
|
|
+ ctx.Fail("too_many_attempts")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证验证码
|
|
|
+ codeKey := fmt.Sprintf("verifycode:reset:%s", accountKey)
|
|
|
storedCode, err := redisclient.DefaultClient().Get(c, codeKey).Result()
|
|
|
if err != nil || storedCode != req.Code {
|
|
|
+ redisclient.DefaultClient().Incr(c, failKey)
|
|
|
+ redisclient.DefaultClient().Expire(c, failKey, 15*time.Minute)
|
|
|
ctx.Fail("invalid_code")
|
|
|
return
|
|
|
}
|
|
|
+ redisclient.DefaultClient().Del(c, failKey)
|
|
|
|
|
|
// 查找用户
|
|
|
var user entity.DtUser
|
|
|
- if err := db.Where("phone = ?", fullPhone).First(&user).Error; err != nil {
|
|
|
- ctx.Fail("user_not_found")
|
|
|
- return
|
|
|
+ if req.Type == "email" {
|
|
|
+ if err := db.Where("email = ?", accountKey).First(&user).Error; err != nil {
|
|
|
+ ctx.Fail("user_not_found")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if err := db.Where("phone = ?", accountKey).First(&user).Error; err != nil {
|
|
|
+ ctx.Fail("user_not_found")
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 加密新密码
|
|
|
@@ -533,7 +687,7 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
|
|
|
// 更新登录时间
|
|
|
db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
|
|
|
- Update("last_login_at", time.Now().Unix())
|
|
|
+ Update("last_login_time", time.Now().Unix())
|
|
|
|
|
|
token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
|
|
|
if err != nil {
|
|
|
@@ -569,15 +723,14 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
|
|
|
nickname := req.Nickname
|
|
|
if nickname == "" {
|
|
|
- nickname = req.Provider + "用户"
|
|
|
+ nickname = req.Provider + ctx.I18n("user_prefix")
|
|
|
}
|
|
|
|
|
|
- // 为OAuth用户生成唯一占位手机号
|
|
|
- openIdPart := req.OpenId
|
|
|
- if len(openIdPart) > 16 {
|
|
|
- openIdPart = openIdPart[:16]
|
|
|
- }
|
|
|
- oauthPhone := fmt.Sprintf("oauth_%s_%s", req.Provider, openIdPart)
|
|
|
+ // 为OAuth用户生成唯一占位手机号(用MD5避免截断碰撞)
|
|
|
+ h := md5.New()
|
|
|
+ h.Write([]byte(req.OpenId))
|
|
|
+ openIdHash := hex.EncodeToString(h.Sum(nil))[:16]
|
|
|
+ oauthPhone := fmt.Sprintf("oauth_%s_%s", req.Provider, openIdHash)
|
|
|
|
|
|
user := &entity.DtUser{
|
|
|
Uid: generateUid(),
|
|
|
@@ -619,8 +772,8 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
if parentId > 0 {
|
|
|
tx.Model(&entity.DtUser{}).Where("id = ?", parentId).
|
|
|
Updates(map[string]interface{}{
|
|
|
- "direct_invite_count": db.Raw("direct_invite_count + 1"),
|
|
|
- "team_count": db.Raw("team_count + 1"),
|
|
|
+ "direct_invite_count": tx.Raw("direct_invite_count + 1"),
|
|
|
+ "team_count": tx.Raw("team_count + 1"),
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -641,6 +794,7 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
"nickname": user.Nickname,
|
|
|
"avatar": user.Avatar,
|
|
|
"phone": user.Phone,
|
|
|
+ "email": user.Email,
|
|
|
},
|
|
|
"isNew": true,
|
|
|
})
|