kobe6258 6 місяців тому
батько
коміт
8359d30fd1

+ 9 - 3
common/exception/exc_enums.go

@@ -41,10 +41,12 @@ const (
 const (
 	UserErrCode int = iota + 30000 // iota 自动递增,从 1 开始
 	TemplateUserNotFound
+	TemplateUserFoundFailed
 	TemplateUserCreateFailed
+	TemplateUserBindFailed
 	GenerateTokenFailed
 	JWTTokenDecodeFailed
-
+	LogoutFailed
 	JWTTokenExpired
 	JWTTokenInvalid
 	NotCurrentUserError
@@ -97,8 +99,9 @@ const (
 
 // ErrorMap 用于存储错误码和错误信息的映射
 var ErrorMap = map[int]string{
-	UnknownError:          "未知错误",
-	Unauthorized:          "用户未授权",
+	UnknownError: "未知错误",
+	Unauthorized: "用户未授权",
+
 	IllegalCodeLength:     "无效的验证码位数设置",
 	IllegalPhoneNumber:    "无效的手机号码",
 	SMSCodeGenerateFailed: "生成手机验证码失败",
@@ -113,7 +116,10 @@ var ErrorMap = map[int]string{
 	GetAnalystListFailed:  "获取研究员列表失败",
 	//用户
 	TemplateUserNotFound:               "临时用户记录不存在",
+	LogoutFailed:                       "退出登录失败",
+	TemplateUserFoundFailed:            "查询临时用户表失败",
 	TemplateUserCreateFailed:           "创建临时用户失败",
+	TemplateUserBindFailed:             "临时用户绑定小程序失败",
 	GenerateTokenFailed:                "创建token失败",
 	JWTTokenDecodeFailed:               "token解析失败",
 	JWTTokenExpired:                    "token已过期",

+ 99 - 0
common/utils/auth/encrypt_utils.go

@@ -0,0 +1,99 @@
+package auth
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"io"
+)
+
+// GenerateAESKey 生成一个随机的AES密钥
+func GenerateAESKey(keySize int) ([]byte, error) {
+	if keySize != 16 && keySize != 24 && keySize != 32 {
+		return nil, fmt.Errorf("unsupported key size: %d", keySize)
+	}
+
+	key := make([]byte, keySize)
+	_, err := io.ReadFull(rand.Reader, key)
+	if err != nil {
+		return nil, err
+	}
+	return key, nil
+}
+
+// AESEncrypt 使用AES CBC模式加密数据
+func AESEncrypt(key []byte, plaintext []byte) (string, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return "", err
+	}
+
+	// 生成一个随机的初始化向量
+	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
+	iv := ciphertext[:aes.BlockSize]
+	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+		return "", err
+	}
+
+	mode := cipher.NewCBCEncrypter(block, iv)
+	mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
+
+	// 返回Base64编码后的字符串
+	return base64.StdEncoding.EncodeToString(ciphertext), nil
+}
+
+// AESDecrypt 使用AES CBC模式解密数据
+func AESDecrypt(key []byte, ciphertext string) (string, error) {
+	ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
+	if err != nil {
+		return "", err
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return "", err
+	}
+
+	if len(ciphertextBytes) < aes.BlockSize {
+		return "", fmt.Errorf("ciphertext too short")
+	}
+
+	iv := ciphertextBytes[:aes.BlockSize]
+	ciphertextBytes = ciphertextBytes[aes.BlockSize:]
+
+	mode := cipher.NewCBCDecrypter(block, iv)
+	mode.CryptBlocks(ciphertextBytes, ciphertextBytes)
+
+	// 去除PKCS#7填充
+	unPadded := unPad(ciphertextBytes)
+	return string(unPadded), nil
+}
+
+// unPad 去除PKCS#7填充
+func unPad(src []byte) []byte {
+	padding := src[len(src)-1]
+	return src[:len(src)-int(padding)]
+}
+
+func main() {
+	key := []byte("this is a key123") // 16字节长的密钥
+	plaintext := []byte("Hello, World!")
+
+	// 加密
+	encrypted, err := AESEncrypt(key, plaintext)
+	if err != nil {
+		fmt.Println("Error encrypting:", err)
+		return
+	}
+	fmt.Println("Encrypted:", encrypted)
+
+	// 解密
+	decrypted, err := AESDecrypt(key, encrypted)
+	if err != nil {
+		fmt.Println("Error decrypting:", err)
+		return
+	}
+	fmt.Println("Decrypted:", decrypted)
+}

+ 1 - 0
common/utils/auth/signature_utils.go

@@ -0,0 +1 @@
+package auth

+ 16 - 8
common/utils/jwt/jwt_utils.go

@@ -8,21 +8,27 @@ import (
 )
 
 type Claims struct {
-	OpenId string `json:"open_id"`
-	Mobile string `json:"mobile"`
+	OpenId    string `json:"open_id"`
+	Mobile    string `json:"mobile"`
+	TokenType string `json:"tokenType"`
 	jwt.StandardClaims
 }
 
+const (
+	AccessToken = "access_token"
+	GuestToken  = "guest_token"
+)
+
 var (
 	KEY = []byte("5Mb5Gdmb5y")
 )
 
 // CreateToken 生成token
-func CreateToken(openId string, mobile string) (tokenString string, err error) {
-
+func CreateToken(openId string, mobile string, tokenType string) (tokenString string, err error) {
 	claims := &Claims{
 		openId,
 		mobile,
+		tokenType,
 		jwt.StandardClaims{
 			NotBefore: time.Now().Unix(),
 			ExpiresAt: time.Now().Add(time.Hour * 24 * 90).Unix(),
@@ -37,8 +43,9 @@ func CreateToken(openId string, mobile string) (tokenString string, err error) {
 }
 
 type JwtInfo struct {
-	OpenId string
-	Mobile string
+	OpenId    string
+	Mobile    string
+	TokenType string
 }
 
 // CheckToken 校验token
@@ -66,8 +73,9 @@ func CheckToken(tokenStr string) (info JwtInfo, err error) {
 			return
 		}
 		info = JwtInfo{
-			OpenId: claims["open_id"].(string),
-			Mobile: claims["mobile"].(string),
+			OpenId:    claims["open_id"].(string),
+			Mobile:    claims["mobile"].(string),
+			TokenType: claims["tokenType"].(string),
 		}
 		return
 	}

+ 2 - 2
common/utils/redis/key_generator.go

@@ -15,8 +15,8 @@ func GenerateSmsKey(mobile string) string {
 	return fmt.Sprint(SmsKeyPrefix, mobile)
 }
 
-func GenerateTokenKey(mobile string) string {
-	return fmt.Sprint(LoginTokenPrefix, mobile)
+func GenerateTokenKey(openId string) string {
+	return fmt.Sprint(LoginTokenPrefix, openId)
 }
 
 func GenerateReportRefreshKey(source string, id int, modifyTime int64) string {

+ 0 - 2
controllers/user/analyst_controller.go

@@ -8,7 +8,6 @@ import (
 	"eta/eta_mini_ht_api/service/media"
 	"eta/eta_mini_ht_api/service/report"
 	"eta/eta_mini_ht_api/service/user"
-	"fmt"
 )
 
 type AnalystController struct {
@@ -60,7 +59,6 @@ func (an *AnalystController) AnalystList() {
 func (an *AnalystController) AnalystDetail(analystId int) {
 	controllers.Wrap(&an.BaseController, func() (result *controllers.WrapData, err error) {
 		result = an.InitWrapData("获取研究员详情失败")
-		fmt.Println(analystId)
 		userInfo := an.Data["user"].(user.User)
 		detail, err := user.GetAnalystDetail(userInfo.Id, analystId)
 		if err != nil {

+ 54 - 10
controllers/user/auth_controller.go

@@ -10,7 +10,6 @@ import (
 	"eta/eta_mini_ht_api/controllers"
 	"eta/eta_mini_ht_api/service/auth"
 	"eta/eta_mini_ht_api/service/user"
-	"fmt"
 	"os"
 	"strings"
 )
@@ -28,6 +27,25 @@ type LoginReq struct {
 	AreaCode   string `json:"areaCode"`
 }
 
+// Logout 小程序退出登录接口
+// @Summary 小程序退出登录接口
+// @Description 用户小程序退出登录
+// @Param   mobile     body    LoginReq  true        "登录请求体"
+// @Success 200 {object} controllers.BaseResponse
+// @router /Logout [post]
+func (a *AuthController) Logout() {
+	controllers.Wrap(&a.BaseController, func() (result *controllers.WrapData, err error) {
+		result = a.InitWrapData("退出登录失败")
+		userInfo := a.Data["user"].(user.User)
+		err = auth.Logout(userInfo.Id, userInfo.OpenId)
+		if err != nil {
+			a.FailedResult("退出登录失败", result)
+			return
+		}
+		return
+	})
+}
+
 // Login 小程序登录接口
 // @Summary 小程序用户登录
 // @Description 用户通过微信小程序登录
@@ -39,6 +57,23 @@ func (a *AuthController) Login() {
 		result = a.InitWrapData("登录失败")
 		loginReq := new(LoginReq)
 		a.GetPostParams(loginReq)
+		userInfo := a.Data["user"].(user.User)
+		if userInfo.Mobile != "" && userInfo.Mobile != loginReq.Mobile {
+			a.FailedResult("登录失败,已绑定手机号:"+userInfo.Mobile, result)
+			err = exception.New(exception.IllegalAreaCode)
+			return
+		}
+		mobileUser, err := user.GetUserByMobile(loginReq.Mobile)
+		if err != nil {
+			a.FailedResult("登录失败:"+userInfo.Mobile, result)
+			err = exception.New(exception.BindMobileFailed)
+			return
+		}
+		if mobileUser.Id > 0 && (mobileUser.Id != userInfo.Id && mobileUser.Mobile == loginReq.Mobile) {
+			a.FailedResult("登录失败,该手机号已绑定过微信", result)
+			err = exception.New(exception.IllegalAreaCode)
+			return
+		}
 		if loginReq.AreaCode == "" || !checkValidAreaCode(loginReq.AreaCode) {
 			a.FailedResult("非法的手机区号", result)
 			err = exception.New(exception.IllegalAreaCode)
@@ -55,18 +90,23 @@ func (a *AuthController) Login() {
 			logger.Warn("验证码校验失败:%v", err)
 			return
 		}
-
-		userInfo := a.Ctx.Input.GetData("user").(user.User)
-		//注册用户或者登录
-		var token string
-		err = auth.BindMobile(userInfo.Id, loginReq.Mobile, loginReq.AreaCode)
-		fmt.Println(token)
+		//用户手机为空需要绑定手机
+		if userInfo.Mobile == "" {
+			//注册用户或者登录
+			err = auth.BindMobile(userInfo.Id, loginReq.Mobile)
+			if err != nil {
+				a.FailedResult("登录异常,绑定手机失败", result)
+				return
+			}
+		}
+		//生成一个正式的登录token,生成登录流水
+		token, err := auth.Login(userInfo.Id, userInfo.OpenId, loginReq.Mobile, loginReq.VerifyCode)
 		if err != nil {
-			a.FailedResult("登录失败", result)
+			a.FailedResult("登录异常,生成accessToken失败", result)
 			return
 		}
 		a.SuccessResult("登录成功", &LoginResp{
-			//Token: token,
+			Token: token,
 		}, result)
 		return
 	})
@@ -118,7 +158,9 @@ func (a *AuthController) SMSCode() {
 
 type RefreshTokenRes struct {
 	Token        string `json:"token"`
+	Status       string `json:"status"`
 	IsBindMobile int    `json:"isBindMobile"`
+	TokenType    string `json:"tokenType"`
 }
 
 // RefreshToken 更新token
@@ -134,7 +176,7 @@ func (a *AuthController) RefreshToken(code string) {
 			return result, exception.New(exception.WeChatCodeEmpty)
 		}
 		//刷新token
-		token, isBindMobile, err := auth.RefreshToken(code)
+		token, isBindMobile, status, tokenType, err := auth.RefreshToken(code)
 		if err != nil {
 			logger.Error("刷新token失败:%v", err)
 			a.FailedResult("刷新token失败", result)
@@ -143,6 +185,8 @@ func (a *AuthController) RefreshToken(code string) {
 		a.SuccessResult("刷新token成功", RefreshTokenRes{
 			Token:        token,
 			IsBindMobile: isBindMobile,
+			TokenType:    tokenType,
+			Status:       status,
 		}, result)
 		return
 	})

+ 1 - 1
controllers/user/user_controller.go

@@ -443,7 +443,7 @@ func covertToUserProfile(user user.User) UserProfileReq {
 // @router /bind_gzh [post]
 func (u *UserController) BindGzh() {
 	controllers.Wrap(&u.BaseController, func() (result *controllers.WrapData, err error) {
-		result = u.InitWrapData("获取我的未读消息失败")
+		result = u.InitWrapData("绑定公众号失败")
 		bindReq := new(BindGzhReq)
 		u.GetPostParams(bindReq)
 		code := bindReq.Code

+ 7 - 0
controllers/web_hook/htfutures_account_controller.go

@@ -0,0 +1,7 @@
+package web_hook
+
+import "eta/eta_mini_ht_api/controllers"
+
+type HTFuturesAccountController struct {
+	controllers.WebHookController
+}

+ 11 - 0
controllers/webhook_controller.go

@@ -0,0 +1,11 @@
+package controllers
+
+import "github.com/beego/beego/v2/server/web"
+
+type WebHookController struct {
+	web.Controller
+}
+
+func (c *WebHookController) Prepare() {
+
+}

+ 16 - 0
domian/user/user_event_flow_serivce.go

@@ -0,0 +1,16 @@
+package user
+
+import "eta/eta_mini_ht_api/models/user"
+
+func UserLoginFlow(userId int) (err error) {
+	return user.InsertEventFlow(user.UserEventFlow{
+		UserID:    userId,
+		EventType: user.Login,
+	})
+}
+func UserLogoutFlow(userId int) (err error) {
+	return user.InsertEventFlow(user.UserEventFlow{
+		UserID:    userId,
+		EventType: user.Logout,
+	})
+}

+ 25 - 5
domian/user/user_serivce.go

@@ -7,12 +7,21 @@ import (
 	"gorm.io/gorm"
 )
 
+type userStatus string
+
+const (
+	UnBind userStatus = "unBind"
+	Login  userStatus = "login"
+	Logout userStatus = "logout"
+)
+
 type UserDTO struct {
 	Id           int
 	Username     string
 	Mobile       string
 	OpenId       string
 	UnionId      string
+	LoginStatus  userDao.LogType
 	FollowingGzh int
 	GzhOpenId    string
 }
@@ -29,6 +38,7 @@ func convertUserDTO(user userDao.TemplateUser) UserDTO {
 		Username:     user.Username,
 		Mobile:       user.Mobile,
 		OpenId:       user.OpenId,
+		LoginStatus:  user.LoginStatus,
 		FollowingGzh: user.FollowingGzh,
 	}
 }
@@ -52,7 +62,20 @@ func GetUserAndCountReadTimes(userId int) (err error) {
 	}
 	return
 }
-
+func UserLogout(userId int) (err error) {
+	err = userDao.UserLogout(userId)
+	if err != nil {
+		logger.Error("用户退出登录失败:%v", err)
+	}
+	return
+}
+func UserLogin(userId int) (err error) {
+	err = userDao.UserLogin(userId)
+	if err != nil {
+		logger.Error("用户登录失败:%v", err)
+	}
+	return
+}
 func RegisterTemplateUser(dto *UserDTO) (err error) {
 	templateUser := convertToTemplateUser(dto)
 	err = userDao.RegisterTemplateUser(&templateUser)
@@ -105,10 +128,7 @@ func convertToUserDTO(templateUser userDao.TemplateUser, dto *UserDTO) {
 func BindUserMobile(userId int, mobile string) (err error) {
 	err = userDao.BindMobile(userId, mobile)
 	if err != nil {
-		if !errors.Is(err, gorm.ErrRecordNotFound) {
-			logger.Error("绑定手机号失败:%v", err)
-		}
-		return
+		logger.Error("绑定手机号失败:%v", err)
 	}
 	return
 }

+ 50 - 7
middleware/auth_middleware.go

@@ -46,6 +46,23 @@ var publicRoutes = []string{
 	"/auth/sendCode",
 	"/user/bind_gzh",
 	"/user/wx/notify",
+	"/webhook/*",
+}
+var privateRoutes = []string{
+	"/user/profile",
+	"/user/followAnalyst",
+	"/user/followAnalysts",
+	"/user/followingAnalystList",
+	"/user/readMessages",
+	"/user/readMessage",
+	"/user/feedback",
+	"/user/checkFollowStatus",
+	"/user/followingAnalysts",
+	"/user/message",
+	"/analyst/analystDetail",
+	"/analyst/list",
+	"/media/count",
+	"/report/count",
 }
 
 func AuthMiddleware() web.FilterFunc {
@@ -60,9 +77,7 @@ func AuthMiddleware() web.FilterFunc {
 				_ = ctx.JSONResp(rep)
 				return
 			}
-			fmt.Println(auth)
 			parts := strings.Split(auth, " ")
-			fmt.Println(len(parts))
 			if len(parts) != 2 || parts[0] != Bearer {
 				logger.Error("token参数不符合格式" + auth)
 				_ = ctx.JSONResp(rep)
@@ -74,7 +89,6 @@ func AuthMiddleware() web.FilterFunc {
 				_ = ctx.JSONResp(rep)
 				return
 			}
-			fmt.Println(info)
 			//校验redis中是否合法
 			redisToken := rd().GetString(redis.GenerateTokenKey(info.OpenId))
 			if redisToken != parts[1] {
@@ -90,11 +104,18 @@ func AuthMiddleware() web.FilterFunc {
 				_ = ctx.JSONResp(illegalUser())
 				return
 			}
-			if userInfo.Mobile == "" && path != baseUrl+"/auth/login" {
-				logger.Error("用户手机号为空:%v", err)
-				_ = ctx.JSONResp(illegalUser())
-				return
+			if needCheckLoginStatus(path) {
+				if info.TokenType != jwt.AccessToken || info.Mobile == "-" || info.Mobile == "" {
+					logger.Error("token信息异常,当前token类型为:%v", jwt.GuestToken)
+					_ = ctx.JSONResp(illegalUser())
+					return
+				}
 			}
+			//if userInfo.Mobile == "-" && path != baseUrl+"/auth/login" {
+			//	logger.Error("用户手机号为空:%v", err)
+			//	_ = ctx.JSONResp(illegalUser())
+			//	return
+			//}
 			ctx.Input.SetData("user", userInfo)
 			return
 		}
@@ -144,3 +165,25 @@ func allowed(path string) bool {
 	}
 	return false
 }
+
+func needCheckLoginStatus(path string) bool {
+	for _, p := range publicRoutes {
+		if stringUtils.IsBlank(p) {
+			continue
+		}
+		src := baseUrl + p
+		if strings.HasSuffix(p, "*") {
+
+			target := src[:len(src)-1]
+			fmt.Println("target:" + target)
+			if strings.HasPrefix(path, target) {
+				return true
+			}
+		} else {
+			if src == path {
+				return true
+			}
+		}
+	}
+	return false
+}

+ 24 - 12
models/user/template_user.go

@@ -12,18 +12,21 @@ const (
 )
 
 type TemplateUser struct {
-	Id           int       `gorm:"column:id;primaryKey;autoIncrement:'id'"`
-	Username     string    `gorm:"column:username;type:varchar(20);comment:用户名"`
-	Mobile       string    `gorm:"column:mobile;type:varchar(15);comment:手机号"`
-	OpenId       string    `gorm:"column:open_id;type:varchar(50);comment:open_id"`
-	GzhOpenId    string    `gorm:"column:gzh_open_id;type:varchar(255);comment:gzh_open_id"`
-	UnionId      string    `gorm:"column:union_id;type:varchar(50);comment:union_id"`
-	ReadCount    int       `gorm:"column:read_count;type:int(11);comment:阅读次数"`
-	FollowingGzh int       `gorm:"column:following_gzh;type:int(1);comment:是否关注公众号"`
-	LastReadTime time.Time `gorm:"column:last_read_time;type:timestamps;comment:最后阅读时间"`
-	IsDeleted    int       `gorm:"column:is_deleted;type:int(11);comment:是否删除"`
-	CreatedTime  time.Time `gorm:"column:created_time;type:timestamps;comment:创建时间"`
-	UpdatedTime  time.Time `gorm:"column:updated_time;type:timestamps;comment:更新时间"`
+	Id             int       `gorm:"column:id;primaryKey;autoIncrement:'id'"`
+	Username       string    `gorm:"column:username;type:varchar(20);comment:用户名"`
+	Mobile         string    `gorm:"column:mobile;type:varchar(15);comment:手机号"`
+	OpenId         string    `gorm:"column:open_id;type:varchar(50);comment:open_id"`
+	GzhOpenId      string    `gorm:"column:gzh_open_id;type:varchar(255);comment:gzh_open_id"`
+	UnionId        string    `gorm:"column:union_id;type:varchar(50);comment:union_id"`
+	ReadCount      int       `gorm:"column:read_count;type:int(11);comment:阅读次数"`
+	FollowingGzh   int       `gorm:"column:following_gzh;type:int(1);comment:是否关注公众号"`
+	LastReadTime   time.Time `gorm:"column:last_read_time;type:timestamps;comment:最后阅读时间"`
+	LastLoginTime  time.Time `gorm:"column:last_login_time;type:timestamps;comment:最后登录时间"`
+	LastLogoutTime time.Time `gorm:"column:last_logout_time;type:timestamps;comment:最后登出时间"`
+	LoginStatus    LogType   `gorm:"column:login_status;type:enum('login','logout');default:null;comment:登录`
+	IsDeleted      int       `gorm:"column:is_deleted;type:int(11);comment:是否删除"`
+	CreatedTime    time.Time `gorm:"column:created_time;type:timestamps;comment:创建时间"`
+	UpdatedTime    time.Time `gorm:"column:updated_time;type:timestamps;comment:更新时间"`
 }
 
 func (t *TemplateUser) BeforeCreate(tx *gorm.DB) (err error) {
@@ -74,3 +77,12 @@ func BindGzhOpenId(userId int, gzhOpenId string, followingGzh int) (err error) {
 func GetUserByGzhOpenId(gzhOpenId string) (user TemplateUser, err error) {
 	return queryByColumn("gzh_open_id", gzhOpenId)
 }
+
+func UserLogin(userId int) (err error) {
+	err = models.Main().Table("template_users").Where("id = ?", userId).Updates(map[string]interface{}{"login_status": Login, "last_login_time": time.Now(), "updated_time": time.Now()}).Error
+	return
+}
+func UserLogout(userId int) (err error) {
+	err = models.Main().Table("template_users").Where("id = ?", userId).Updates(map[string]interface{}{"login_status": Logout, "last_logout_time": time.Now(), "updated_time": time.Now()}).Error
+	return
+}

+ 33 - 0
models/user/user_event_flow.go

@@ -0,0 +1,33 @@
+package user
+
+import (
+	"eta/eta_mini_ht_api/models"
+	"gorm.io/gorm"
+	"time"
+)
+
+type LogType string
+
+const (
+	Login  LogType = "login"
+	Logout LogType = "logout"
+)
+
+type UserEventFlow struct {
+	ID         int       `gorm:"primaryKey;autoIncrement"`
+	UserID     int       `gorm:"not null"`
+	EventType  LogType   `gorm:"type:enum('login','logout');not null"`
+	EventTime  time.Time `gorm:"default:null"`
+	CreateTime time.Time `gorm:"default:null"`
+}
+
+// BeforeCreate 钩子用于在创建记录前设置 CreateTime
+func (uef *UserEventFlow) BeforeCreate(tx *gorm.DB) (err error) {
+	uef.CreateTime = time.Now()
+	return
+}
+
+func InsertEventFlow(flow UserEventFlow) error {
+	db := models.Main()
+	return db.Model(&UserEventFlow{}).Create(&flow).Error
+}

+ 91 - 75
service/auth/auth_service.go

@@ -15,6 +15,7 @@ import (
 	stringUtils "eta/eta_mini_ht_api/common/utils/string"
 	smsService "eta/eta_mini_ht_api/domian/sms"
 	userService "eta/eta_mini_ht_api/domian/user"
+	userDao "eta/eta_mini_ht_api/models/user"
 	"fmt"
 	"gorm.io/gorm"
 	"strings"
@@ -28,6 +29,10 @@ var (
 	redisCache *cache.RedisCache
 )
 
+const (
+	emptyPhoneNum = "-"
+)
+
 func message() sms.SMSClient {
 	if smsSender == nil {
 		smsSender = sms.GetInstance()
@@ -126,97 +131,134 @@ type LoginDTO struct {
 	Mobile     string
 }
 
-func Login(login LoginDTO) (token string, err error) {
-	var user userService.UserDTO
-	user, err = userService.GetUserByMobile(login.Mobile)
+func Logout(userId int, openId string) (err error) {
+	//设置用户为登录状态
+	err = userService.UserLogout(userId)
 	if err != nil {
-		if !errors.Is(err, gorm.ErrRecordNotFound) {
-			err = exception.New(exception.UnknownError)
-			return
-		}
-		//注册用户
-		var wechatInfo wechat.WxUser
-		//微信请求异常
-		wechatInfo, err = wx().Login(login.Code)
-		//微信客户端的异常不做处理,已经是EtaError
-		if err != nil {
-			return
-		}
-		user = initUser(wechatInfo, login)
-		err = userService.RegisterTemplateUser(&user)
-		if err != nil {
-			err = exception.New(exception.TemplateUserCreateFailed)
-			return
-		}
+		logger.Error("用户退出登录失败:%v", err)
+		err = exception.New(exception.LogoutFailed)
+		return
+	}
+	_ = userService.UserLogoutFlow(userId)
+	err = rd().Delete(redis.GenerateTokenKey(openId))
+	if err != nil {
+		err = rd().SetString(redis.GenerateTokenKey(openId), "", 1)
+	}
+	if err != nil {
+		logger.Error("用户退出登录失败:%v", err)
+		err = exception.New(exception.LogoutFailed)
+		return
+	}
+	return
+}
+func Login(userId int, openId, mobile, verifyCode string) (token string, err error) {
+	//设置用户为登录状态
+	err = userService.UserLogin(userId)
+	if err != nil {
+		logger.Error("用户登录失败:%v", err)
+		err = exception.New(exception.GenerateTokenFailed)
+		return
 	}
-	token, err = jwt.CreateToken(user.OpenId, user.Mobile)
+	_ = userService.UserLoginFlow(userId)
+	token, err = jwt.CreateToken(openId, mobile, jwt.AccessToken)
 	if err != nil {
 		err = exception.New(exception.GenerateTokenFailed)
 		return
 	}
-	err = rd().SetString(redis.GenerateTokenKey(user.Mobile), token, 90*24*60*60)
+	//保险先删一下
+	_ = rd().Delete(redis.GenerateTokenKey(openId))
+	err = rd().SetString(redis.GenerateTokenKey(openId), token, 90*24*60*60)
 	if err != nil {
 		err = exception.New(exception.GenerateTokenFailed)
 		return
 	}
+	//清除手机验证码
 	codeDTO := smsService.CodeDTO{
-		Mobile: login.Mobile,
-		Code:   login.VerifyCode,
+		Mobile: mobile,
+		Code:   verifyCode,
 	}
 	//登录成功删除短信验证码,数据库留痕
-	err = rd().Delete(redis.GenerateSmsKey(login.Mobile))
+	err = rd().Delete(redis.GenerateSmsKey(mobile))
 	if err != nil {
 		logger.Error("清除redis 短信验证码失败,%v", err)
-		_ = rd().SetString(redis.GenerateSmsKey(login.Mobile), "", 1)
-		return "", err
+		err = rd().SetString(redis.GenerateSmsKey(mobile), "", 1)
+		return
 	}
 	_ = smsService.TryVerifiedCode(codeDTO)
 	return
 }
 
-func BindMobile(userId int, mobile, verifyCode string) (err error) {
+func BindMobile(userId int, mobile string) (err error) {
 	err = userService.BindUserMobile(userId, mobile)
 	if err != nil {
 		err = exception.New(exception.BindMobileFailed)
 		return
 	}
-	codeDTO := smsService.CodeDTO{
-		Mobile: mobile,
-		Code:   verifyCode,
-	}
-	//登录成功删除短信验证码,数据库留痕
-	err = rd().Delete(redis.GenerateSmsKey(mobile))
-	if err != nil {
-		logger.Error("清除redis 短信验证码失败,%v", err)
-		_ = rd().SetString(redis.GenerateSmsKey(mobile), "", 1)
-		return err
-	}
-	_ = smsService.TryVerifiedCode(codeDTO)
+
 	return
 }
 
-func RefreshToken(code string) (token string, isBindMobile int, err error) {
+func RefreshToken(code string) (token string, isBindMobile int, status string, tokenType string, err error) {
 	//注册用户
 	var wechatInfo wechat.WxUser
 	//微信请求异常
 	wechatInfo, err = wx().Login(code)
 	//微信客户端的异常不做处理,已经是EtaError
 	if err != nil {
-		fmt.Println("wx.Login,err:" + err.Error())
+		logger.Error("获取微信用户信息失败,错误信息:%v", err)
 		return
 	}
 	user, err := userService.GetTemplateUserByOpenId(wechatInfo.OpenId)
-	var isAdd bool
-	if err != nil {
-		if err == gorm.ErrRecordNotFound {
-			isAdd = true
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		err = exception.New(exception.TemplateUserFoundFailed)
+		return
+	}
+	//用户记录不存在则返回unbind
+	//未注册/未绑定
+	var mobile string
+	if errors.Is(err, gorm.ErrRecordNotFound) || user.Mobile == "" {
+		status = string(userService.UnBind)
+		tokenType = jwt.GuestToken
+		isBindMobile = 0
+		mobile = emptyPhoneNum
+	} else {
+		isBindMobile = 1
+		//已绑定(登出)
+		if user.LoginStatus == userDao.Logout {
+			tokenType = jwt.GuestToken
+			mobile = emptyPhoneNum
+			status = string(userService.Logout)
 		} else {
-			err = exception.New(exception.TemplateUserNotFound)
+			//已绑定(登录)
+			tokenType = jwt.AccessToken
+			mobile = user.Mobile
+			status = string(userService.Login)
+		}
+	}
+	//通过unionId 查询用户是否已注册,存在是因为先发生了公众号的关注,所以只需要绑定小程序openId就行
+	wxUser, err := userService.GetTemplateUserByUnionId(wechatInfo.UnionId)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		err = exception.New(exception.TemplateUserFoundFailed)
+		return
+	}
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		user.OpenId = wechatInfo.OpenId
+		user.UnionId = wechatInfo.UnionId
+		err = userService.RegisterTemplateUser(&user)
+		if err != nil {
+			err = exception.New(exception.TemplateUserCreateFailed)
+			return
+		}
+	} else { //修改微信小程序openid
+		err = userService.BindUserXcxOpenId(wxUser.Id, user.OpenId)
+		if err != nil {
+			err = exception.New(exception.TemplateUserBindFailed)
 			return
 		}
 	}
-	token, err = jwt.CreateToken(wechatInfo.OpenId, wechatInfo.OpenId)
+	token, err = jwt.CreateToken(wechatInfo.OpenId, mobile, tokenType)
 	if err != nil {
+		logger.Error("生成token失败:%v", err)
 		err = exception.New(exception.GenerateTokenFailed)
 		return
 	}
@@ -225,32 +267,6 @@ func RefreshToken(code string) (token string, isBindMobile int, err error) {
 		err = exception.New(exception.GenerateTokenFailed)
 		return
 	}
-	if user.Mobile == "" {
-		isBindMobile = 0
-	} else {
-		isBindMobile = 1
-	}
-	if isAdd {
-		user.OpenId = wechatInfo.OpenId
-		user.UnionId = wechatInfo.UnionId
-		var isRegister bool
-
-		//判断unionid是否存在
-		wxUser, unionErr := userService.GetTemplateUserByUnionId(wechatInfo.UnionId)
-		if unionErr != nil {
-			if unionErr == gorm.ErrRecordNotFound { //注册用户
-				isRegister = true
-			} else {
-				err = exception.New(exception.TemplateUserNotFound)
-				return
-			}
-		}
-		if isRegister {
-			err = userService.RegisterTemplateUser(&user)
-		} else { //修改微信小程序openid
-			err = userService.BindUserXcxOpenId(wxUser.Id, user.OpenId)
-		}
-	}
 	return
 }