auth.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. package daytask
  2. import (
  3. "app/apis/middleware"
  4. "app/commons/core/redisclient"
  5. "app/commons/model/entity"
  6. "crypto/rand"
  7. "fmt"
  8. "github.com/gin-gonic/gin"
  9. "golang.org/x/crypto/bcrypt"
  10. "math/big"
  11. "regexp"
  12. "time"
  13. )
  14. // generateInviteCode 生成邀请码
  15. func generateInviteCode() string {
  16. const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  17. code := make([]byte, 8)
  18. for i := range code {
  19. n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
  20. code[i] = charset[n.Int64()]
  21. }
  22. return string(code)
  23. }
  24. // generateUid 生成用户UID
  25. func generateUid() string {
  26. return fmt.Sprintf("DT%d", time.Now().UnixNano()/1000000)
  27. }
  28. // SendSmsCode 发送短信验证码
  29. func (s *Server) SendSmsCode(c *gin.Context) {
  30. ctx := s.FromContext(c)
  31. type Req struct {
  32. Phone string `json:"phone" binding:"required"`
  33. Type string `json:"type" binding:"required"` // register/login/reset
  34. }
  35. var req Req
  36. if err := c.ShouldBindJSON(&req); err != nil {
  37. ctx.Fail("invalid_params")
  38. return
  39. }
  40. // 验证手机号格式
  41. phoneRegex := regexp.MustCompile(`^\d{9,15}$`)
  42. if !phoneRegex.MatchString(req.Phone) {
  43. ctx.Fail("invalid_phone")
  44. return
  45. }
  46. // 防止重复发送
  47. cacheKey := fmt.Sprintf("sms:%s:%s", req.Type, req.Phone)
  48. if !ctx.RepeatFilter(cacheKey, 60*time.Second) {
  49. ctx.Fail("sms_send_too_fast")
  50. return
  51. }
  52. // 生成验证码
  53. n, _ := rand.Int(rand.Reader, big.NewInt(900000))
  54. code := fmt.Sprintf("%06d", n.Int64()+100000)
  55. // TODO: 调用短信服务发送验证码
  56. // smsService.Send(req.Phone, code)
  57. // 存储验证码到Redis
  58. codeKey := fmt.Sprintf("smscode:%s:%s", req.Type, req.Phone)
  59. redisclient.DefaultClient().Set(c, codeKey, code, 5*time.Minute)
  60. ctx.OK(gin.H{
  61. "message": "sms_sent",
  62. })
  63. }
  64. // Register 用户注册
  65. func (s *Server) Register(c *gin.Context) {
  66. ctx := s.FromContext(c)
  67. db := s.DB()
  68. type Req struct {
  69. Phone string `json:"phone" binding:"required"`
  70. Code string `json:"code" binding:"required"`
  71. Password string `json:"password" binding:"required,min=6"`
  72. InviteCode string `json:"inviteCode"`
  73. }
  74. var req Req
  75. if err := c.ShouldBindJSON(&req); err != nil {
  76. ctx.Fail("invalid_params")
  77. return
  78. }
  79. // 验证短信验证码
  80. codeKey := fmt.Sprintf("smscode:register:%s", req.Phone)
  81. storedCode, err := redisclient.DefaultClient().Get(c, codeKey).Result()
  82. if err != nil || storedCode != req.Code {
  83. ctx.Fail("invalid_code")
  84. return
  85. }
  86. // 检查手机号是否已注册
  87. var existUser entity.DtUser
  88. if err := db.Where("phone = ?", req.Phone).First(&existUser).Error; err == nil {
  89. ctx.Fail("phone_registered")
  90. return
  91. }
  92. // 查找邀请人
  93. var parentId int64 = 0
  94. if req.InviteCode != "" {
  95. var inviter entity.DtUser
  96. if err := db.Where("invite_code = ?", req.InviteCode).First(&inviter).Error; err == nil {
  97. parentId = inviter.Id
  98. }
  99. }
  100. // 加密密码
  101. hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
  102. if err != nil {
  103. ctx.Fail("register_failed")
  104. return
  105. }
  106. // 获取默认等级
  107. var defaultLevel entity.DtUserLevel
  108. db.Where("is_default = ?", 1).First(&defaultLevel)
  109. // 创建用户
  110. user := &entity.DtUser{
  111. Uid: generateUid(),
  112. Phone: req.Phone,
  113. Password: string(hashedPassword),
  114. Nickname: "用户" + req.Phone[len(req.Phone)-4:],
  115. ParentId: parentId,
  116. LevelId: defaultLevel.Id,
  117. InviteCode: generateInviteCode(),
  118. Status: 1,
  119. }
  120. tx := db.Begin()
  121. if err := tx.Create(user).Error; err != nil {
  122. tx.Rollback()
  123. ctx.Fail("register_failed")
  124. return
  125. }
  126. // 更新邀请人的直推人数和团队人数
  127. if parentId > 0 {
  128. tx.Model(&entity.DtUser{}).Where("id = ?", parentId).
  129. Updates(map[string]interface{}{
  130. "direct_invite_count": db.Raw("direct_invite_count + 1"),
  131. "team_count": db.Raw("team_count + 1"),
  132. })
  133. // 更新上级的团队人数(多级)
  134. var parent entity.DtUser
  135. if err := tx.Where("id = ?", parentId).First(&parent).Error; err == nil && parent.ParentId > 0 {
  136. tx.Model(&entity.DtUser{}).Where("id = ?", parent.ParentId).
  137. Update("team_count", db.Raw("team_count + 1"))
  138. }
  139. }
  140. tx.Commit()
  141. // 删除验证码
  142. redisclient.DefaultClient().Del(c, codeKey)
  143. // 生成Token
  144. token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
  145. if err != nil {
  146. ctx.Fail("register_failed")
  147. return
  148. }
  149. ctx.OK(gin.H{
  150. "token": token,
  151. "user": gin.H{
  152. "id": user.Id,
  153. "uid": user.Uid,
  154. "nickname": user.Nickname,
  155. "avatar": user.Avatar,
  156. "phone": user.Phone,
  157. },
  158. })
  159. }
  160. // LoginByPassword 密码登录
  161. func (s *Server) LoginByPassword(c *gin.Context) {
  162. ctx := s.FromContext(c)
  163. db := s.DB()
  164. type Req struct {
  165. Phone string `json:"phone" binding:"required"`
  166. Password string `json:"password" binding:"required"`
  167. }
  168. var req Req
  169. if err := c.ShouldBindJSON(&req); err != nil {
  170. ctx.Fail("invalid_params")
  171. return
  172. }
  173. // 查找用户
  174. var user entity.DtUser
  175. if err := db.Where("phone = ?", req.Phone).First(&user).Error; err != nil {
  176. ctx.Fail("user_not_found")
  177. return
  178. }
  179. // 检查用户状态
  180. if user.Status != 1 {
  181. ctx.Fail("user_disabled")
  182. return
  183. }
  184. // 验证密码
  185. if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
  186. ctx.Fail("invalid_password")
  187. return
  188. }
  189. // 更新登录时间
  190. db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
  191. Update("last_login_at", time.Now().Unix())
  192. // 生成Token
  193. token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
  194. if err != nil {
  195. ctx.Fail("login_failed")
  196. return
  197. }
  198. ctx.OK(gin.H{
  199. "token": token,
  200. "user": gin.H{
  201. "id": user.Id,
  202. "uid": user.Uid,
  203. "nickname": user.Nickname,
  204. "avatar": user.Avatar,
  205. "phone": user.Phone,
  206. },
  207. })
  208. }
  209. // LoginBySms 短信验证码登录
  210. func (s *Server) LoginBySms(c *gin.Context) {
  211. ctx := s.FromContext(c)
  212. db := s.DB()
  213. type Req struct {
  214. Phone string `json:"phone" binding:"required"`
  215. Code string `json:"code" binding:"required"`
  216. }
  217. var req Req
  218. if err := c.ShouldBindJSON(&req); err != nil {
  219. ctx.Fail("invalid_params")
  220. return
  221. }
  222. // 验证短信验证码
  223. codeKey := fmt.Sprintf("smscode:login:%s", req.Phone)
  224. storedCode, err := redisclient.DefaultClient().Get(c, codeKey).Result()
  225. if err != nil || storedCode != req.Code {
  226. ctx.Fail("invalid_code")
  227. return
  228. }
  229. // 查找用户
  230. var user entity.DtUser
  231. if err := db.Where("phone = ?", req.Phone).First(&user).Error; err != nil {
  232. ctx.Fail("user_not_found")
  233. return
  234. }
  235. // 检查用户状态
  236. if user.Status != 1 {
  237. ctx.Fail("user_disabled")
  238. return
  239. }
  240. // 更新登录时间
  241. db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
  242. Update("last_login_at", time.Now().Unix())
  243. // 删除验证码
  244. redisclient.DefaultClient().Del(c, codeKey)
  245. // 生成Token
  246. token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
  247. if err != nil {
  248. ctx.Fail("login_failed")
  249. return
  250. }
  251. ctx.OK(gin.H{
  252. "token": token,
  253. "user": gin.H{
  254. "id": user.Id,
  255. "uid": user.Uid,
  256. "nickname": user.Nickname,
  257. "avatar": user.Avatar,
  258. "phone": user.Phone,
  259. },
  260. })
  261. }
  262. // ResetPassword 重置密码
  263. func (s *Server) ResetPassword(c *gin.Context) {
  264. ctx := s.FromContext(c)
  265. db := s.DB()
  266. type Req struct {
  267. Phone string `json:"phone" binding:"required"`
  268. Code string `json:"code" binding:"required"`
  269. NewPassword string `json:"newPassword" binding:"required,min=6"`
  270. }
  271. var req Req
  272. if err := c.ShouldBindJSON(&req); err != nil {
  273. ctx.Fail("invalid_params")
  274. return
  275. }
  276. // 验证短信验证码
  277. codeKey := fmt.Sprintf("smscode:reset:%s", req.Phone)
  278. storedCode, err := redisclient.DefaultClient().Get(c, codeKey).Result()
  279. if err != nil || storedCode != req.Code {
  280. ctx.Fail("invalid_code")
  281. return
  282. }
  283. // 查找用户
  284. var user entity.DtUser
  285. if err := db.Where("phone = ?", req.Phone).First(&user).Error; err != nil {
  286. ctx.Fail("user_not_found")
  287. return
  288. }
  289. // 加密新密码
  290. hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
  291. if err != nil {
  292. ctx.Fail("reset_failed")
  293. return
  294. }
  295. // 更新密码
  296. db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
  297. Update("password", string(hashedPassword))
  298. // 删除验证码
  299. redisclient.DefaultClient().Del(c, codeKey)
  300. ctx.OK(gin.H{
  301. "message": "password_reset_success",
  302. })
  303. }
  304. // OAuthLogin OAuth登录(Google/Zalo等)
  305. func (s *Server) OAuthLogin(c *gin.Context) {
  306. ctx := s.FromContext(c)
  307. db := s.DB()
  308. type Req struct {
  309. Provider string `json:"provider" binding:"required"` // google/zalo/telegram
  310. OpenId string `json:"openId" binding:"required"`
  311. Nickname string `json:"nickname"`
  312. Avatar string `json:"avatar"`
  313. InviteCode string `json:"inviteCode"`
  314. }
  315. var req Req
  316. if err := c.ShouldBindJSON(&req); err != nil {
  317. ctx.Fail("invalid_params")
  318. return
  319. }
  320. // 查找是否已绑定
  321. var social entity.DtUserSocial
  322. if err := db.Where("platform = ? AND open_id = ?", req.Provider, req.OpenId).First(&social).Error; err == nil {
  323. // 已绑定,直接登录
  324. var user entity.DtUser
  325. if err := db.Where("id = ?", social.UserId).First(&user).Error; err != nil {
  326. ctx.Fail("user_not_found")
  327. return
  328. }
  329. if user.Status != 1 {
  330. ctx.Fail("user_disabled")
  331. return
  332. }
  333. // 更新登录时间
  334. db.Model(&entity.DtUser{}).Where("id = ?", user.Id).
  335. Update("last_login_at", time.Now().Unix())
  336. token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
  337. if err != nil {
  338. ctx.Fail("login_failed")
  339. return
  340. }
  341. ctx.OK(gin.H{
  342. "token": token,
  343. "user": gin.H{
  344. "id": user.Id,
  345. "uid": user.Uid,
  346. "nickname": user.Nickname,
  347. "avatar": user.Avatar,
  348. "phone": user.Phone,
  349. },
  350. })
  351. return
  352. }
  353. // 未绑定,创建新用户
  354. var parentId int64 = 0
  355. if req.InviteCode != "" {
  356. var inviter entity.DtUser
  357. if err := db.Where("invite_code = ?", req.InviteCode).First(&inviter).Error; err == nil {
  358. parentId = inviter.Id
  359. }
  360. }
  361. // 获取默认等级
  362. var defaultLevel entity.DtUserLevel
  363. db.Where("is_default = ?", 1).First(&defaultLevel)
  364. nickname := req.Nickname
  365. if nickname == "" {
  366. nickname = req.Provider + "用户"
  367. }
  368. user := &entity.DtUser{
  369. Uid: generateUid(),
  370. Nickname: nickname,
  371. Avatar: req.Avatar,
  372. ParentId: parentId,
  373. LevelId: defaultLevel.Id,
  374. InviteCode: generateInviteCode(),
  375. Status: 1,
  376. }
  377. tx := db.Begin()
  378. if err := tx.Create(user).Error; err != nil {
  379. tx.Rollback()
  380. ctx.Fail("login_failed")
  381. return
  382. }
  383. // 创建社交账号绑定
  384. social = entity.DtUserSocial{
  385. UserId: user.Id,
  386. Platform: req.Provider,
  387. Account: req.OpenId,
  388. Nickname: req.Nickname,
  389. Avatar: req.Avatar,
  390. }
  391. if err := tx.Create(&social).Error; err != nil {
  392. tx.Rollback()
  393. ctx.Fail("login_failed")
  394. return
  395. }
  396. // 更新邀请人统计
  397. if parentId > 0 {
  398. tx.Model(&entity.DtUser{}).Where("id = ?", parentId).
  399. Updates(map[string]interface{}{
  400. "direct_invite_count": db.Raw("direct_invite_count + 1"),
  401. "team_count": db.Raw("team_count + 1"),
  402. })
  403. }
  404. tx.Commit()
  405. token, err := middleware.GenerateJWT(middleware.Member{ID: user.Id, Uid: user.Uid})
  406. if err != nil {
  407. ctx.Fail("login_failed")
  408. return
  409. }
  410. ctx.OK(gin.H{
  411. "token": token,
  412. "user": gin.H{
  413. "id": user.Id,
  414. "uid": user.Uid,
  415. "nickname": user.Nickname,
  416. "avatar": user.Avatar,
  417. "phone": user.Phone,
  418. },
  419. "isNew": true,
  420. })
  421. }