Pārlūkot izejas kodu

zalo 一键登录

urbanu 1 mēnesi atpakaļ
vecāks
revīzija
d26b3dd99c
2 mainītis faili ar 112 papildinājumiem un 0 dzēšanām
  1. 111 0
      apis/daytask/auth.go
  2. 1 0
      commons/model/entity/dt_config.go

+ 111 - 0
apis/daytask/auth.go

@@ -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
+}

+ 1 - 0
commons/model/entity/dt_config.go

@@ -39,5 +39,6 @@ const (
 	ConfigKeyKefuPhone         = "kefu_phone"       // 客服电话
 	ConfigKeyGoogleClientId    = "google_client_id"    // Google OAuth Client ID
 	ConfigKeyZaloAppId         = "zalo_app_id"        // Zalo App ID
+	ConfigKeyZaloSecret        = "zalo_secret"        // Zalo Secret Key
 	ConfigKeyTelegramBotName   = "telegram_bot_name"  // Telegram Bot 用户名
 )