package account import ( "encoding/base64" "encoding/hex" "errors" "fmt" "mtp20access/client" "mtp20access/global" accountModel "mtp20access/model/account" "mtp20access/model/account/request" "mtp20access/packet" "mtp20access/rabbitmq" "mtp20access/res/pb" "sync" "mtp20access/utils" "strconv" commonRequest "mtp20access/model/common/request" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/golang/protobuf/proto" "go.uber.org/zap" ) var ( mtx sync.RWMutex curSessionID int = 90000 // 本服务SessionID从90000开始,以避免与旧登录服务重叠 ) // Login 用户登录 func Login(req request.LoginReq, addr string) (loginaccount *accountModel.Loginaccount, token string, sessionID int, expiresAt int64, err error) { // 分别尝试用LoginID、LoginCode和手机号码进行登录 loginaccount, _, err = getLoginAccount(req.UserName, req.Password) if err != nil { return } // 判断用户状态 if loginaccount.LOGINSTATUS == 2 { err = errors.New("账户已冻结") return } if loginaccount.LOGINSTATUS == 3 { err = errors.New("账户已注销") return } // 生成Token,并写入Redis token, expiresAt, sessionID, err = buildRedisLoginInfo(*loginaccount, addr, req.ClientType) if err != nil { return } // 发送登录报文给总线 loginReq := pb.LoginReq{} loginReq.LoginID = utils.SetPointValue(uint64(loginaccount.LOGINID)) // loginReq.LoginPWD = utils.SetPointValue(strings.ToLower(utils.EncoderSha256(fmt.Sprintf("%v%s", loginReq.LoginID, oriPwd)))) loginReq.LoginPWD = utils.SetPointValue(loginaccount.PASSWORD) loginReq.LoginIp = utils.SetPointValue(addr) loginReq.LoginType = utils.SetPointValue[uint32](0) loginReq.ClientType = utils.SetPointValue(uint32(req.ClientType)) uid, _ := uuid.NewV4() loginReq.GUID = utils.SetPointValue(uid.String()) loginReq.Version = utils.SetPointValue("1.0.0") uid, _ = uuid.NewV4() loginReq.DeviceID = utils.SetPointValue(uid.String()) loginReq.ClientAppID = utils.SetPointValue("MTP20_GO_ACCESS") header := pb.MessageHead{} header.FunCode = utils.SetPointValue(uint32(global.LoginReq)) uid, _ = uuid.NewV4() header.UUID = utils.SetPointValue(uid.String()) loginReq.Header = &header if b, e := proto.Marshal(&loginReq); e == nil { packet := &client.MQPacket{ FunCode: uint32(global.LoginReq), SessionId: uint32(sessionID), Data: &b, } go rabbitmq.Publish(global.TOPIC_REQ_USER, packet) } return } // getLoginAccount 分别尝试用LoginID、LoginCode和手机号码进行登录 func getLoginAccount(userName string, password string) (loginaccount *accountModel.Loginaccount, oriPwd string, err error) { // 密码解密(5.0报文解密) d, err := base64.StdEncoding.DecodeString(password) if err != nil { return } d1 := d[4 : len(d)-8] // 解密时要去头尾 p, err := packet.Decrypt(d1, packet.AESKey, true) if err != nil { return } oriPwd = string(p) // 通过LoginID查询 if loginID, _ := strconv.Atoi(userName); loginID != 0 { loginaccount = &accountModel.Loginaccount{ LOGINID: int64(loginID), PASSWORD: utils.EncoderSha256(fmt.Sprintf("%s%s", userName, oriPwd)), // 构建数据库存储的密码 } if has, _ := global.M2A_DB.Get(loginaccount); has { return } } // 通过LoginCode查询 loginaccount = &accountModel.Loginaccount{ LOGINCODE: userName, } if has, _ := global.M2A_DB.Get(loginaccount); has { // 构建数据库存储的密码 if loginaccount.PASSWORD == utils.EncoderSha256(fmt.Sprintf("%d%s", loginaccount.LOGINID, oriPwd)) { return } } // 通过手机号码查询,需要AES加密 key, _ := hex.DecodeString(utils.AESSecretKey) if mobileEncrypted, _ := utils.AESEncrypt([]byte(userName), key); mobileEncrypted != nil { // 从三方表获取LoginID userauthinfo := &accountModel.Userauthinfo{ AUTHID: hex.EncodeToString(mobileEncrypted), AUTHTYPE: 3, } if has, _ := global.M2A_DB.Get(userauthinfo); has { loginaccount = &accountModel.Loginaccount{ LOGINID: userauthinfo.LOGINID, PASSWORD: utils.EncoderSha256(fmt.Sprintf("%v%s", userauthinfo.LOGINID, oriPwd)), // 构建数据库存储的密码 } if has, _ := global.M2A_DB.Get(loginaccount); has { return } } // loginaccount = &accountModel.Loginaccount{ // MOBILE: string(mobileEncrypted), // } // if has, _ := global.M2A_DB.Get(loginaccount); has { // // 构建数据库存储的密码 // if loginaccount.PASSWORD == utils.EncoderSha256(fmt.Sprintf("%d%s", loginaccount.LOGINID, pwd)) { // return // } // } } err = errors.New("错误的用户名或密码") return } // newSessionID 获取 func newSessionID() int { mtx.RLock() defer mtx.RUnlock() curSessionID += 1 return curSessionID } // buildRedisLoginInfo 生成Token,并写入Redis func buildRedisLoginInfo(loginaccount accountModel.Loginaccount, addr string, group int) (token string, expiresAt int64, sessionID int, err error) { // 生成SessionID sessionID = newSessionID() // 生成本服务Token j := &utils.JWT{SigningKey: []byte(global.M2A_CONFIG.JWT.SigningKey)} // 唯一签名 claims := j.CreateClaims(commonRequest.BaseClaims{ LoginID: int(loginaccount.LOGINID), Group: group, SessionID: sessionID, UserID: int(loginaccount.USERID), }) token, err = j.CreateToken(claims) if err != nil { global.M2A_LOG.Error("生成本服token失败", zap.Error(err)) return } expiresAt = claims.RegisteredClaims.ExpiresAt.Unix() loginLogin := client.LoginRedis{ LoginID: strconv.Itoa(int(loginaccount.LOGINID)), UserID: strconv.Itoa(int(loginaccount.USERID)), SessionID: strconv.Itoa(sessionID), Token: token, Group: strconv.Itoa(group), Addr: addr, } loginMap, err := loginLogin.ToMap() // loginMap := map[string]interface{}{ // "LoginID": strconv.Itoa(int(loginaccount.LOGINID)), // "UserID": strconv.Itoa(int(loginaccount.USERID)), // "SessionID": strconv.Itoa(sessionID), // "Token": token, // "Group": strconv.Itoa(group), // "Addr": addr, // } if err != nil { global.M2A_LOG.Error("生成登录信息MAP失败", zap.Error(err)) return } if err = j.SetRedisLogin(int(loginaccount.LOGINID), group, loginMap); err != nil { global.M2A_LOG.Error("Token写入Redis失败", zap.Error(err)) return } // 生成旧登录服务Token // if err = j.SetOriRedisToken(int(loginaccount.LOGINID), group); err != nil { // // FIXME: 这里有类事务的回滚问题 // global.M2A_LOG.Error("生成旧登录服务Token失败", zap.Error(err)) // return // } // 记录用户信息 mtx.Lock() defer mtx.Unlock() if client.Clients == nil { client.Clients = make(map[int]*client.Client, 0) } // 这里应该按loginid和group把之前的client删除掉 // delete(client.Clients, s) targetKeys := make([]int, 0) for key, item := range client.Clients { if item.LoginID == loginLogin.LoginID && item.Group == loginLogin.Group { targetKeys = append(targetKeys, key) } } for _, k := range targetKeys { delete(client.Clients, k) } client.Clients[claims.SessionID] = &client.Client{LoginRedis: loginLogin} return } // RestoreLoginWithToken 通过Token检验恢复登录状态失败 func RestoreLoginWithToken(loginID int, group int, token string) (err error) { // 先判断Clients中是否有对应的对像 if client.Clients != nil { targetKeys := make([]int, 0) for key, item := range client.Clients { if item.LoginID == strconv.Itoa(loginID) && item.Group == strconv.Itoa(group) { targetKeys = append(targetKeys, key) } } if len(targetKeys) > 0 { return } } // 没有的话重新创建一个 // 从Redis获取登录信息 j := utils.NewJWT() loginMap, err := j.GetRedisLogin(loginID, group) if err != nil { global.M2A_LOG.Error("Token检验恢复登录状态失败", zap.Error(err)) return } loginId := loginMap["loginId"] userId := loginMap["userId"] sessionId := loginMap["sessionId"] addr := loginMap["addr"] loginLogin := client.LoginRedis{ LoginID: loginId, UserID: userId, SessionID: sessionId, Token: token, Group: strconv.Itoa(group), Addr: addr, } // 记录用户信息 mtx.Lock() defer mtx.Unlock() if client.Clients == nil { client.Clients = make(map[int]*client.Client, 0) } s, err := strconv.Atoi(sessionId) if err != nil { global.M2A_LOG.Error("Token检验恢复登录状态失败", zap.Error(err)) return } client.Clients[s] = &client.Client{LoginRedis: loginLogin} return } // GetClientsByAccountID 通过资金账户获取所有的 func GetClientsByAccountID(accountID uint64) (clients []*client.Client, err error) { clients = make([]*client.Client, 0) loginIds := make([]string, 0) sql := fmt.Sprintf(` SELECT to_char(t.loginid) FROM loginaccount t INNER JOIN taaccount a ON a.userid = t.userid WHERE a.accountid = %v `, accountID) if err = global.M2A_DB.SQL(sql).Find(&loginIds); err != nil { global.M2A_LOG.Info("获取LoginID失败", zap.Error(err)) return } else { global.M2A_LOG.Info("获取LoginID", zap.Any("loginIds", loginIds)) } var mtx sync.RWMutex mtx.Lock() defer mtx.Unlock() if len(loginIds) > 0 && len(client.Clients) > 0 { for _, item := range loginIds { for _, client := range client.Clients { // c := client.Clients[i] if client.LoginID == item { clients = append(clients, client) } } } } return } func Logout(c *gin.Context) (err error) { // 获取请求账号信息 s, exists := c.Get("claims") if !exists { err = errors.New("获取请求账号信息异常") global.M2A_LOG.Error(err.Error(), zap.Error(err)) return } claims := s.(*commonRequest.CustomClaims) // 获取登录账户信息 t, exists := client.Clients[claims.SessionID] if !exists { err = errors.New("获取登录账户信息异常") global.M2A_LOG.Error(err.Error(), zap.Any("SessionID", claims.SessionID), zap.Error(err)) return } // 发送登出报文给总线 logoutReq := pb.LogoutReq{} logoutReq.LoginID = utils.SetPointValue(uint64(claims.LoginID)) logoutReq.Token = utils.SetPointValue(t.OldToken) logoutReq.LoginIp = utils.SetPointValue(c.ClientIP()) port, _ := strconv.Atoi(c.Request.URL.Port()) logoutReq.LoginPort = utils.SetPointValue(uint32(port)) header := pb.MessageHead{} header.FunCode = utils.SetPointValue(uint32(global.LogoutReq)) userID, _ := strconv.Atoi(t.UserID) header.UserID = utils.SetPointValue(uint32(userID)) uid, _ := uuid.NewV4() header.UUID = utils.SetPointValue(uid.String()) logoutReq.Header = &header if b, e := proto.Marshal(&logoutReq); e == nil { packet := &client.MQPacket{ FunCode: uint32(global.LogoutReq), SessionId: uint32(claims.SessionID), Data: &b, } go rabbitmq.Publish(global.TOPIC_REQ_USER, packet) } return }