login.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package account
  2. import (
  3. "encoding/base64"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "mtp20access/client"
  8. "mtp20access/global"
  9. accountModel "mtp20access/model/account"
  10. "mtp20access/model/account/request"
  11. "mtp20access/packet"
  12. "mtp20access/rabbitmq"
  13. "mtp20access/res/pb"
  14. "sync"
  15. "mtp20access/utils"
  16. "strconv"
  17. commonRequest "mtp20access/model/common/request"
  18. "github.com/gin-gonic/gin"
  19. "github.com/gofrs/uuid"
  20. "github.com/golang/protobuf/proto"
  21. "go.uber.org/zap"
  22. )
  23. var (
  24. mtx sync.RWMutex
  25. curSessionID int = 90000 // 本服务SessionID从90000开始,以避免与旧登录服务重叠
  26. )
  27. // Login 用户登录
  28. func Login(req request.LoginReq, addr string) (loginaccount *accountModel.Loginaccount, token string, sessionID int, expiresAt int64, err error) {
  29. // 分别尝试用LoginID、LoginCode和手机号码进行登录
  30. loginaccount, _, err = getLoginAccount(req.UserName, req.Password)
  31. if err != nil {
  32. return
  33. }
  34. // 判断用户状态
  35. if loginaccount.LOGINSTATUS == 2 {
  36. err = errors.New("账户已冻结")
  37. return
  38. }
  39. if loginaccount.LOGINSTATUS == 3 {
  40. err = errors.New("账户已注销")
  41. return
  42. }
  43. // 生成Token,并写入Redis
  44. token, expiresAt, sessionID, err = buildRedisLoginInfo(*loginaccount, addr, req.ClientType)
  45. if err != nil {
  46. return
  47. }
  48. // 发送登录报文给总线
  49. loginReq := pb.LoginReq{}
  50. loginReq.LoginID = utils.SetPointValue(uint64(loginaccount.LOGINID))
  51. // loginReq.LoginPWD = utils.SetPointValue(strings.ToLower(utils.EncoderSha256(fmt.Sprintf("%v%s", loginReq.LoginID, oriPwd))))
  52. loginReq.LoginPWD = utils.SetPointValue(loginaccount.PASSWORD)
  53. loginReq.LoginIp = utils.SetPointValue(addr)
  54. loginReq.LoginType = utils.SetPointValue[uint32](0)
  55. loginReq.ClientType = utils.SetPointValue(uint32(req.ClientType))
  56. uid, _ := uuid.NewV4()
  57. loginReq.GUID = utils.SetPointValue(uid.String())
  58. loginReq.Version = utils.SetPointValue("1.0.0")
  59. uid, _ = uuid.NewV4()
  60. loginReq.DeviceID = utils.SetPointValue(uid.String())
  61. loginReq.ClientAppID = utils.SetPointValue("MTP20_GO_ACCESS")
  62. header := pb.MessageHead{}
  63. header.FunCode = utils.SetPointValue(uint32(global.LoginReq))
  64. uid, _ = uuid.NewV4()
  65. header.UUID = utils.SetPointValue(uid.String())
  66. loginReq.Header = &header
  67. if b, e := proto.Marshal(&loginReq); e == nil {
  68. packet := &client.MQPacket{
  69. FunCode: uint32(global.LoginReq),
  70. SessionId: uint32(sessionID),
  71. Data: &b,
  72. }
  73. go rabbitmq.Publish(global.TOPIC_REQ_USER, packet)
  74. }
  75. return
  76. }
  77. // getLoginAccount 分别尝试用LoginID、LoginCode和手机号码进行登录
  78. func getLoginAccount(userName string, password string) (loginaccount *accountModel.Loginaccount, oriPwd string, err error) {
  79. // 密码解密(5.0报文解密)
  80. d, err := base64.StdEncoding.DecodeString(password)
  81. if err != nil {
  82. return
  83. }
  84. d1 := d[4 : len(d)-8] // 解密时要去头尾
  85. p, err := packet.Decrypt(d1, packet.AESKey, true)
  86. if err != nil {
  87. return
  88. }
  89. oriPwd = string(p)
  90. // 通过LoginID查询
  91. if loginID, _ := strconv.Atoi(userName); loginID != 0 {
  92. loginaccount = &accountModel.Loginaccount{
  93. LOGINID: int64(loginID),
  94. PASSWORD: utils.EncoderSha256(fmt.Sprintf("%s%s", userName, oriPwd)), // 构建数据库存储的密码
  95. }
  96. if has, _ := global.M2A_DB.Get(loginaccount); has {
  97. return
  98. }
  99. }
  100. // 通过LoginCode查询
  101. loginaccount = &accountModel.Loginaccount{
  102. LOGINCODE: userName,
  103. }
  104. if has, _ := global.M2A_DB.Get(loginaccount); has {
  105. // 构建数据库存储的密码
  106. if loginaccount.PASSWORD == utils.EncoderSha256(fmt.Sprintf("%d%s", loginaccount.LOGINID, oriPwd)) {
  107. return
  108. }
  109. }
  110. // 通过手机号码查询,需要AES加密
  111. key, _ := hex.DecodeString(utils.AESSecretKey)
  112. if mobileEncrypted, _ := utils.AESEncrypt([]byte(userName), key); mobileEncrypted != nil {
  113. // 从三方表获取LoginID
  114. userauthinfo := &accountModel.Userauthinfo{
  115. AUTHID: hex.EncodeToString(mobileEncrypted),
  116. AUTHTYPE: 3,
  117. }
  118. if has, _ := global.M2A_DB.Get(userauthinfo); has {
  119. loginaccount = &accountModel.Loginaccount{
  120. LOGINID: userauthinfo.LOGINID,
  121. PASSWORD: utils.EncoderSha256(fmt.Sprintf("%v%s", userauthinfo.LOGINID, oriPwd)), // 构建数据库存储的密码
  122. }
  123. if has, _ := global.M2A_DB.Get(loginaccount); has {
  124. return
  125. }
  126. }
  127. // loginaccount = &accountModel.Loginaccount{
  128. // MOBILE: string(mobileEncrypted),
  129. // }
  130. // if has, _ := global.M2A_DB.Get(loginaccount); has {
  131. // // 构建数据库存储的密码
  132. // if loginaccount.PASSWORD == utils.EncoderSha256(fmt.Sprintf("%d%s", loginaccount.LOGINID, pwd)) {
  133. // return
  134. // }
  135. // }
  136. }
  137. err = errors.New("错误的用户名或密码")
  138. return
  139. }
  140. // newSessionID 获取
  141. func newSessionID() int {
  142. mtx.RLock()
  143. defer mtx.RUnlock()
  144. curSessionID += 1
  145. return curSessionID
  146. }
  147. // buildRedisLoginInfo 生成Token,并写入Redis
  148. func buildRedisLoginInfo(loginaccount accountModel.Loginaccount, addr string, group int) (token string, expiresAt int64, sessionID int, err error) {
  149. // 生成SessionID
  150. sessionID = newSessionID()
  151. // 生成本服务Token
  152. j := &utils.JWT{SigningKey: []byte(global.M2A_CONFIG.JWT.SigningKey)} // 唯一签名
  153. claims := j.CreateClaims(commonRequest.BaseClaims{
  154. LoginID: int(loginaccount.LOGINID),
  155. Group: group,
  156. SessionID: sessionID,
  157. UserID: int(loginaccount.USERID),
  158. })
  159. token, err = j.CreateToken(claims)
  160. if err != nil {
  161. global.M2A_LOG.Error("生成本服token失败", zap.Error(err))
  162. return
  163. }
  164. expiresAt = claims.RegisteredClaims.ExpiresAt.Unix()
  165. loginLogin := client.LoginRedis{
  166. LoginID: strconv.Itoa(int(loginaccount.LOGINID)),
  167. UserID: strconv.Itoa(int(loginaccount.USERID)),
  168. SessionID: strconv.Itoa(sessionID),
  169. Token: token,
  170. Group: strconv.Itoa(group),
  171. Addr: addr,
  172. }
  173. loginMap, err := loginLogin.ToMap()
  174. // loginMap := map[string]interface{}{
  175. // "LoginID": strconv.Itoa(int(loginaccount.LOGINID)),
  176. // "UserID": strconv.Itoa(int(loginaccount.USERID)),
  177. // "SessionID": strconv.Itoa(sessionID),
  178. // "Token": token,
  179. // "Group": strconv.Itoa(group),
  180. // "Addr": addr,
  181. // }
  182. if err != nil {
  183. global.M2A_LOG.Error("生成登录信息MAP失败", zap.Error(err))
  184. return
  185. }
  186. if err = j.SetRedisLogin(int(loginaccount.LOGINID), group, loginMap); err != nil {
  187. global.M2A_LOG.Error("Token写入Redis失败", zap.Error(err))
  188. return
  189. }
  190. // 生成旧登录服务Token
  191. // if err = j.SetOriRedisToken(int(loginaccount.LOGINID), group); err != nil {
  192. // // FIXME: 这里有类事务的回滚问题
  193. // global.M2A_LOG.Error("生成旧登录服务Token失败", zap.Error(err))
  194. // return
  195. // }
  196. // 记录用户信息
  197. mtx.Lock()
  198. defer mtx.Unlock()
  199. if client.Clients == nil {
  200. client.Clients = make(map[int]*client.Client, 0)
  201. }
  202. // 这里应该按loginid和group把之前的client删除掉
  203. // delete(client.Clients, s)
  204. targetKeys := make([]int, 0)
  205. for key, item := range client.Clients {
  206. if item.LoginID == loginLogin.LoginID && item.Group == loginLogin.Group {
  207. targetKeys = append(targetKeys, key)
  208. }
  209. }
  210. for _, k := range targetKeys {
  211. delete(client.Clients, k)
  212. }
  213. client.Clients[claims.SessionID] = &client.Client{LoginRedis: loginLogin}
  214. return
  215. }
  216. // RestoreLoginWithToken 通过Token检验恢复登录状态失败
  217. func RestoreLoginWithToken(loginID int, group int, token string) (err error) {
  218. // 先判断Clients中是否有对应的对像
  219. if client.Clients != nil {
  220. targetKeys := make([]int, 0)
  221. for key, item := range client.Clients {
  222. if item.LoginID == strconv.Itoa(loginID) && item.Group == strconv.Itoa(group) {
  223. targetKeys = append(targetKeys, key)
  224. }
  225. }
  226. if len(targetKeys) > 0 {
  227. return
  228. }
  229. }
  230. // 没有的话重新创建一个
  231. // 从Redis获取登录信息
  232. j := utils.NewJWT()
  233. loginMap, err := j.GetRedisLogin(loginID, group)
  234. if err != nil {
  235. global.M2A_LOG.Error("Token检验恢复登录状态失败", zap.Error(err))
  236. return
  237. }
  238. loginId := loginMap["loginId"]
  239. userId := loginMap["userId"]
  240. sessionId := loginMap["sessionId"]
  241. addr := loginMap["addr"]
  242. loginLogin := client.LoginRedis{
  243. LoginID: loginId,
  244. UserID: userId,
  245. SessionID: sessionId,
  246. Token: token,
  247. Group: strconv.Itoa(group),
  248. Addr: addr,
  249. }
  250. // 记录用户信息
  251. mtx.Lock()
  252. defer mtx.Unlock()
  253. if client.Clients == nil {
  254. client.Clients = make(map[int]*client.Client, 0)
  255. }
  256. s, err := strconv.Atoi(sessionId)
  257. if err != nil {
  258. global.M2A_LOG.Error("Token检验恢复登录状态失败", zap.Error(err))
  259. return
  260. }
  261. client.Clients[s] = &client.Client{LoginRedis: loginLogin}
  262. return
  263. }
  264. // GetClientsByAccountID 通过资金账户获取所有的
  265. func GetClientsByAccountID(accountID uint64) (clients []*client.Client, err error) {
  266. clients = make([]*client.Client, 0)
  267. loginIds := make([]string, 0)
  268. sql := fmt.Sprintf(`
  269. SELECT
  270. to_char(t.loginid)
  271. FROM loginaccount t
  272. INNER JOIN taaccount a ON a.userid = t.userid
  273. WHERE a.accountid = %v
  274. `, accountID)
  275. if err = global.M2A_DB.SQL(sql).Find(&loginIds); err != nil {
  276. global.M2A_LOG.Info("获取LoginID失败", zap.Error(err))
  277. return
  278. } else {
  279. global.M2A_LOG.Info("获取LoginID", zap.Any("loginIds", loginIds))
  280. }
  281. var mtx sync.RWMutex
  282. mtx.Lock()
  283. defer mtx.Unlock()
  284. if len(loginIds) > 0 && len(client.Clients) > 0 {
  285. for _, item := range loginIds {
  286. for _, client := range client.Clients {
  287. // c := client.Clients[i]
  288. if client.LoginID == item {
  289. clients = append(clients, client)
  290. }
  291. }
  292. }
  293. }
  294. return
  295. }
  296. func Logout(c *gin.Context) (err error) {
  297. // 获取请求账号信息
  298. s, exists := c.Get("claims")
  299. if !exists {
  300. err = errors.New("获取请求账号信息异常")
  301. global.M2A_LOG.Error(err.Error(), zap.Error(err))
  302. return
  303. }
  304. claims := s.(*commonRequest.CustomClaims)
  305. // 获取登录账户信息
  306. t, exists := client.Clients[claims.SessionID]
  307. if !exists {
  308. err = errors.New("获取登录账户信息异常")
  309. global.M2A_LOG.Error(err.Error(), zap.Any("SessionID", claims.SessionID), zap.Error(err))
  310. return
  311. }
  312. // 发送登出报文给总线
  313. logoutReq := pb.LogoutReq{}
  314. logoutReq.LoginID = utils.SetPointValue(uint64(claims.LoginID))
  315. logoutReq.Token = utils.SetPointValue(t.OldToken)
  316. logoutReq.LoginIp = utils.SetPointValue(c.ClientIP())
  317. port, _ := strconv.Atoi(c.Request.URL.Port())
  318. logoutReq.LoginPort = utils.SetPointValue(uint32(port))
  319. header := pb.MessageHead{}
  320. header.FunCode = utils.SetPointValue(uint32(global.LogoutReq))
  321. userID, _ := strconv.Atoi(t.UserID)
  322. header.UserID = utils.SetPointValue(uint32(userID))
  323. uid, _ := uuid.NewV4()
  324. header.UUID = utils.SetPointValue(uid.String())
  325. logoutReq.Header = &header
  326. if b, e := proto.Marshal(&logoutReq); e == nil {
  327. packet := &client.MQPacket{
  328. FunCode: uint32(global.LogoutReq),
  329. SessionId: uint32(claims.SessionID),
  330. Data: &b,
  331. }
  332. go rabbitmq.Publish(global.TOPIC_REQ_USER, packet)
  333. }
  334. return
  335. }