package api import ( "fmt" "github.com/gin-gonic/gin" "log" "math/rand" "qnc-server/config" common "qnc-server/model/common" model "qnc-server/model/model" "qnc-server/model/request" "qnc-server/model/response" "qnc-server/service" "qnc-server/utils" "strconv" "time" ) type User struct { } var userService service.UserService var verifyService service.VerifyCode // 注册路由 func InitUser(group *gin.RouterGroup) { var u User userPublicGroup := group.Group("user") userPrivateGroup := group.Group("user") { userPublicGroup.GET("test", u.Test) userPublicGroup.POST("log", u.Log) userPublicGroup.POST("login", u.Login) userPublicGroup.POST("h5_login", u.H5Login) userPublicGroup.POST("phone_login", u.PhoneLogin) userPublicGroup.POST("verify", u.GetVerify) userPublicGroup.GET("get_config", u.GetSDKConfig) } { userPrivateGroup.Use(JWTAuth()) } } func (u *User) Test(c *gin.Context) { authIdentifier := model.AuthIdentifier{ OpenID: "oR_hJ6kv2bmH7YWPzuZdwQvN4B5g", UnionID: "ogzHA6W4T7vHGl-99nlHkSJDczBc", } authentications, err := userService.MatchingAuthentications(authIdentifier) if err != nil { return } response.OkWithData(authentications, c) } func (u *User) Log(c *gin.Context) { postForm := c.Request.PostForm log.Printf("前端请求打印:%s", postForm) response.Ok(c) } func (u *User) Login(c *gin.Context) { var reqBody request.UserLoginReq // 尝试将请求的 JSON body 绑定到 reqBody 结构体 if err := c.ShouldBindJSON(&reqBody); err != nil { response.FailWithMessage(err.Error(), c) return } // 检查请求体中是否包含 code 字段 if reqBody.Code == "" { response.FailWithMessage("code is required", c) return } // Code获取用户信息 wxResponse, err := userService.RequestWx(reqBody.Code) if err != nil { response.FailWithMessage(fmt.Errorf("request weixin err:%w", err).Error(), c) return } authIdentifier := model.AuthIdentifier{ OpenID: wxResponse.OpenId, UnionID: wxResponse.Unionid, } // 是否有匹配的校验信息 authentications, err := userService.MatchingAuthentications(authIdentifier) if err != nil { log.Printf("MatchingAuthentications Error:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } var authentication model.Authentication if len(authentications) == 0 { authentication, err = userService.Register(model.User{}, model.AUTHTYPE_WECHAT_MP, authIdentifier) if err != nil { log.Printf("注册用户错误:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } //成功创建用户 } else { // 校验信息是否有该平台 hasWechatMP := false for _, auth := range authentications { if auth.AuthType == model.AUTHTYPE_WECHAT_MP { hasWechatMP = true authentication = auth } } // 没有则创建该平台校验并关联用户 if !hasWechatMP { userid := authentication.UserID authentication, err = userService.CreateAuthentications(userid, authIdentifier, model.AUTHTYPE_WECHAT_MP) if err != nil { log.Printf("创建平台校验错误:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } //成功校验该平台成功 } } user, err := userService.GetUser(authentication.UserID) if err != nil { log.Printf("authentication获取关联用户失败:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } u.TokenNext(c, user, authIdentifier, model.AUTHTYPE_WECHAT_MP) } // 公众号h5登录 func (u *User) H5Login(c *gin.Context) { var reqBody request.H5UserLoginReq // 尝试将请求的 JSON body 绑定到 reqBody 结构体 if err := c.ShouldBindJSON(&reqBody); err != nil { response.FailWithMessage(err.Error(), c) return } if reqBody.UserinfoAuth == nil { response.FailWithDetailed(reqBody, "根本没有Auth", c) return } // 检查请求体中是否包含 code 字段 if reqBody.Code == "" { response.FailWithMessage("code is required", c) return } // 获取到h5的openid token wxH5Response, err := userService.RequestWxH5(reqBody.Code) if err != nil { log.Printf("request weixin err:%v", err) response.FailWithMessage(fmt.Sprintf("登录失败,请稍后再试:%v", err), c) return } authIdentifier := model.AuthIdentifier{ OpenID: wxH5Response.Openid, } // 是否有匹配的校验信息 authentications, err := userService.MatchingAuthentications(authIdentifier) if err != nil { log.Printf("GetUserByH5 err:%v", err) response.FailWithMessage(fmt.Sprintf("系统错误登录失败,请稍后再试:%v", err), c) return } var authentication model.Authentication if len(authentications) == 0 { // 是否手动授权登录 if *reqBody.UserinfoAuth { userinfo, err := userService.GetSnsUserInfo(wxH5Response.AccessToken, wxH5Response.Openid) if err != nil { log.Printf("request weixin getuserinfo err:%v", err) response.FailWithMessage(fmt.Sprintf("登录失败,请稍后再试:%v", err), c) return } log.Printf("UserInfo:%v", userinfo) authIdentifier.UnionID = userinfo.UnionID log.Printf("authIdentifier:%v", authIdentifier) //查找匹配UnionID authentications, err = userService.MatchingAuthentications(authIdentifier) log.Printf("authentications:%v", authentications) if err != nil { log.Printf("get user err:%v", err) response.FailWithMessage(fmt.Sprintf("登录失败,请稍后再试:%v", err), c) return } if len(authentications) == 0 { //没有则创建用户 authentication, err = userService.Register(model.User{}, model.AUTHTYPE_WECHAT_H5, authIdentifier) if err != nil { log.Printf("注册用户错误:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } } else { // 有则创建平台校验 authentication, err = userService.CreateAuthentications(authentications[0].UserID, authIdentifier, model.AUTHTYPE_WECHAT_H5) if err != nil { log.Printf("创建平台校验错误:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } } } else { // 静默登录并且库中无openid,让前端重新进行手动授权 response.ManualAuth(c) return } } else { for _, auth := range authentications { if auth.AuthType == model.AUTHTYPE_WECHAT_H5 { authentication = auth } } } user, err := userService.GetUser(authentication.UserID) if err != nil { log.Printf("authentication获取关联用户失败:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } //有则直接登录 u.TokenNext(c, user, authIdentifier, model.AUTHTYPE_WECHAT_H5) } // 手机号登录 func (u *User) PhoneLogin(c *gin.Context) { var reqBody request.PhoneLoginReq // 尝试将请求的 JSON body 绑定到 reqBody 结构体 if err := c.ShouldBindJSON(&reqBody); err != nil { response.FailWithMessage(err.Error(), c) return } // 校验验证码 isRight, err := utils.VerifyCode(reqBody.PhoneNumber, reqBody.VerifyCode) if err != nil { response.FailWithMessage(err.Error(), c) return } else if !isRight { response.FailWithMessage("验证码错误", c) return } authIdentifier := model.AuthIdentifier{ Phone: reqBody.PhoneNumber, } authentications, err := userService.MatchingAuthentications(authIdentifier) var authentication model.Authentication if len(authentications) == 0 { authentication, err = userService.Register(model.User{Phone: reqBody.PhoneNumber}, model.AUTHTYPE_PHONE, authIdentifier) if err != nil { log.Printf("注册用户错误:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } //成功创建用户 } else { authentication = authentications[0] } user, err := userService.GetUser(authentication.UserID) if err != nil { log.Printf("authentication获取关联用户失败:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } u.TokenNext(c, user, authIdentifier, model.AUTHTYPE_PHONE) } // TokenNext 登录以后签发jwt func (u *User) TokenNext(c *gin.Context, user *model.User, authIdentifiers model.AuthIdentifier, platform model.AuthType) { j := &utils.JWT{SigningKey: []byte(config.ConfigData.JWT.SigningKey)} // 唯一签名 claims := j.CreateClaims(common.BaseClaims{ Userid: user.Userid, Disable: user.Disable != nil && *user.Disable, AuthIdentifiers: authIdentifiers, Platform: platform, }) token, err := j.CreateToken(claims) if err != nil { response.FailWithMessage("获取token失败", c) return } utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix())) response.OkWithDetailed(response.LoginResponse{ User: *user, //AuthIdentifiers: authIdentifiers, Token: token, ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000, }, "登录成功", c) return } // 获取验证码 func (u *User) GetVerify(c *gin.Context) { var req request.VerifyCodeReq // 绑定JSON数据到结构体 if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage("PhoneNumber is required", c) return } // 验证手机号码格式 if !utils.IsValidPhoneNumber(req.PhoneNumber) { response.FailWithMessage("Invalid phone number format", c) return } existsCode, err := utils.CanRequestCode(req.PhoneNumber) if err != nil { return } if existsCode { response.FailWithMessage("请求过于频繁,请稍后再试", c) return } var code = utils.GenerateVerificationCode() defaultTemplateCode := config.ConfigData.VerifyCode.TemplateCode if req.Template == "marriage" { defaultTemplateCode = config.ConfigData.VerifyCode.MarriageTemplateCode } err = verifyService.Send(code, req.PhoneNumber, defaultTemplateCode) if err != nil { log.Printf("【获取验证码】请求验证码发送失败:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } err = utils.StoreVerificationCode(req.PhoneNumber, code) if err != nil { log.Printf("【获取验证码】验证码缓存失败:%v", err) response.FailWithMessage("系统错误,请稍后再试", c) return } response.OkWithMessage("获取成功", c) } // h5 sdk config func (u *User) GetSDKConfig(c *gin.Context) { pageURL := c.Query("url") if pageURL == "" { log.Printf("GetSDKConfig url 没传") response.FailWithMessage("系统错误", c) return } ticket, err := userService.GetJsapiTicket() if err != nil || ticket == "" { log.Printf("get jsapi ticket err:%v", err) response.FailWithMessage("获取配置失败", c) return } nonceStr := strconv.Itoa(rand.Int()) timestamp := strconv.FormatInt(time.Now().Unix(), 10) signature := userService.CreateSignature(ticket, nonceStr, timestamp, pageURL) response.OkWithData(gin.H{ "ticket": ticket, "appId": config.ConfigData.System.WxH5AppId, "timestamp": timestamp, "nonceStr": nonceStr, "signature": signature, }, c) }