package account import ( "encoding/base64" "encoding/hex" "errors" "fmt" "mtp20access/client" "mtp20access/global" accountModel "mtp20access/model/account" "mtp20access/model/account/request" jwtRequest "mtp20access/model/common/request" "mtp20access/packet" "sync" "mtp20access/utils" "strconv" "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, 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 if token, expiresAt, err = buildRedisLoginInfo(*loginaccount, addr, req.ClientType); err != nil { return } return } // getLoginAccount 分别尝试用LoginID、LoginCode和手机号码进行登录 func getLoginAccount(userName string, password string) (loginaccount *accountModel.Loginaccount, 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 } pwd := string(p) // 通过LoginID查询 if loginID, _ := strconv.Atoi(userName); loginID != 0 { loginaccount = &accountModel.Loginaccount{ LOGINID: int64(loginID), PASSWORD: utils.EncoderSha256(fmt.Sprintf("%s%s", userName, pwd)), // 构建数据库存储的密码 } 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, pwd)) { return } } // 通过手机号码查询,需要AES加密 key, _ := hex.DecodeString(utils.AESSecretKey) if mobileEncrypted, _ := utils.AESEncrypt([]byte(userName), key); mobileEncrypted != nil { 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, err error) { // 生成SessionID sessionID := newSessionID() // 生成本服务Token j := &utils.JWT{SigningKey: []byte(global.M2A_CONFIG.JWT.SigningKey)} // 唯一签名 claims := j.CreateClaims(jwtRequest.BaseClaims{ LoginID: int(loginaccount.LOGINID), Group: group, SessionID: sessionID, }) 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) } delete(client.Clients, claims.SessionID) client.Clients[claims.SessionID] = &client.Client{LoginRedis: loginLogin} return } // RestoreLoginWithToken 通过Token检验恢复登录状态失败 func RestoreLoginWithToken(loginID int, group int, token string) (err error) { // 从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 } delete(client.Clients, s) client.Clients[s] = &client.Client{LoginRedis: loginLogin} return }