urbanu 1 месяц назад
Родитель
Сommit
14ff659736
3 измененных файлов с 127 добавлено и 0 удалено
  1. 119 0
      apis/daytask/auth.go
  2. 6 0
      apis/daytask/home.go
  3. 2 0
      commons/model/entity/dt_config.go

+ 119 - 0
apis/daytask/auth.go

@@ -671,6 +671,19 @@ func (s *Server) OAuthLogin(c *gin.Context) {
 		return
 	}
 
+	// TikTok 特殊处理:需要用 code 换取 access_token 和用户信息
+	if req.Provider == "tiktok" && req.Extra != "" {
+		tiktokUser, err := s.getTiktokUserInfo(req.OpenId, req.Extra)
+		if err != nil {
+			fmt.Printf("TikTok get user info error: %v\n", err)
+			ctx.Fail("tiktok_auth_failed")
+			return
+		}
+		req.OpenId = tiktokUser.ID
+		req.Nickname = tiktokUser.Name
+		req.Avatar = tiktokUser.Picture
+	}
+
 	// Zalo 特殊处理:需要用 code 换取 access_token
 	if req.Provider == "zalo" && req.Extra != "" {
 		zaloUser, err := s.getZaloUserInfo(req.OpenId, req.Extra)
@@ -919,3 +932,109 @@ func (s *Server) getZaloUserInfo(code, codeVerifier string) (*ZaloUser, error) {
 		Picture: userInfo.Picture.Data.URL,
 	}, nil
 }
+
+// TiktokUser TikTok用户信息
+type TiktokUser struct {
+	ID      string `json:"id"`
+	Name    string `json:"name"`
+	Picture string `json:"picture"`
+}
+
+// getTiktokUserInfo 使用code获取TikTok用户信息
+func (s *Server) getTiktokUserInfo(code, redirectUri string) (*TiktokUser, error) {
+	db := s.DB()
+
+	// 获取 TikTok Client Key 和 Secret
+	var clientKeyConfig, clientSecretConfig entity.DtConfig
+	if err := db.Where("`key` = ?", entity.ConfigKeyTiktokClientKey).First(&clientKeyConfig).Error; err != nil {
+		return nil, fmt.Errorf("tiktok client_key not configured")
+	}
+	if err := db.Where("`key` = ?", entity.ConfigKeyTiktokClientSecret).First(&clientSecretConfig).Error; err != nil {
+		return nil, fmt.Errorf("tiktok client_secret not configured")
+	}
+
+	// 1. 用 code 换取 access_token
+	tokenUrl := "https://open.tiktokapis.com/v2/oauth/token/"
+	data := url.Values{}
+	data.Set("client_key", clientKeyConfig.Value)
+	data.Set("client_secret", clientSecretConfig.Value)
+	data.Set("code", code)
+	data.Set("grant_type", "authorization_code")
+	data.Set("redirect_uri", redirectUri)
+
+	req, _ := http.NewRequest("POST", tokenUrl, strings.NewReader(data.Encode()))
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	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("TikTok token response: %s\n", string(body))
+
+	var tokenResp struct {
+		AccessToken string `json:"access_token"`
+		OpenId      string `json:"open_id"`
+		ExpiresIn   int    `json:"expires_in"`
+		TokenType   string `json:"token_type"`
+		Error       string `json:"error"`
+		ErrorDesc   string `json:"error_description"`
+	}
+	if err := json.Unmarshal(body, &tokenResp); err != nil {
+		return nil, fmt.Errorf("parse token response failed: %v", err)
+	}
+	if tokenResp.Error != "" || tokenResp.AccessToken == "" {
+		return nil, fmt.Errorf("get token failed: %s - %s", tokenResp.Error, tokenResp.ErrorDesc)
+	}
+
+	// 2. 用 access_token 获取用户信息
+	userUrl := "https://open.tiktokapis.com/v2/user/info/?fields=open_id,display_name,avatar_url"
+	userReq, _ := http.NewRequest("GET", userUrl, nil)
+	userReq.Header.Set("Authorization", "Bearer "+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("TikTok user response: %s\n", string(userBody))
+
+	var userInfoResp struct {
+		Data struct {
+			User struct {
+				OpenId      string `json:"open_id"`
+				DisplayName string `json:"display_name"`
+				AvatarUrl   string `json:"avatar_url"`
+			} `json:"user"`
+		} `json:"data"`
+		Error struct {
+			Code    string `json:"code"`
+			Message string `json:"message"`
+		} `json:"error"`
+	}
+	if err := json.Unmarshal(userBody, &userInfoResp); err != nil {
+		return nil, fmt.Errorf("parse user info failed: %v", err)
+	}
+	if userInfoResp.Error.Code != "" && userInfoResp.Error.Code != "ok" {
+		return nil, fmt.Errorf("get user info failed: [%s] %s", userInfoResp.Error.Code, userInfoResp.Error.Message)
+	}
+
+	openId := userInfoResp.Data.User.OpenId
+	if openId == "" {
+		openId = tokenResp.OpenId
+	}
+	if openId == "" {
+		return nil, fmt.Errorf("get user info failed: empty open_id")
+	}
+
+	return &TiktokUser{
+		ID:      openId,
+		Name:    userInfoResp.Data.User.DisplayName,
+		Picture: userInfoResp.Data.User.AvatarUrl,
+	}, nil
+}

+ 6 - 0
apis/daytask/home.go

@@ -336,6 +336,12 @@ func (s *Server) OAuthConfig(c *gin.Context) {
 		result["telegramBotId"] = telegramIdConfig.Value
 	}
 
+	// 获取 TikTok Client Key
+	var tiktokConfig entity.DtConfig
+	if err := db.Where("`key` = ?", entity.ConfigKeyTiktokClientKey).First(&tiktokConfig).Error; err == nil {
+		result["tiktokClientKey"] = tiktokConfig.Value
+	}
+
 	ctx.OK(result)
 }
 

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

@@ -47,4 +47,6 @@ const (
 	ConfigKeySmtpUser          = "smtp_user"          // SMTP用户名(发件邮箱)
 	ConfigKeySmtpPass          = "smtp_pass"          // SMTP密码/授权码
 	ConfigKeySmtpFrom          = "smtp_from"          // 发件人名称
+	ConfigKeyTiktokClientKey    = "tiktok_client_key"    // TikTok Client Key
+	ConfigKeyTiktokClientSecret = "tiktok_client_secret" // TikTok Client Secret
 )