新增飞书登录驱动

This commit is contained in:
Liujian
2025-07-17 11:40:08 +08:00
parent 58d02bcf08
commit 76ae5287eb
5 changed files with 229 additions and 1 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ require (
// github.com/eolinker/eosc => ../../eolinker/eosc
//)
//replace github.com/eolinker/ap-account => ../../eolinker/ap-account
replace github.com/eolinker/ap-account => ../../eolinker/ap-account
//
//replace github.com/eolinker/go-common => ../../eolinker/go-common
+1
View File
@@ -4,6 +4,7 @@ package main
import (
_ "github.com/APIParkLab/APIPark/frontend"
_ "github.com/APIParkLab/APIPark/gateway/apinto"
_ "github.com/APIParkLab/APIPark/login_driver/feishu"
_ "github.com/APIParkLab/APIPark/plugins/core"
_ "github.com/APIParkLab/APIPark/plugins/openapi"
_ "github.com/APIParkLab/APIPark/plugins/permit"
+56
View File
@@ -0,0 +1,56 @@
package feishu
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
var (
client = http.Client{
Timeout: 10 * time.Second,
}
)
func SendRequest[T any](uri string, method string, header http.Header, query url.Values, body []byte) (*T, error) {
if uri == "" {
return nil, fmt.Errorf("invalid URL")
}
req, err := http.NewRequest(method, uri, bytes.NewReader(body))
if err != nil {
return nil, err
}
if query != nil {
req.URL.RawQuery = query.Encode()
}
if header != nil {
req.Header = header
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
result := new(T)
err = json.Unmarshal(respBody, result)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code error: %d, response: %s", resp.StatusCode, respBody)
}
return result, nil
}
+27
View File
@@ -0,0 +1,27 @@
package feishu
type UserTokenResponse struct {
Code int `json:"code"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type UserInfoResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data UserInfo `json:"data"`
}
type UserInfo struct {
Name string `json:"name"`
OpenID string `json:"open_id"`
UnionId string `json:"union_id"`
Email string `json:"email"`
Mobile string `json:"mobile"`
}
+144
View File
@@ -0,0 +1,144 @@
package feishu
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/eolinker/eosc/common/bean"
"github.com/eolinker/ap-account/service/user"
"github.com/eolinker/go-common/utils"
"github.com/google/uuid"
"gorm.io/gorm"
"github.com/eolinker/ap-account/service/account"
"github.com/eolinker/ap-account/auth_driver"
)
const (
name = "feishu"
title = "飞书"
getTokenUri = "https://open.feishu.cn/open-apis/authen/v2/oauth/token"
getUserInfoUri = "https://open.feishu.cn/open-apis/authen/v1/user_info"
)
var _ auth_driver.IDriver = (*Driver)(nil)
func init() {
d := &Driver{}
bean.Autowired(&d.accountService)
bean.Autowired(&d.userService)
auth_driver.Register(name, d)
}
type Driver struct {
accountService account.IAccountService `autowired:""`
userService user.IUserService `autowired:""`
}
func (d *Driver) FilterConfig(config map[string]string) {
delete(config, "client_secret")
}
func (d *Driver) Name() string {
return name
}
func (d *Driver) Title() string {
return title
}
func (d *Driver) ThirdLogin(ctx context.Context, args map[string]string) (string, error) {
code, ok := args["code"]
if !ok {
return "", fmt.Errorf("missing code parameter")
}
clientId, ok := args["client_id"]
if !ok {
return "", fmt.Errorf("missing client_id parameter")
}
clientSecret, ok := args["client_secret"]
if !ok {
return "", fmt.Errorf("missing client_secret parameter")
}
tokenResp, err := getUserToken(code, clientId, clientSecret)
if err != nil {
return "", err
}
userInfoResp, err := getUserInfo(tokenResp.TokenType, tokenResp.AccessToken)
if err != nil {
return "", err
}
userId := userInfoResp.Data.UnionId
username := userInfoResp.Data.Name
email := userInfoResp.Data.Email
mobile := userInfoResp.Data.Mobile
info, err := d.accountService.GetIdentifier(ctx, name, userId)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return "", err
}
uId := uuid.NewString()
err = d.accountService.Save(ctx, name, uId, userId, utils.Md5(fmt.Sprintf("%s%s", uId, userId)))
if err != nil {
return "", err
}
_, err = d.userService.Create(ctx, uId, username, email, mobile, "")
if err != nil {
return "", err
}
return userId, nil
}
_, err = d.userService.Update(ctx, info.Uid, &username, &email, &mobile)
if err != nil {
return "", err
}
return userId, nil
}
func getUserToken(code string, clientId string, clientSecret string) (*UserTokenResponse, error) {
headers := http.Header{}
headers.Set("Content-Type", "application/json")
body := url.Values{}
body.Set("grant_type", "authorization_code")
body.Set("code", code)
body.Set("client_id", clientId)
body.Set("client_secret", clientSecret)
resp, err := SendRequest[UserTokenResponse](getTokenUri, http.MethodPost, headers, nil, []byte(body.Encode()))
if err != nil {
return nil, fmt.Errorf("failed to get user token: %w", err)
}
if resp.Code != 0 {
return nil, fmt.Errorf("failed to get user token: %s", resp.ErrorDescription)
}
return resp, nil
}
func getUserInfo(tokenType string, token string) (*UserInfoResponse, error) {
headers := http.Header{}
headers.Set("Content-Type", "application/json")
switch tokenType {
case "Bearer":
headers.Set("Authorization", fmt.Sprintf("Bearer %s", token))
}
resp, err := SendRequest[UserInfoResponse](getUserInfoUri, http.MethodGet, headers, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get user info: %w", err)
}
if resp.Code != 0 {
return nil, fmt.Errorf("failed to get user info: %s", resp.Msg)
}
return resp, nil
}
func (d *Driver) Delete(ctx context.Context, ids ...string) error {
return d.accountService.OnRemoveUsers(ctx, ids...)
}