auth_service.go 11 KB

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