auth_service.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. package auth
  2. import (
  3. "crypto/rand"
  4. "encoding/base64"
  5. "errors"
  6. "eta/eta_mini_ht_api/common/component/cache"
  7. logger "eta/eta_mini_ht_api/common/component/log"
  8. "eta/eta_mini_ht_api/common/component/wechat"
  9. "eta/eta_mini_ht_api/common/exception"
  10. authUtils "eta/eta_mini_ht_api/common/utils/auth"
  11. "eta/eta_mini_ht_api/common/utils/jwt"
  12. "eta/eta_mini_ht_api/common/utils/redis"
  13. "eta/eta_mini_ht_api/common/utils/sms"
  14. stringUtils "eta/eta_mini_ht_api/common/utils/string"
  15. smsService "eta/eta_mini_ht_api/domian/sms"
  16. userService "eta/eta_mini_ht_api/domian/user"
  17. userDao "eta/eta_mini_ht_api/models/user"
  18. "fmt"
  19. "gorm.io/gorm"
  20. "strings"
  21. )
  22. var (
  23. smsSender sms.SMSClient
  24. wechatClient *wechat.Client
  25. redisCache *cache.RedisCache
  26. )
  27. const (
  28. emptyPhoneNum = "-"
  29. )
  30. func message() sms.SMSClient {
  31. if smsSender == nil {
  32. smsSender = sms.GetInstance()
  33. }
  34. return smsSender
  35. }
  36. func wx() *wechat.Client {
  37. if wechatClient == nil {
  38. wechatClient = wechat.GetInstance()
  39. }
  40. return wechatClient
  41. }
  42. func SendSMSCode(mobile string) (err error) {
  43. //code := rd().GetString(redis.GenerateSmsKey(mobile))
  44. //if stringUtils.IsEmptyOrNil(code) {
  45. // code, err = authUtils.GenerateCode(6)
  46. // if err != nil {
  47. // logger.Warn("生成验证码失败:%v", err)
  48. // return exception.New(exception.SMSCodeGenerateFailed)
  49. // }
  50. // codeDTO := smsService.CodeDTO{
  51. // Mobile: mobile,
  52. // Code: code,
  53. // ExpireMinute: message().GetExpireMinute(),
  54. // }
  55. // //消息domain层
  56. // var smid int
  57. // smid, err = smsService.SendSMSCode(codeDTO)
  58. // if err != nil {
  59. // logger.Error("发送短信失败:%v", err)
  60. // err = exception.New(exception.SendingSMSFailed)
  61. // }
  62. // logger.Debug("验证码:%v", code)
  63. // _, err = message().SendSms(mobile, code, smid)
  64. // if err != nil {
  65. // logger.Error("发送短信失败:%v", err)
  66. // err = exception.New(exception.SendingSMSFailed)
  67. // }
  68. // return
  69. //}
  70. //return exception.New(exception.SMSCodeAlreadySent)
  71. code := rd().GetString(redis.GenerateSmsKey(mobile))
  72. if stringUtils.IsEmptyOrNil(code) {
  73. logger.Info("更新验证码 手机号[%v],旧验证码[%v]", mobile, code)
  74. }
  75. code, err = authUtils.GenerateCode(6)
  76. logger.Info("更新验证码 手机号[%v],新验证码[%v]", mobile, code)
  77. if err != nil {
  78. logger.Warn("生成验证码失败:%v", err)
  79. return exception.New(exception.SMSCodeGenerateFailed)
  80. }
  81. codeDTO := smsService.CodeDTO{
  82. Mobile: mobile,
  83. Code: code,
  84. ExpireMinute: message().GetExpireMinute(),
  85. }
  86. //消息domain层
  87. var smid int
  88. smid, err = smsService.SendSMSCode(codeDTO)
  89. if err != nil {
  90. logger.Error("发送短信失败:%v", err)
  91. err = exception.New(exception.SendingSMSFailed)
  92. }
  93. logger.Debug("验证码:%v", code)
  94. _, err = message().SendSms(mobile, code, smid)
  95. if err != nil {
  96. logger.Error("发送短信失败:%v", err)
  97. err = exception.New(exception.SendingSMSFailed)
  98. }
  99. return
  100. }
  101. func CheckUser(mobile string, code string) (err error) {
  102. smsCode := rd().GetString(redis.GenerateSmsKey(mobile))
  103. if stringUtils.IsEmptyOrNil(smsCode) {
  104. logger.Warn("验证码已过期:%v", code)
  105. codeDTO := smsService.CodeDTO{
  106. Mobile: mobile,
  107. Code: code,
  108. }
  109. _ = smsService.TryExpireCode(codeDTO)
  110. err = exception.New(exception.SMSCodeExpired)
  111. return
  112. }
  113. if smsCode != code {
  114. err = exception.New(exception.SMSCodeError)
  115. return
  116. }
  117. return nil
  118. }
  119. type LoginDTO struct {
  120. VerifyCode string
  121. Code string
  122. Mobile string
  123. }
  124. func Logout(userId int, openId string) (err error) {
  125. //设置用户为登录状态
  126. err = userService.UserLogout(userId)
  127. if err != nil {
  128. logger.Error("用户退出登录失败:%v", err)
  129. err = exception.New(exception.LogoutFailed)
  130. return
  131. }
  132. _ = userService.UserLogoutFlow(userId)
  133. err = rd().Delete(redis.GenerateTokenKey(openId))
  134. if err != nil {
  135. err = rd().SetString(redis.GenerateTokenKey(openId), "", 1)
  136. }
  137. if err != nil {
  138. logger.Error("用户退出登录失败:%v", err)
  139. err = exception.New(exception.LogoutFailed)
  140. return
  141. }
  142. return
  143. }
  144. func Login(userId int, openId, mobile, verifyCode string) (token string, err error) {
  145. //设置用户为登录状态
  146. err = userService.UserLogin(userId)
  147. if err != nil {
  148. logger.Error("用户登录失败:%v", err)
  149. err = exception.New(exception.GenerateTokenFailed)
  150. return
  151. }
  152. _ = userService.UserLoginFlow(userId)
  153. token, err = jwt.CreateToken(openId, mobile, jwt.AccessToken)
  154. if err != nil {
  155. err = exception.New(exception.GenerateTokenFailed)
  156. return
  157. }
  158. //保险先删一下
  159. _ = rd().Delete(redis.GenerateTokenKey(openId))
  160. err = rd().SetString(redis.GenerateTokenKey(openId), token, 90*24*60*60)
  161. if err != nil {
  162. err = exception.New(exception.GenerateTokenFailed)
  163. return
  164. }
  165. //清除手机验证码
  166. codeDTO := smsService.CodeDTO{
  167. Mobile: mobile,
  168. Code: verifyCode,
  169. }
  170. //登录成功删除短信验证码,数据库留痕
  171. err = rd().Delete(redis.GenerateSmsKey(mobile))
  172. if err != nil {
  173. logger.Error("清除redis 短信验证码失败,%v", err)
  174. err = rd().SetString(redis.GenerateSmsKey(mobile), "", 1)
  175. return
  176. }
  177. _ = smsService.TryVerifiedCode(codeDTO)
  178. return
  179. }
  180. func BindMobile(userId int, mobile string, areaCode string) (err error) {
  181. err = userService.BindUserMobile(userId, mobile, areaCode)
  182. if err != nil {
  183. err = exception.New(exception.BindMobileFailed)
  184. return
  185. }
  186. return
  187. }
  188. func RefreshToken(code string) (token string, isBindMobile int, status string, tokenType string, err error) {
  189. //注册用户
  190. var wechatInfo wechat.WxUser
  191. //微信请求异常
  192. wechatInfo, err = wx().Login(code)
  193. //微信客户端的异常不做处理,已经是EtaError
  194. if err != nil {
  195. logger.Error("获取微信用户信息失败,错误信息:%v", err)
  196. return
  197. }
  198. user, err := userService.GetTemplateUserByOpenId(wechatInfo.OpenId)
  199. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  200. err = exception.New(exception.TemplateUserFoundFailed)
  201. return
  202. }
  203. //用户记录不存在则返回unbind
  204. //未注册/未绑定
  205. var mobile string
  206. if errors.Is(err, gorm.ErrRecordNotFound) || user.Mobile == "" {
  207. status = string(userService.UnBind)
  208. tokenType = jwt.GuestToken
  209. isBindMobile = 0
  210. mobile = emptyPhoneNum
  211. } else {
  212. isBindMobile = 1
  213. //已绑定(登出)
  214. if user.LoginStatus == userDao.Logout {
  215. tokenType = jwt.GuestToken
  216. mobile = emptyPhoneNum
  217. status = string(userService.Logout)
  218. } else {
  219. //已绑定(登录)
  220. tokenType = jwt.AccessToken
  221. mobile = user.Mobile
  222. status = string(userService.Login)
  223. }
  224. }
  225. //通过unionId 查询用户是否已注册,存在是因为先发生了公众号的关注,所以只需要绑定小程序openId就行
  226. wxUser, err := userService.GetTemplateUserByUnionId(wechatInfo.UnionId)
  227. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  228. err = exception.New(exception.TemplateUserFoundFailed)
  229. return
  230. }
  231. if errors.Is(err, gorm.ErrRecordNotFound) {
  232. user.OpenId = wechatInfo.OpenId
  233. user.UnionId = wechatInfo.UnionId
  234. err = userService.RegisterTemplateUser(&user)
  235. if err != nil {
  236. err = exception.New(exception.TemplateUserCreateFailed)
  237. return
  238. }
  239. } else { //修改微信小程序openid
  240. err = userService.BindUserXcxOpenId(wxUser.Id, user.OpenId)
  241. if err != nil {
  242. err = exception.New(exception.TemplateUserBindFailed)
  243. return
  244. }
  245. }
  246. token, err = jwt.CreateToken(wechatInfo.OpenId, mobile, tokenType)
  247. if err != nil {
  248. logger.Error("生成token失败:%v", err)
  249. err = exception.New(exception.GenerateTokenFailed)
  250. return
  251. }
  252. err = rd().SetString(redis.GenerateTokenKey(wechatInfo.OpenId), token, 90*24*60*60)
  253. if err != nil {
  254. err = exception.New(exception.GenerateTokenFailed)
  255. return
  256. }
  257. return
  258. }
  259. func GetAreaCodes() (list []userService.AreaCodeDTO, err error) {
  260. list, err = userService.GetAreaCodes()
  261. if err != nil {
  262. err = exception.New(exception.GetAreaCodesFailed)
  263. }
  264. return
  265. }
  266. func GetValidAreaCodes() (list []string) {
  267. str := rd().GetString(redis.ValidAreaCode)
  268. if str == "" {
  269. codes, err := userService.GetValidAreaCodes()
  270. if err != nil {
  271. return []string{}
  272. }
  273. var validCodes []string
  274. for _, code := range codes {
  275. validCodes = append(validCodes, code.CodeValue)
  276. }
  277. err = rd().SetString(redis.ValidAreaCode, strings.Join(validCodes, ","), 60*60)
  278. if err != nil {
  279. logger.Error("设置Redis区号失败:%v", err)
  280. list = validCodes
  281. }
  282. list = strings.Split(rd().GetString(redis.ValidAreaCode), ",")
  283. } else {
  284. list = strings.Split(str, ",")
  285. }
  286. return
  287. }
  288. func initUser(user wechat.WxUser, dto LoginDTO) userService.UserDTO {
  289. username := generateRandomUsername(user.OpenId)
  290. if stringUtils.IsBlank(username) {
  291. username = dto.Mobile
  292. }
  293. return userService.UserDTO{
  294. OpenId: user.OpenId,
  295. UnionId: user.UnionId,
  296. Username: username,
  297. Mobile: dto.Mobile,
  298. }
  299. }
  300. // GenerateRandomUsername 生成基于UnionId的随机用户名
  301. func generateRandomUsername(openId string) string {
  302. // 生成一个随机的4字节二进制数据
  303. randomBytes := make([]byte, 4)
  304. _, err := rand.Read(randomBytes)
  305. if err != nil {
  306. return ""
  307. }
  308. // 将随机字节转换为base64编码的URL安全字符串
  309. // 并去掉可能出现的等于号
  310. randomStr := strings.TrimRight(base64.URLEncoding.EncodeToString(randomBytes), "=")
  311. // 拼接UnionId和随机字符串
  312. username := fmt.Sprintf("%s_%s", openId, randomStr)
  313. return username
  314. }
  315. func rd() *cache.RedisCache {
  316. if redisCache == nil {
  317. redisCache = cache.GetInstance()
  318. }
  319. return redisCache
  320. }
  321. func BindXcxOpenId(userId int, openId string) (err error) {
  322. err = userService.BindUserXcxOpenId(userId, openId)
  323. return
  324. }
  325. // 绑定微信公众号
  326. func BindWxGzh(code string) (isBind bool, err error) {
  327. //注册用户
  328. wxUser, err := GetWxUserInfo(code)
  329. if err != nil {
  330. logger.Error("获取微信公众号用户信息失败" + err.Error())
  331. err = exception.New(exception.TemplateUserNotFound)
  332. return
  333. }
  334. logger.Info("GetWxUserInfo:")
  335. if wxUser.ErrCode != 0 {
  336. logger.Error("获取微信公众号用户信息失败" + wxUser.ErrMsg)
  337. err = exception.New(exception.TemplateUserNotFound)
  338. return
  339. }
  340. return BindWxGzhByOpenId(wxUser.OpenId)
  341. }
  342. // BindWxGzhByOpenId
  343. // @Description: 通过openid绑定微信公众号
  344. // @author: Roc
  345. // @datetime 2024-08-13 13:19:49
  346. // @param openId string
  347. // @return isBind bool
  348. // @return err error
  349. func BindWxGzhByOpenId(openId string) (isBind bool, err error) {
  350. wxUserDetail, err := GetWxUserDetail(openId)
  351. if err != nil {
  352. logger.Error("获取微信公众号用户详细信息失败" + err.Error())
  353. err = exception.New(exception.WeChatResponseError)
  354. return
  355. }
  356. unionId := wxUserDetail.UnionID
  357. followingGzh := int(wxUserDetail.Subscribe)
  358. user, err := userService.GetTemplateUserByGzhOpenId(openId)
  359. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  360. err = exception.New(exception.TemplateUserFoundFailed)
  361. return
  362. }
  363. if errors.Is(err, gorm.ErrRecordNotFound) {
  364. user.GzhOpenId = openId
  365. user.UnionId = unionId
  366. user.FollowingGzh = followingGzh
  367. //判断unionid是否存在
  368. wxUser, unionErr := userService.GetTemplateUserByUnionId(unionId)
  369. if unionErr != nil && !errors.Is(unionErr, gorm.ErrRecordNotFound) {
  370. err = exception.New(exception.TemplateUserFoundFailed)
  371. return
  372. }
  373. if errors.Is(unionErr, gorm.ErrRecordNotFound) {
  374. err = userService.RegisterTemplateUser(&user)
  375. if err != nil {
  376. logger.Info("RegisterTemplateUser,Err" + err.Error())
  377. }
  378. } else { //修改微信小程序openid
  379. logger.Info("wxUser.Id:%d", wxUser.Id)
  380. err = userService.BindUserGzhOpenId(wxUser.Id, openId, followingGzh)
  381. if err != nil {
  382. logger.Info("BindUserGzhOpenId,Err" + err.Error())
  383. }
  384. }
  385. } else {
  386. isBind = true
  387. err = userService.BindUserGzhOpenId(user.Id, openId, followingGzh)
  388. if err != nil {
  389. logger.Info("BindUserGzhOpenId,Err" + err.Error())
  390. }
  391. }
  392. return
  393. }
  394. // UnSubscribeWxGzhByOpenId
  395. // @Description: 通过openid取消关注微信公众号
  396. // @author: Roc
  397. // @datetime 2024-08-13 13:19:49
  398. // @param openId string
  399. // @return isBind bool
  400. // @return err error
  401. func UnSubscribeWxGzhByOpenId(openId string) {
  402. logger.Info("openId:" + openId)
  403. var err error
  404. defer func() {
  405. if err != nil {
  406. logger.Info("通过openid取消关注微信公众号失败,openId:", openId, ";错误信息:", err.Error())
  407. }
  408. }()
  409. // 通过公众号openid获取用户信息
  410. user, err := userService.GetTemplateUserByGzhOpenId(openId)
  411. if err != nil {
  412. if err == gorm.ErrRecordNotFound {
  413. err = nil
  414. // 找不到就直接返回了
  415. return
  416. } else {
  417. err = exception.New(exception.TemplateUserNotFound)
  418. return
  419. }
  420. }
  421. // 解绑用户
  422. err = userService.BindUserGzhOpenId(user.Id, openId, 0)
  423. if err != nil {
  424. logger.Info("UnBindWxGzhByOpenId,Err" + err.Error())
  425. }
  426. return
  427. }