🎨 update project structure

This commit is contained in:
landaiqing
2024-09-29 15:46:35 +08:00
parent 2769467ce2
commit 87f1ff6961
85 changed files with 693 additions and 431 deletions

View File

@@ -0,0 +1,254 @@
package oauth_controller
import (
"encoding/json"
"errors"
"fmt"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"net/http"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"strconv"
"time"
)
type GiteeUser struct {
AvatarURL string `json:"avatar_url"`
Bio string `json:"bio"`
Blog string `json:"blog"`
CreatedAt time.Time `json:"created_at"`
Email string `json:"email"`
EventsURL string `json:"events_url"`
Followers int `json:"followers"`
FollowersURL string `json:"followers_url"`
Following int `json:"following"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Login string `json:"login"`
Name string `json:"name"`
OrganizationsURL string `json:"organizations_url"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsURL string `json:"received_events_url"`
Remark string `json:"remark"`
ReposURL string `json:"repos_url"`
Stared int `json:"stared"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
Watched int `json:"watched"`
Weibo interface{} `json:"weibo"`
}
// GetGiteeRedirectUrl 获取Gitee登录地址
// @Summary 获取Gitee登录地址
// @Description 获取Gitee登录地址
// @Tags Gitee OAuth
// @Produce json
// @Success 200 {string} string "登录地址"
// @Router /controller/oauth/gitee/get_url [get]
func (OAuthController) GetGiteeRedirectUrl(c *gin.Context) {
clientID := global.CONFIG.OAuth.Gitee.ClientID
redirectURI := global.CONFIG.OAuth.Gitee.RedirectURI
url := "https://gitee.com/oauth/authorize?client_id=" + clientID + "&redirect_uri=" + redirectURI + "&response_type=code"
result.OkWithData(url, c)
return
}
// GetGiteeTokenAuthUrl 获取Gitee token
func GetGiteeTokenAuthUrl(code string) string {
clientId := global.CONFIG.OAuth.Gitee.ClientID
clientSecret := global.CONFIG.OAuth.Gitee.ClientSecret
redirectURI := global.CONFIG.OAuth.Gitee.RedirectURI
return fmt.Sprintf(
"https://gitee.com/oauth/token?grant_type=authorization_code&code=%s&client_id=%s&redirect_uri=%s&client_secret=%s",
code, clientId, redirectURI, clientSecret,
)
}
// GetGiteeToken 获取 token
func GetGiteeToken(url string) (*Token, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodPost, url, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 token并返回
var token Token
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
// GetGiteeUserInfo 获取用户信息
func GetGiteeUserInfo(token *Token) (map[string]interface{}, error) {
// 形成请求
var userInfoUrl = "https://gitee.com/api/v5/user" // github用户信息获取接口
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}
// GiteeCallback 处理Gitee回调
// @Summary 处理Gitee回调
// @Description 处理Gitee回调
// @Tags Gitee OAuth
// @Produce json
// @Router /controller/oauth/gitee/callback [get]
func (OAuthController) GiteeCallback(c *gin.Context) {
var err error
// 获取 code
var code = c.Query("code")
if code == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
// 异步获取 token
var tokenChan = make(chan *Token)
var errChan = make(chan error)
go func() {
var tokenAuthUrl = GetGiteeTokenAuthUrl(code)
token, err := GetGiteeToken(tokenAuthUrl)
if err != nil {
errChan <- err
return
}
tokenChan <- token
}()
// 异步获取用户信息
var userInfoChan = make(chan map[string]interface{})
go func() {
token := <-tokenChan
if token == nil {
errChan <- errors.New("failed to get token")
return
}
userInfo, err := GetGiteeUserInfo(token)
if err != nil {
errChan <- err
return
}
userInfoChan <- userInfo
}()
// 等待结果
select {
case err = <-errChan:
global.LOG.Error(err)
return
case userInfo := <-userInfoChan:
userInfoBytes, err := json.Marshal(userInfo)
if err != nil {
global.LOG.Error(err)
return
}
var giteeUser GiteeUser
err = json.Unmarshal(userInfoBytes, &giteeUser)
if err != nil {
global.LOG.Error(err)
return
}
Id := strconv.Itoa(giteeUser.ID)
userSocial, err := userSocialService.QueryUserSocialByOpenIDService(Id, enum.OAuthSourceGitee)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
db := global.DB
tx := db.Begin() // 开始事务
if tx.Error != nil {
global.LOG.Error(tx.Error)
return
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 第一次登录,创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
user := model.ScaAuthUser{
UID: &uidStr,
Username: &giteeUser.Login,
Nickname: &giteeUser.Name,
Avatar: &giteeUser.AvatarURL,
Blog: &giteeUser.Blog,
Email: &giteeUser.Email,
Gender: &enum.Male,
}
addUser, err := userService.AddUserService(user)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
gitee := enum.OAuthSourceGitee
userSocial = model.ScaAuthUserSocial{
UserID: &uidStr,
OpenID: &Id,
Source: &gitee,
}
err = userSocialService.AddUserSocialService(userSocial)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
_, err = global.Casbin.AddRoleForUser(uidStr, enum.User)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
HandleLoginResponse(c, *addUser.UID)
} else {
HandleLoginResponse(c, *userSocial.UserID)
}
return
}
}

View File

@@ -0,0 +1,264 @@
package oauth_controller
import (
"encoding/json"
"errors"
"fmt"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"net/http"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"strconv"
)
type GitHubUser struct {
AvatarURL string `json:"avatar_url"`
Bio interface{} `json:"bio"`
Blog string `json:"blog"`
Company interface{} `json:"company"`
CreatedAt string `json:"created_at"`
Email string `json:"email"`
EventsURL string `json:"events_url"`
Followers int `json:"followers"`
FollowersURL string `json:"followers_url"`
Following int `json:"following"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
GravatarID string `json:"gravatar_id"`
Hireable interface{} `json:"hireable"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Location interface{} `json:"location"`
Login string `json:"login"`
Name string `json:"name"`
NodeID string `json:"node_id"`
NotificationEmail interface{} `json:"notification_email"`
OrganizationsURL string `json:"organizations_url"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsURL string `json:"received_events_url"`
ReposURL string `json:"repos_url"`
SiteAdmin bool `json:"site_admin"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
TwitterUsername interface{} `json:"twitter_username"`
Type string `json:"type"`
UpdatedAt string `json:"updated_at"`
URL string `json:"url"`
}
// GetRedirectUrl 获取github登录url
// @Summary 获取github登录url
// @Description 获取github登录url
// @Tags Github OAuth
// @Produce json
// @Success 200 {string} string "登录url"
// @Router /controller/oauth/github/get_url [get]
func (OAuthController) GetRedirectUrl(c *gin.Context) {
state := c.Query("state")
clientId := global.CONFIG.OAuth.Github.ClientID
redirectUrl := global.CONFIG.OAuth.Github.RedirectURI
url := "https://github.com/login/oauth/authorize?client_id=" + clientId + "&redirect_uri=" + redirectUrl + "&state=" + state
result.OkWithData(url, c)
return
}
// GetTokenAuthUrl 通过code获取token认证url
func GetTokenAuthUrl(code string) string {
clientId := global.CONFIG.OAuth.Github.ClientID
clientSecret := global.CONFIG.OAuth.Github.ClientSecret
return fmt.Sprintf(
"https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s",
clientId, clientSecret, code,
)
}
// GetToken 获取 token
func GetToken(url string) (*Token, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 token并返回
var token Token
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
// GetUserInfo 获取用户信息
func GetUserInfo(token *Token) (map[string]interface{}, error) {
// 形成请求
var userInfoUrl = "https://api.github.com/user" // github用户信息获取接口
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}
// Callback 登录回调函数
// @Summary 登录回调函数
// @Description 登录回调函数
// @Tags Github OAuth
// @Produce json
// @Param code query string true "code"
// @Success 200 {string} string "登录成功"
// @Router /controller/oauth/github/callback [get]
func (OAuthController) Callback(c *gin.Context) {
var err error
// 获取 code
var code = c.Query("code")
if code == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
// 使用channel来接收异步操作的结果
tokenChan := make(chan *Token)
userInfoChan := make(chan map[string]interface{})
errChan := make(chan error)
// 异步获取token
go func() {
var tokenAuthUrl = GetTokenAuthUrl(code)
token, err := GetToken(tokenAuthUrl)
if err != nil {
errChan <- err
return
}
tokenChan <- token
}()
// 异步获取用户信息
go func() {
token := <-tokenChan
if token == nil {
return
}
userInfo, err := GetUserInfo(token)
if err != nil {
errChan <- err
return
}
userInfoChan <- userInfo
}()
select {
case err = <-errChan:
global.LOG.Error(err)
return
case userInfo := <-userInfoChan:
if userInfo == nil {
global.LOG.Error(<-errChan)
return
}
// 继续处理用户信息
userInfoBytes, err := json.Marshal(<-userInfoChan)
if err != nil {
global.LOG.Error(err)
return
}
var gitHubUser GitHubUser
err = json.Unmarshal(userInfoBytes, &gitHubUser)
if err != nil {
global.LOG.Error(err)
return
}
Id := strconv.Itoa(gitHubUser.ID)
userSocial, err := userSocialService.QueryUserSocialByOpenIDService(Id, enum.OAuthSourceGithub)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
db := global.DB
tx := db.Begin() // 开始事务
if tx.Error != nil {
global.LOG.Error(tx.Error)
return
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 第一次登录,创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
user := model.ScaAuthUser{
UID: &uidStr,
Username: &gitHubUser.Login,
Nickname: &gitHubUser.Name,
Avatar: &gitHubUser.AvatarURL,
Blog: &gitHubUser.Blog,
Email: &gitHubUser.Email,
Gender: &enum.Male,
}
addUser, err := userService.AddUserService(user)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
github := enum.OAuthSourceGithub
userSocial = model.ScaAuthUserSocial{
UserID: &uidStr,
OpenID: &Id,
Source: &github,
}
err = userSocialService.AddUserSocialService(userSocial)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
_, err = global.Casbin.AddRoleForUser(uidStr, enum.User)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
HandleLoginResponse(c, *addUser.UID)
} else {
HandleLoginResponse(c, *userSocial.UserID)
}
return
}
}

View File

@@ -0,0 +1,193 @@
package oauth_controller
import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/mssola/useragent"
"gorm.io/gorm"
"net/http"
"schisandra-cloud-album/common/constant"
"schisandra-cloud-album/common/redis"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"schisandra-cloud-album/service/impl"
"schisandra-cloud-album/utils"
"sync"
"time"
)
var mu sync.Mutex
type OAuthController struct{}
var userSocialService = impl.UserSocialServiceImpl{}
var userService = impl.UserServiceImpl{}
var userDeviceService = impl.UserDeviceServiceImpl{}
type Token struct {
AccessToken string `json:"access_token"`
}
var script = `
<script>
window.opener.postMessage('%s', '%s');
window.close();
</script>
`
func HandleLoginResponse(c *gin.Context, uid string) {
res, data := HandelUserLogin(uid, c)
if !res {
return
}
tokenData, err := json.Marshal(data)
if err != nil {
global.LOG.Error(err)
return
}
formattedScript := fmt.Sprintf(script, tokenData, global.CONFIG.System.WebURL())
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(formattedScript))
return
}
// HandelUserLogin 处理用户登录
func HandelUserLogin(userId string, c *gin.Context) (bool, result.Response) {
// 使用goroutine生成accessToken
accessTokenChan := make(chan string)
errChan := make(chan error)
go func() {
accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: &userId})
if err != nil {
errChan <- err
return
}
accessTokenChan <- accessToken
}()
// 使用goroutine生成refreshToken
refreshTokenChan := make(chan string)
expiresAtChan := make(chan int64)
go func() {
refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: &userId}, time.Hour*24*7)
refreshTokenChan <- refreshToken
expiresAtChan <- expiresAt
}()
// 等待accessToken和refreshToken生成完成
var accessToken string
var refreshToken string
var expiresAt int64
var err error
select {
case accessToken = <-accessTokenChan:
case err = <-errChan:
global.LOG.Error(err)
return false, result.Response{}
}
select {
case refreshToken = <-refreshTokenChan:
case expiresAt = <-expiresAtChan:
}
data := ResponseData{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresAt: expiresAt,
UID: &userId,
}
wrong := utils.SetSession(c, constant.SessionKey, data)
if wrong != nil {
return false, result.Response{}
}
// 使用goroutine将数据存入redis
redisErrChan := make(chan error)
go func() {
fail := redis.Set(constant.UserLoginTokenRedisKey+userId, data, time.Hour*24*7).Err()
if fail != nil {
redisErrChan <- fail
return
}
redisErrChan <- nil
}()
// 等待redis操作完成
redisErr := <-redisErrChan
if redisErr != nil {
global.LOG.Error(redisErr)
return false, result.Response{}
}
responseData := result.Response{
Data: data,
Message: "success",
Code: 200,
Success: true,
}
return true, responseData
}
// GetUserLoginDevice 获取用户登录设备
func (OAuthController) GetUserLoginDevice(c *gin.Context) {
userId := c.Query("user_id")
if userId == "" {
return
}
userAgent := c.GetHeader("User-Agent")
if userAgent == "" {
global.LOG.Errorln("user-agent is empty")
return
}
ua := useragent.New(userAgent)
ip := utils.GetClientIP(c)
location, err := global.IP2Location.SearchByStr(ip)
location = utils.RemoveZeroAndAdjust(location)
if err != nil {
global.LOG.Errorln(err)
return
}
isBot := ua.Bot()
browser, browserVersion := ua.Browser()
os := ua.OS()
mobile := ua.Mobile()
mozilla := ua.Mozilla()
platform := ua.Platform()
engine, engineVersion := ua.Engine()
device := model.ScaAuthUserDevice{
UserID: &userId,
IP: &ip,
Location: &location,
Agent: userAgent,
Browser: &browser,
BrowserVersion: &browserVersion,
OperatingSystem: &os,
Mobile: &mobile,
Bot: &isBot,
Mozilla: &mozilla,
Platform: &platform,
EngineName: &engine,
EngineVersion: &engineVersion,
}
mu.Lock()
defer mu.Unlock()
userDevice, err := userDeviceService.GetUserDeviceByUIDIPAgentService(userId, ip, userAgent)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = userDeviceService.AddUserDeviceService(&device)
if err != nil {
global.LOG.Errorln(err)
return
}
return
} else {
err := userDeviceService.UpdateUserDeviceService(userDevice.ID, &device)
if err != nil {
global.LOG.Errorln(err)
return
}
return
}
}

View File

@@ -0,0 +1,304 @@
package oauth_controller
import (
"encoding/json"
"errors"
"fmt"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"net/http"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"strconv"
)
type AuthQQme struct {
ClientID string `json:"client_id"`
OpenID string `json:"openid"`
}
type QQToken struct {
AccessToken string `json:"access_token"`
ExpireIn string `json:"expire_in"`
RefreshToken string `json:"refresh_token"`
}
type QQUserInfo struct {
City string `json:"city"`
Figureurl string `json:"figureurl"`
Figureurl1 string `json:"figureurl_1"`
Figureurl2 string `json:"figureurl_2"`
FigureurlQq string `json:"figureurl_qq"`
FigureurlQq1 string `json:"figureurl_qq_1"`
FigureurlQq2 string `json:"figureurl_qq_2"`
Gender string `json:"gender"`
GenderType int `json:"gender_type"`
IsLost int `json:"is_lost"`
IsYellowVip string `json:"is_yellow_vip"`
IsYellowYearVip string `json:"is_yellow_year_vip"`
Level string `json:"level"`
Msg string `json:"msg"`
Nickname string `json:"nickname"`
Province string `json:"province"`
Ret int `json:"ret"`
Vip string `json:"vip"`
Year string `json:"year"`
YellowVipLevel string `json:"yellow_vip_level"`
}
// GetQQRedirectUrl 获取登录地址
// @Summary 获取QQ登录地址
// @Description 获取QQ登录地址
// @Tags QQ OAuth
// @Produce json
// @Success 200 {string} string "登录地址"
// @Router /controller/oauth/qq/get_url [get]
func (OAuthController) GetQQRedirectUrl(c *gin.Context) {
state := c.Query("state")
clientId := global.CONFIG.OAuth.QQ.ClientID
redirectURI := global.CONFIG.OAuth.QQ.RedirectURI
url := "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" + clientId + "&redirect_uri=" + redirectURI + "&state=" + state
result.OkWithData(url, c)
return
}
// GetQQTokenAuthUrl 通过code获取token认证url
func GetQQTokenAuthUrl(code string) string {
clientId := global.CONFIG.OAuth.QQ.ClientID
clientSecret := global.CONFIG.OAuth.QQ.ClientSecret
redirectURI := global.CONFIG.OAuth.QQ.RedirectURI
return fmt.Sprintf(
"https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&fmt=json",
clientId, clientSecret, code, redirectURI,
)
}
// GetQQToken 获取 token
func GetQQToken(url string) (*QQToken, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil {
global.LOG.Error(err)
return nil, err
}
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
global.LOG.Error(err)
return nil, err
}
//将响应体解析为 token并返回
var token QQToken
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
global.LOG.Error(err)
return nil, err
}
return &token, nil
}
// GetQQUserOpenID 获取用户 openid
func GetQQUserOpenID(token *QQToken) (*AuthQQme, error) {
// 形成请求
var userInfoUrl = "https://graph.qq.com/oauth2.0/me?access_token=" + token.AccessToken + "&fmt=json"
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
global.LOG.Error(err)
return nil, err
}
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
global.LOG.Error(err)
return nil, err
}
// 将响应体解析为 AuthQQme并返回
var authQQme AuthQQme
if err = json.NewDecoder(res.Body).Decode(&authQQme); err != nil {
global.LOG.Error(err)
return nil, err
}
return &authQQme, nil
}
// GetQQUserUserInfo 获取用户信息
func GetQQUserUserInfo(token *QQToken, openId string) (map[string]interface{}, error) {
clientId := global.CONFIG.OAuth.QQ.ClientID
// 形成请求
var userInfoUrl = "https://graph.qq.com/user/get_user_info?access_token=" + token.AccessToken + "&oauth_consumer_key=" + clientId + "&openid=" + openId
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
global.LOG.Error(err)
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
global.LOG.Error(err)
return nil, err
}
return userInfo, nil
}
// QQCallback QQ登录回调
// @Summary QQ登录回调
// @Description QQ登录回调
// @Tags QQ OAuth
// @Produce json
// @Router /controller/oauth/qq/callback [get]
func (OAuthController) QQCallback(c *gin.Context) {
var err error
// 获取 code
var code = c.Query("code")
if code == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
// 通过 code, 获取 token
var tokenAuthUrl = GetQQTokenAuthUrl(code)
tokenChan := make(chan *QQToken)
errChan := make(chan error)
go func() {
token, err := GetQQToken(tokenAuthUrl)
if err != nil {
errChan <- err
return
}
tokenChan <- token
}()
var token *QQToken
select {
case token = <-tokenChan:
case err = <-errChan:
global.LOG.Error(err)
return
}
// 通过 token获取 openid
openIDChan := make(chan *AuthQQme)
errChan = make(chan error)
go func() {
authQQme, err := GetQQUserOpenID(token)
if err != nil {
errChan <- err
return
}
openIDChan <- authQQme
}()
var authQQme *AuthQQme
select {
case authQQme = <-openIDChan:
case err = <-errChan:
global.LOG.Error(err)
return
}
// 通过token获取用户信息
userInfoChan := make(chan map[string]interface{})
errChan = make(chan error)
go func() {
userInfo, err := GetQQUserUserInfo(token, authQQme.OpenID)
if err != nil {
errChan <- err
return
}
userInfoChan <- userInfo
}()
var userInfo map[string]interface{}
select {
case userInfo = <-userInfoChan:
case err = <-errChan:
global.LOG.Error(err)
return
}
userInfoBytes, err := json.Marshal(userInfo)
if err != nil {
global.LOG.Error(err)
return
}
var qqUserInfo QQUserInfo
err = json.Unmarshal(userInfoBytes, &qqUserInfo)
if err != nil {
global.LOG.Error(err)
return
}
userSocial, err := userSocialService.QueryUserSocialByOpenIDService(authQQme.OpenID, enum.OAuthSourceQQ)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
db := global.DB
tx := db.Begin() // 开始事务
if tx.Error != nil {
global.LOG.Error(tx.Error)
return
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 第一次登录,创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
user := model.ScaAuthUser{
UID: &uidStr,
Username: &authQQme.OpenID,
Nickname: &qqUserInfo.Nickname,
Avatar: &qqUserInfo.FigureurlQq1,
Gender: &qqUserInfo.Gender,
}
addUser, err := userService.AddUserService(user)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
qq := enum.OAuthSourceQQ
userSocial = model.ScaAuthUserSocial{
UserID: &uidStr,
OpenID: &authQQme.OpenID,
Source: &qq,
}
err = userSocialService.AddUserSocialService(userSocial)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
_, err = global.Casbin.AddRoleForUser(uidStr, enum.User)
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
global.LOG.Error(err)
return
}
HandleLoginResponse(c, *addUser.UID)
return
} else {
HandleLoginResponse(c, *userSocial.UserID)
}
}

View File

@@ -0,0 +1,19 @@
package oauth_controller
import "encoding/json"
// ResponseData 返回数据
type ResponseData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresAt int64 `json:"expires_at"`
UID *string `json:"uid"`
}
func (res ResponseData) MarshalBinary() ([]byte, error) {
return json.Marshal(res)
}
func (res ResponseData) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, &res)
}

View File

@@ -0,0 +1,332 @@
package oauth_controller
import (
"encoding/gob"
"encoding/json"
"errors"
"github.com/ArtisanCloud/PowerLibs/v3/http/helper"
"github.com/ArtisanCloud/PowerWeChat/v3/src/basicService/qrCode/response"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/contract"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/messages"
models2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models"
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount/server/handlers/models"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"schisandra-cloud-album/common/constant"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/common/randomname"
"schisandra-cloud-album/common/redis"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/controller/websocket_controller"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"schisandra-cloud-album/utils"
"strconv"
"strings"
"time"
)
// CallbackNotify 微信回调
// @Summary 微信回调
// @Tags 微信公众号
// @Description 微信回调
// @Produce json
// @Router /controller/oauth/callback_notify [POST]
func (OAuthController) CallbackNotify(c *gin.Context) {
rs, err := global.Wechat.Server.Notify(c.Request, func(event contract.EventInterface) interface{} {
switch event.GetMsgType() {
case models2.CALLBACK_MSG_TYPE_EVENT:
switch event.GetEvent() {
case models.CALLBACK_EVENT_SUBSCRIBE:
msg := models.EventSubscribe{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
key := strings.TrimPrefix(msg.EventKey, "qrscene_")
res := wechatLoginHandler(msg.FromUserName, key, c)
if !res {
return messages.NewText(ginI18n.MustGetMessage(c, "LoginFailed"))
}
return messages.NewText(ginI18n.MustGetMessage(c, "LoginSuccess"))
case models.CALLBACK_EVENT_UNSUBSCRIBE:
msg := models.EventUnSubscribe{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
return messages.NewText("ok")
case models.CALLBACK_EVENT_SCAN:
msg := models.EventScan{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
res := wechatLoginHandler(msg.FromUserName, msg.EventKey, c)
if !res {
return messages.NewText(ginI18n.MustGetMessage(c, "LoginFailed"))
}
return messages.NewText(ginI18n.MustGetMessage(c, "LoginSuccess"))
}
case models2.CALLBACK_MSG_TYPE_TEXT:
msg := models.MessageText{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
}
return messages.NewText("ok")
})
if err != nil {
panic(err)
}
err = helper.HttpResponseSend(rs, c.Writer)
if err != nil {
panic(err)
}
}
// CallbackVerify 微信回调验证
// @Summary 微信回调验证
// @Tags 微信公众号
// @Description 微信回调验证
// @Produce json
// @Router /controller/oauth/callback_verify [get]
func (OAuthController) CallbackVerify(c *gin.Context) {
rs, err := global.Wechat.Server.VerifyURL(c.Request)
if err != nil {
panic(err)
}
err = helper.HttpResponseSend(rs, c.Writer)
}
// GetTempQrCode 获取临时二维码
// @Summary 获取临时二维码
// @Tags 微信公众号
// @Description 获取临时二维码
// @Produce json
// @Param client_id query string true "客户端ID"
// @Router /controller/oauth/get_temp_qrcode [get]
func (OAuthController) GetTempQrCode(c *gin.Context) {
clientId := c.Query("client_id")
if clientId == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
ip := utils.GetClientIP(c) // 使用工具函数获取客户端IP
key := constant.UserLoginQrcodeRedisKey + ip
// 从Redis获取二维码数据
qrcode := redis.Get(key).Val()
if qrcode != "" {
data := new(response.ResponseQRCodeCreate)
if err := json.Unmarshal([]byte(qrcode), data); err != nil {
global.LOG.Error(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
result.OK(ginI18n.MustGetMessage(c, "QRCodeGetSuccess"), data.Url, c)
return
}
// 生成临时二维码
data, err := global.Wechat.QRCode.Temporary(c.Request.Context(), clientId, 7*24*3600)
if err != nil {
global.LOG.Error(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
// 序列化数据并存储到Redis
serializedData, err := json.Marshal(data)
if err != nil {
global.LOG.Error(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
if err := redis.Set(key, serializedData, time.Hour*24*7).Err(); err != nil {
global.LOG.Error(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
result.OK(ginI18n.MustGetMessage(c, "QRCodeGetSuccess"), data.Url, c)
}
// wechatLoginHandler 微信登录处理
func wechatLoginHandler(openId string, clientId string, c *gin.Context) bool {
if openId == "" {
return false
}
authUserSocial, err := userSocialService.QueryUserSocialByOpenIDService(openId, enum.OAuthSourceWechat)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
tx := global.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
avatar, err := utils.GenerateAvatar(uidStr)
name := randomname.GenerateName()
if err != nil {
global.LOG.Errorln(err)
return false
}
createUser := model.ScaAuthUser{
UID: &uidStr,
Username: &openId,
Avatar: &avatar,
Nickname: &name,
Gender: &enum.Male,
}
// 异步添加用户
addUserChan := make(chan *model.ScaAuthUser, 1)
errChan := make(chan error, 1)
go func() {
addUser, err := userService.AddUserService(createUser)
if err != nil {
errChan <- err
return
}
addUserChan <- addUser
}()
var addUser *model.ScaAuthUser
select {
case addUser = <-addUserChan:
case err := <-errChan:
tx.Rollback()
global.LOG.Error(err)
return false
}
wechat := enum.OAuthSourceWechat
userSocial := model.ScaAuthUserSocial{
UserID: &uidStr,
OpenID: &openId,
Source: &wechat,
}
// 异步添加用户社交信息
wrongChan := make(chan error, 1)
go func() {
wrong := userSocialService.AddUserSocialService(userSocial)
wrongChan <- wrong
}()
select {
case wrong := <-wrongChan:
if wrong != nil {
tx.Rollback()
global.LOG.Error(wrong)
return false
}
}
// 异步添加角色
roleErrChan := make(chan error, 1)
go func() {
_, err := global.Casbin.AddRoleForUser(uidStr, enum.User)
roleErrChan <- err
}()
select {
case err := <-roleErrChan:
if err != nil {
tx.Rollback()
global.LOG.Error(err)
return false
}
}
// 异步处理用户登录
resChan := make(chan bool, 1)
go func() {
res := handelUserLogin(*addUser.UID, clientId, c)
resChan <- res
}()
select {
case res := <-resChan:
if !res {
tx.Rollback()
return false
}
}
tx.Commit()
return true
} else {
res := handelUserLogin(*authUserSocial.UserID, clientId, c)
if !res {
return false
}
return true
}
}
// handelUserLogin 处理用户登录
func handelUserLogin(userId string, clientId string, c *gin.Context) bool {
resultChan := make(chan bool, 1)
go func() {
accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: &userId})
if err != nil {
resultChan <- false
return
}
refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: &userId}, time.Hour*24*7)
data := ResponseData{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresAt: expiresAt,
UID: &userId,
}
fail := redis.Set(constant.UserLoginTokenRedisKey+userId, data, time.Hour*24*7).Err()
if fail != nil {
resultChan <- false
return
}
responseData := result.Response{
Data: data,
Message: "success",
Code: 200,
Success: true,
}
tokenData, err := json.Marshal(responseData)
if err != nil {
resultChan <- false
return
}
gob.Register(ResponseData{})
wrong := utils.SetSession(c, constant.SessionKey, data)
if wrong != nil {
resultChan <- false
return
}
// gws方式发送消息
err = websocket_controller.Handler.SendMessageToClient(clientId, tokenData)
if err != nil {
global.LOG.Error(err)
resultChan <- false
return
}
resultChan <- true
}()
return <-resultChan
}