编写 GRPC 插件
以下内容来源于官方文档
编写插件
新建一个 feishu.go
,实现飞书登录逻辑。
go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/kun/any"
"github.com/yaoapp/kun/grpc"
)
// Feishu
type Feishu struct{ grpc.Plugin }
const appid = "cli_a107a"
const secret = "uRzTSIxWXuA3MqX"
// Exec 读取
func (feishu *Feishu) Exec(name string, args ...interface{}) (*grpc.Response, error) {
var v interface{}
switch name {
case "authurl":
// 在处理器,传入的是可变数量的参数,需要自行进行参数检查
if len(args) < 1 {
v = map[string]interface{}{"code": 400, "message": "缺少回调地址参数", "args": args}
break
}
if len(args) < 2 {
v = map[string]interface{}{"code": 400, "message": "缺少回调状态参数", "args": args}
break
}
url, ok := args[0].(string)
if !ok {
v = map[string]interface{}{"code": 400, "message": "回调地址数据类型错误", "args": args}
}
state, ok := args[1].(string)
if !ok {
v = map[string]interface{}{"code": 400, "message": "状态参数数据类型错误", "args": args}
}
v = AuthURL(url, state)
break
case "authback":
if len(args) < 1 {
v = map[string]interface{}{"code": 400, "message": "缺少预授权码参数", "args": args}
break
}
if len(args) < 2 {
v = map[string]interface{}{"code": 400, "message": "缺少回调状态参数", "args": args}
break
}
code, ok := args[0].(string)
if !ok {
v = map[string]interface{}{"code": 400, "message": "预授权码数据类型错误", "args": args}
}
state, ok := args[1].(string)
if !ok {
v = map[string]interface{}{"code": 400, "message": "状态参数数据类型错误", "args": args}
}
v = AuthBack(code, state)
break
default:
v = map[string]interface{}{"name": name, "args": args}
}
bytes, err := json.Marshal(v)
if err != nil {
return nil, err
}
return &grpc.Response{Bytes: bytes, Type: "map"}, nil
}
func main() {
var output io.Writer = os.Stdout
plugin := &Feishu{}
plugin.SetLogger(output, grpc.Trace)
grpc.Serve(plugin)
}
// AuthURL 登录地址
func AuthURL(redirect string, state string) map[string]interface{} {
api := fmt.Sprintf(
"https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=%s&app_id=%s&state=%s",
url.QueryEscape(redirect),
appid,
state,
)
return map[string]interface{}{"url": api}
}
// AuthBack 请求回调
func AuthBack(code string, state string) map[string]interface{} {
data, err := userToken(code)
if err != nil {
return map[string]interface{}{"code": 400, "message": err.Error()}
}
return data
}
func appToken() (string, error) {
body, err := jsoniter.Marshal(map[string]interface{}{
"app_id": appid,
"app_secret": secret,
})
if err != nil {
return "", err
}
url := "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := (&http.Client{}).Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body) // response body is []byte
if err != nil {
return "", err
}
res := map[string]interface{}{}
err = jsoniter.Unmarshal(body, &res)
if err != nil {
return "", err
}
if res["msg"] != "ok" {
return "", fmt.Errorf("tenant_access_token 接口返回失败 %s", res["msg"])
}
return res["tenant_access_token"].(string), nil
}
// Post 发起请求
func userToken(code string) (map[string]interface{}, error) {
content, err := jsoniter.Marshal(map[string]interface{}{
"grant_type": "authorization_code",
"code": code,
})
if err != nil {
return nil, err
}
url := "https://open.feishu.cn/open-apis/authen/v1/access_token"
req, err := http.NewRequest("POST", url, bytes.NewBuffer(content))
if err != nil {
return nil, err
}
token, err := appToken()
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := (&http.Client{}).Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) // response body is []byte
res := map[string]interface{}{}
err = jsoniter.Unmarshal(body, &res)
if err != nil {
return nil, err
}
if res["msg"] != "success" {
return nil, fmt.Errorf("access_token 接口返回失败 %s", res["msg"])
}
return any.Of(res["data"]).MapStr(), nil
}
编译制品
bash
go build -o ./feishu.so .
chmod +x ./feishu.so
使用插件
将插件制品 feishu.so
复制到应用 plugins
目录
运行调试
bash
yao run plugins.feishu.AuthURL "https://127.0.0.1:5099/xiang/login/user?from=feishu" "login"
在 scripts 中使用插件
js
Process('plugins.feishu.AuthURL', redirect, state);