From 22474e459ff74f8e5506fdb4a15dbb29e263110e Mon Sep 17 00:00:00 2001 From: junleea <354425203@qq.com> Date: Sat, 26 Apr 2025 18:59:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0github=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=BB=9F=E4=B8=80=E8=8E=B7=E5=8F=96?= =?UTF-8?q?url=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E4=B8=8D=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handler/knowledgeBase.go | 1 + handler/tool.go | 128 ++++++++++++++++++++++++++++++++++----- proto/conf.go | 3 +- proto/status.go | 6 ++ proto/tool.go | 24 ++++++++ service/toolService.go | 32 ++++++++++ worker/github.go | 63 +++++++++++++++++++ 7 files changed, 241 insertions(+), 16 deletions(-) create mode 100644 worker/github.go diff --git a/handler/knowledgeBase.go b/handler/knowledgeBase.go index ad23c7d..5f3b9d3 100644 --- a/handler/knowledgeBase.go +++ b/handler/knowledgeBase.go @@ -97,5 +97,6 @@ func DeleteKnowledgeBase(c *gin.Context) { resp.Code = proto.ParameterError resp.Message = "参数错误:" + err.Error() } + c.JSON(http.StatusOK, resp) } diff --git a/handler/tool.go b/handler/tool.go index 7387b6c..1e31aed 100644 --- a/handler/tool.go +++ b/handler/tool.go @@ -6,6 +6,7 @@ import ( "StuAcaWorksAI/service" "StuAcaWorksAI/service/spark" "StuAcaWorksAI/worker" + "encoding/base64" "encoding/json" "fmt" "github.com/gin-gonic/gin" @@ -57,8 +58,9 @@ func SetUpToolGroup(router *gin.Engine) { toolGroup.POST("/monitor", SetDeviceStatusV2) toolGroup.POST("/qq_callback", handleQQCallback) toolGroup.GET("/qq_auth", GetQQAuthUrl) - toolGroup.GET("/github_auth", ToGithubAuthPage) - toolGroup.POST("/github_callback", handleGithubCallback) + toolGroup.GET("/github_auth", GetGithubAuthUrl) + toolGroup.GET("/get_auth_url", GetThirdPartyAuthUrl) + toolGroup.GET("/github_callback", handleGithubCallback) toolGroup.POST("/loginRedirect", LoginRedirect) //发送邮件 toolGroup.POST("/send_mail", SendMailTool) @@ -641,10 +643,29 @@ func GetSparkCreatePPTStatus(c *gin.Context) { } func handleGithubCallback(c *gin.Context) { - + var resp proto.GenerateResp + code := c.Query("code") //code + stateBase64Str := c.Query("state") //state + //解析base64 + decodedBytes, err := base64.StdEncoding.DecodeString(stateBase64Str) + if err != nil { + fmt.Println("Decoding error:", err) + } else { + decodedStr := string(decodedBytes) + //json解析 + var state proto.ThirdPartyLoginState + err = json.Unmarshal([]byte(decodedStr), &state) + if err != nil { + log.Println("json unmarshal error:", err) + } + service.DoGithubCallBack(&state, code) + } + resp.Code = 0 + resp.Message = "success" + c.JSON(http.StatusOK, resp) } -func ToGithubAuthPage(c *gin.Context) { +func GetGithubAuthUrl(c *gin.Context) { uuid := c.Query("uuid") hType := c.Query("type") //操作类型add,login var resp proto.GenerateResp @@ -653,21 +674,98 @@ func ToGithubAuthPage(c *gin.Context) { resp.Message = "uuid or type is empty" c.JSON(http.StatusOK, resp) return + } else { + var state proto.ThirdPartyLoginState + state.UUID = uuid + state.Type = hType + state.Platform = "github" + state.Project = "saw" + stateStr, _ := json.Marshal(state) + //base64编码 + stateBase64Str := base64.StdEncoding.EncodeToString(stateStr) + + params := url.Values{} + params.Add("client_id", proto.Config.GITHUB_CLIENT_ID) + params.Add("login", uuid) + params.Add("state", stateBase64Str) + baseUri := "https://github.com/login/oauth/authorize" + redirectUrl := fmt.Sprintf("%s?%s", baseUri, params.Encode()) + //c.Redirect(http.StatusFound, redirectUrl) + resp.Message = "success" + resp.Code = proto.SuccessCode + resp.Data = redirectUrl + c.JSON(http.StatusOK, resp) } - params := url.Values{} - params.Add("client_id", proto.Config.GITHUB_CLIENT_ID) - params.Add("login", uuid) - params.Add("state", "saw_"+hType+"_"+uuid) - baseUri := "https://github.com/login/oauth/authorize" - redirectUrl := fmt.Sprintf("%s?%s", baseUri, params.Encode()) - //c.Redirect(http.StatusFound, redirectUrl) - resp.Message = "success" - resp.Code = proto.SuccessCode - resp.Data = redirectUrl - c.JSON(http.StatusOK, resp) } func LoginRedirect(c *gin.Context) { c.Redirect(http.StatusFound, "https://sv.ljsea.top/") //重定向到登录页面 } + +func GetThirdPartyAuthUrl(c *gin.Context) { + platform := c.Query("platform") + uuid := c.Query("uuid") + hType := c.Query("type") //操作类型add,login + var resp proto.GenerateResp + if platform == "" || uuid == "" || hType == "" { + resp.Code = proto.ParameterError + resp.Message = "platform or uuid is empty" + c.JSON(http.StatusOK, resp) + return + } + var state proto.ThirdPartyLoginState + state.UUID = uuid + state.Type = hType + state.Platform = platform + state.Project = "SAW" + if hType == "add" { + //查看是否已经绑定 + token := c.Request.Header.Get("token") + if token == "" { + resp.Code = proto.ParameterError + resp.Message = "token is empty" + return + } + userID, err := service.DecodeJWTToken(token) + if err != nil { + resp.Code = proto.ParameterError + resp.Message = err.Error() + return + } + //需要将uuid绑定在该用户上 + worker.SetRedisWithExpire("user_add_platform_"+uuid, strconv.Itoa(userID), time.Minute*9) + state.UserID = userID + } + + stateStr, _ := json.Marshal(state) + var respUrl string + //base64编码 + stateBase64Str := base64.StdEncoding.EncodeToString(stateStr) + switch platform { + case "qq": + params := url.Values{} + params.Add("response_type", "code") + params.Add("client_id", worker.AppId) + params.Add("state", stateBase64Str) + str := fmt.Sprintf("%s", params.Encode()) + respUrl = fmt.Sprintf("%s?%s", proto.QQAuthorizeBaseUrl, str) + case "github": + params := url.Values{} + params.Add("client_id", proto.Config.GITHUB_CLIENT_ID) + params.Add("login", uuid) + params.Add("state", stateBase64Str) + baseUri := proto.GitHuAuthorizeBaseUrl + respUrl = fmt.Sprintf("%s?%s", baseUri, params.Encode()) + } + resp.Message = "success" + resp.Code = proto.SuccessCode + resp.Data = respUrl +} + +type GetThirdPartyAddAuthUrlReq struct { + Platform string `json:"platform" form:"platform"` + Uuid string `json:"uuid" form:"uuid"` + HType string `json:"type" form:"type"` //操作类型add,login + //Platform string `json:"platform" form:"platform"` //操作类型add,login +} diff --git a/proto/conf.go b/proto/conf.go index a9eee59..1cf157b 100644 --- a/proto/conf.go +++ b/proto/conf.go @@ -9,7 +9,7 @@ import ( var Config ConfigStruct var SigningKey = []byte{} -var Url_map = map[string]bool{"/login": true, "/register": true, "/uuid": true, "/gqr": true, "/cid/callback": true, "/tool/monitor": true, "/user/sync": true, "/tool/file/": true, "/user/reset": true, "/tool/qq_auth": true, "/tool/qq_callback": true, "/tool/github_auth": true, "/tool/github_callback": true, "/user/oAuth": true, "/user/oAuth_uuid": true, "/tool/loginRedirect": true} // 不需要token验证的url +var Url_map = map[string]bool{"/login": true, "/register": true, "/uuid": true, "/gqr": true, "/cid/callback": true, "/tool/monitor": true, "/user/sync": true, "/tool/file/": true, "/user/reset": true, "/tool/qq_auth": true, "/tool/qq_callback": true, "/tool/github_auth": true, "/tool/github_callback": true, "/user/oAuth": true, "/user/oAuth_uuid": true, "/tool/loginRedirect": true, "/tool/get_auth_url": true} // 不需要token验证的url var Per_menu_map = map[string]int{"/video/": 1, "/device/": 2, "/cid/": 3} var File_Type = map[string]int{"im": 1, "avatar": 2, "file": 3, "config": 4} // 文件类型 const ( @@ -86,6 +86,7 @@ type ConfigStruct struct { SPARK_PPT_USAGE bool `json:"spark_ppt_usage"` // 是否使用spark ppt功能 KBASE_SERVER []KBaseServer `json:"kbase_server"` // 知识库服务器列表 GITHUB_CLIENT_ID string `json:"github_client_id"` // github client id + GITHUB_CLIENT_SECRET string `json:"github_client_secret"` // github client secret } type KBaseServer struct { diff --git a/proto/status.go b/proto/status.go index 0a41017..1bbb890 100644 --- a/proto/status.go +++ b/proto/status.go @@ -183,3 +183,9 @@ const ( FileTypeText = "text_file" FileTypeImage = "image_file" ) + +// 第三方登录设计url +const ( + GitHuAuthorizeBaseUrl = "https://github.com/login/oauth/authorize" + QQAuthorizeBaseUrl = "https://graph.qq.com/oauth2.0/authorize" +) diff --git a/proto/tool.go b/proto/tool.go index 65a496b..15049d8 100644 --- a/proto/tool.go +++ b/proto/tool.go @@ -68,3 +68,27 @@ type UserLoginInfo struct { Email string `json:"email"` // 用户邮箱 Token string `json:"token"` // 用户token } + +// 第三方登录state +type ThirdPartyLoginState struct { + UUID string `json:"uuid"` // uuid + Type string `json:"type"` // 操作类型add,login + Project string `json:"project"` // 项目名称,saw + //第三方平台 + Platform string `json:"platform"` // 平台名称,qq,github + UserID int `json:"user_id"` // 用户ID,当为add时需要 +} + +// github +type GitHubOAuthResponse struct { + AccessToken string `json:"access_token"` + Scope string `json:"scope"` + TokenType string `json:"token_type"` +} + +type GitHubOAuthRequest struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Code string `json:"code"` + RedirectURI string `json:"redirect_uri"` +} diff --git a/service/toolService.go b/service/toolService.go index c449682..14c1fea 100644 --- a/service/toolService.go +++ b/service/toolService.go @@ -6,6 +6,7 @@ import ( "StuAcaWorksAI/worker" "encoding/json" "fmt" + "github.com/golang-jwt/jwt" "log" "regexp" "time" @@ -201,3 +202,34 @@ func SetDashboardInfoToRedis() { } } + +func DoGithubCallBack(state *proto.ThirdPartyLoginState, code string) { + //获取Access Token + +} + +// 解析jwt内容 +func DecodeJWTToken(tokenStr string) (int, error) { + //解析jwt + // 使用加密secret 解析 JWT 令牌 + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + return proto.SigningKey, nil + }) + if err != nil { + return 0, err + } + // 验证令牌 + if !token.Valid { + return 0, fmt.Errorf("invalid token") + } + // 获取用户ID + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return 0, fmt.Errorf("invalid token claims") + } + userID, ok := claims["id"].(float64) + if !ok { + return 0, fmt.Errorf("invalid token claims") + } + return int(userID), nil +} diff --git a/worker/github.go b/worker/github.go new file mode 100644 index 0000000..2d8d789 --- /dev/null +++ b/worker/github.go @@ -0,0 +1,63 @@ +package worker + +import ( + "StuAcaWorksAI/proto" + "bytes" + "encoding/json" + "io" + "net/http" +) + +// 获取access token +func ExchangeCodeForAccessToken(clientID, clientSecret, code, redirectURI string) (proto.GitHubOAuthResponse, error) { + request := proto.GitHubOAuthRequest{ + ClientID: clientID, + ClientSecret: clientSecret, + Code: code, + RedirectURI: redirectURI, + } + + payload, err := json.Marshal(request) + if err != nil { + return proto.GitHubOAuthResponse{}, err + } + + req, err := http.NewRequest("POST", "https://github.com/login/oauth/access_token", bytes.NewBuffer(payload)) + if err != nil { + return proto.GitHubOAuthResponse{}, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return proto.GitHubOAuthResponse{}, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return proto.GitHubOAuthResponse{}, err + } + + var response proto.GitHubOAuthResponse + err = json.Unmarshal(body, &response) + if err != nil { + return proto.GitHubOAuthResponse{}, err + } + + return response, nil +} + +// 获取用户信息 +func GetGitHubUserInfo(accessToken string) { + + url := "https://api.github.com/user" + headers := map[string]string{ + "Authorization": "Bearer " + accessToken, + } + err, data := DoGetRequest(url, headers) + +}