|
|
@@ -7,6 +7,7 @@ import (
|
|
|
"crypto/md5"
|
|
|
"crypto/rand"
|
|
|
"encoding/hex"
|
|
|
+ "encoding/json"
|
|
|
"fmt"
|
|
|
"io"
|
|
|
"math/big"
|
|
|
@@ -494,6 +495,7 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
Nickname string `json:"nickname"`
|
|
|
Avatar string `json:"avatar"`
|
|
|
InviteCode string `json:"inviteCode"`
|
|
|
+ Extra string `json:"extra"` // Zalo: code_verifier
|
|
|
}
|
|
|
var req Req
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
|
@@ -501,6 +503,19 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ // Zalo 特殊处理:需要用 code 换取 access_token
|
|
|
+ if req.Provider == "zalo" && req.Extra != "" {
|
|
|
+ zaloUser, err := s.getZaloUserInfo(req.OpenId, req.Extra)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("Zalo get user info error: %v\n", err)
|
|
|
+ ctx.Fail("zalo_auth_failed")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ req.OpenId = zaloUser.ID
|
|
|
+ req.Nickname = zaloUser.Name
|
|
|
+ req.Avatar = zaloUser.Picture
|
|
|
+ }
|
|
|
+
|
|
|
// 查找是否已绑定
|
|
|
var social entity.DtUserSocial
|
|
|
if err := db.Where("platform = ? AND account = ?", req.Provider, req.OpenId).First(&social).Error; err == nil {
|
|
|
@@ -630,3 +645,99 @@ func (s *Server) OAuthLogin(c *gin.Context) {
|
|
|
"isNew": true,
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+// ZaloUser Zalo用户信息
|
|
|
+type ZaloUser struct {
|
|
|
+ ID string `json:"id"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ Picture string `json:"picture"`
|
|
|
+}
|
|
|
+
|
|
|
+// getZaloUserInfo 使用code获取Zalo用户信息
|
|
|
+func (s *Server) getZaloUserInfo(code, codeVerifier string) (*ZaloUser, error) {
|
|
|
+ db := s.DB()
|
|
|
+
|
|
|
+ // 获取 Zalo App ID 和 Secret
|
|
|
+ var appIdConfig, secretConfig entity.DtConfig
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeyZaloAppId).First(&appIdConfig).Error; err != nil {
|
|
|
+ return nil, fmt.Errorf("zalo app_id not configured")
|
|
|
+ }
|
|
|
+ if err := db.Where("`key` = ?", entity.ConfigKeyZaloSecret).First(&secretConfig).Error; err != nil {
|
|
|
+ return nil, fmt.Errorf("zalo secret not configured")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 用 code 换取 access_token
|
|
|
+ tokenUrl := "https://oauth.zaloapp.com/v4/access_token"
|
|
|
+ data := url.Values{}
|
|
|
+ data.Set("app_id", appIdConfig.Value)
|
|
|
+ data.Set("code", code)
|
|
|
+ data.Set("code_verifier", codeVerifier)
|
|
|
+ data.Set("grant_type", "authorization_code")
|
|
|
+
|
|
|
+ req, _ := http.NewRequest("POST", tokenUrl, strings.NewReader(data.Encode()))
|
|
|
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
+ req.Header.Set("secret_key", secretConfig.Value)
|
|
|
+
|
|
|
+ client := &http.Client{Timeout: 10 * time.Second}
|
|
|
+ resp, err := client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("request token failed: %v", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, _ := io.ReadAll(resp.Body)
|
|
|
+ fmt.Printf("Zalo token response: %s\n", string(body))
|
|
|
+
|
|
|
+ var tokenResp struct {
|
|
|
+ AccessToken string `json:"access_token"`
|
|
|
+ ExpiresIn int `json:"expires_in"`
|
|
|
+ Error int `json:"error"`
|
|
|
+ Message string `json:"message"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal(body, &tokenResp); err != nil {
|
|
|
+ return nil, fmt.Errorf("parse token response failed: %v", err)
|
|
|
+ }
|
|
|
+ if tokenResp.Error != 0 || tokenResp.AccessToken == "" {
|
|
|
+ return nil, fmt.Errorf("get token failed: %s", tokenResp.Message)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 用 access_token 获取用户信息
|
|
|
+ userUrl := "https://graph.zalo.me/v2.0/me?fields=id,name,picture"
|
|
|
+ userReq, _ := http.NewRequest("GET", userUrl, nil)
|
|
|
+ userReq.Header.Set("access_token", tokenResp.AccessToken)
|
|
|
+
|
|
|
+ userResp, err := client.Do(userReq)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("request user info failed: %v", err)
|
|
|
+ }
|
|
|
+ defer userResp.Body.Close()
|
|
|
+
|
|
|
+ userBody, _ := io.ReadAll(userResp.Body)
|
|
|
+ fmt.Printf("Zalo user response: %s\n", string(userBody))
|
|
|
+
|
|
|
+ var userInfo struct {
|
|
|
+ ID string `json:"id"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ Picture struct {
|
|
|
+ Data struct {
|
|
|
+ URL string `json:"url"`
|
|
|
+ } `json:"data"`
|
|
|
+ } `json:"picture"`
|
|
|
+ Error struct {
|
|
|
+ Code int `json:"code"`
|
|
|
+ Message string `json:"message"`
|
|
|
+ } `json:"error"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal(userBody, &userInfo); err != nil {
|
|
|
+ return nil, fmt.Errorf("parse user info failed: %v", err)
|
|
|
+ }
|
|
|
+ if userInfo.Error.Code != 0 {
|
|
|
+ return nil, fmt.Errorf("get user info failed: %s", userInfo.Error.Message)
|
|
|
+ }
|
|
|
+
|
|
|
+ return &ZaloUser{
|
|
|
+ ID: userInfo.ID,
|
|
|
+ Name: userInfo.Name,
|
|
|
+ Picture: userInfo.Picture.Data.URL,
|
|
|
+ }, nil
|
|
|
+}
|