to #feat 初始化提交
This commit is contained in:
parent
16f426cfa2
commit
ae3f631591
|
|
@ -0,0 +1 @@
|
||||||
|
/.idea
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Makefile for nbartookt
|
||||||
|
|
||||||
|
# Go parameters
|
||||||
|
# GOCMD=GOOS=linux GOARCH=amd64 go
|
||||||
|
# GOBUILD=CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo
|
||||||
|
ifndef GOCMD
|
||||||
|
GOCMD=go
|
||||||
|
endif
|
||||||
|
ifndef GOBUILD
|
||||||
|
GOBUILD=$(GOCMD) build
|
||||||
|
endif
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
|
GOINSTALL=$(GOCMD) install
|
||||||
|
GOTEST=$(GOCMD) test
|
||||||
|
GODEP=$(GOTEST) -i
|
||||||
|
GOFMT=gofmt -w
|
||||||
|
BINARY_NAME=wukong-eos
|
||||||
|
|
||||||
|
all: test
|
||||||
|
test:
|
||||||
|
$(GOTEST) -v -cover=true -coverprofile=./sdk.cover ./...
|
||||||
|
go tool cover -html=./sdk.cover -o ./sdk.html
|
||||||
|
fmt:
|
||||||
|
find ./ -name "*.go" | xargs gofmt -w
|
||||||
|
|
@ -0,0 +1,345 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/handler"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/utils"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:23
|
||||||
|
*/
|
||||||
|
|
||||||
|
type StreamClient struct {
|
||||||
|
AppCredential *AppCredentialConfig
|
||||||
|
UserAgent *UserAgentConfig
|
||||||
|
AutoReconnect bool
|
||||||
|
|
||||||
|
subscriptions map[string]map[string]handler.IFrameHandler
|
||||||
|
|
||||||
|
conn *websocket.Conn
|
||||||
|
sessionId string
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStreamClient(options ...ClientOption) *StreamClient {
|
||||||
|
cli := &StreamClient{}
|
||||||
|
|
||||||
|
defaultOptions := []ClientOption{
|
||||||
|
WithSubscription(utils.SubscriptionTypeKSystem, "disconnect", cli.OnDisconnect),
|
||||||
|
WithSubscription(utils.SubscriptionTypeKSystem, "ping", cli.OnPing),
|
||||||
|
WithUserAgent(NewDingtalkGoSDKUserAgent()),
|
||||||
|
WithAutoReconnect(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range defaultOptions {
|
||||||
|
option(cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
if option == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
option(cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cli
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) Start(ctx context.Context) error {
|
||||||
|
if cli.conn != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.mutex.Lock()
|
||||||
|
defer cli.mutex.Unlock()
|
||||||
|
|
||||||
|
if cli.conn != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint, err := cli.GetConnectionEndpoint(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wssUrl := fmt.Sprintf("%s?ticket=%s", endpoint.Endpoint, endpoint.Ticket)
|
||||||
|
|
||||||
|
header := make(http.Header)
|
||||||
|
|
||||||
|
conn, resp, err := websocket.DefaultDialer.Dial(wssUrl, header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建连失败
|
||||||
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
|
return utils.ErrorFromHttpResponseBody(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.conn = conn
|
||||||
|
cli.sessionId = endpoint.Ticket
|
||||||
|
|
||||||
|
logger.GetLogger().Infof("connect success, sessionId=[%s]", cli.sessionId)
|
||||||
|
|
||||||
|
go cli.processLoop()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) processLoop() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logger.GetLogger().Errorf("connection process panic due to unknown reason, error=[%s]", err)
|
||||||
|
}
|
||||||
|
if cli.AutoReconnect {
|
||||||
|
go cli.reconnect()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if cli.conn == nil {
|
||||||
|
logger.GetLogger().Errorf("connection process connect nil, maybe disconnected.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
messageType, message, err := cli.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
logger.GetLogger().Errorf("connection process read message error: messageType=[%d] message=[%s] error=[%s]", messageType, string(message), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.GetLogger().Debugf("ReadRawMessage : messageType=[%d] message=[%s]", messageType, string(message))
|
||||||
|
|
||||||
|
go cli.processDataFrame(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) processDataFrame(rawData []byte) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logger.GetLogger().Errorf("connection processDataFrame panic, error=[%s]", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
dataFrame, err := payload.DecodeDataFrame(rawData)
|
||||||
|
if err != nil {
|
||||||
|
logger.GetLogger().Errorf("connection process decode data frame error: length=[%d] error=[%s]", len(rawData), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataFrame == nil || dataFrame.Headers == nil {
|
||||||
|
logger.GetLogger().Errorf("connection processDataFrame dataFrame nil.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frameHandler, err := cli.GetHandler(dataFrame.Type, dataFrame.GetTopic())
|
||||||
|
if err != nil {
|
||||||
|
logger.GetLogger().Errorf("connection processDataFrame unregistered handler: type=[%s] topic=[%s]", dataFrame.Type, dataFrame.GetTopic())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dataAck, err := frameHandler(context.Background(), dataFrame)
|
||||||
|
|
||||||
|
if dataAck == nil && err != nil {
|
||||||
|
dataAck = payload.NewErrorDataFrameResponse(dataFrame.GetMessageId(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataAck == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errSend := cli.SendDataFrameResponse(context.Background(), dataAck)
|
||||||
|
logger.GetLogger().Debugf("SendFrameAck dataAck=[%v", dataAck)
|
||||||
|
|
||||||
|
if errSend != nil {
|
||||||
|
logger.GetLogger().Errorf("connection processDataFrame send response error: error=[%s]", errSend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) Close() {
|
||||||
|
if cli.conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.mutex.Lock()
|
||||||
|
defer cli.mutex.Unlock()
|
||||||
|
|
||||||
|
if cli.conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cli.conn.Close(); err != nil {
|
||||||
|
logger.GetLogger().Errorf("StreamClient close. error=[%s]", err)
|
||||||
|
}
|
||||||
|
cli.conn = nil
|
||||||
|
cli.sessionId = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) reconnect() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logger.GetLogger().Errorf("reconect panic due to unknown reason. error=[%s]", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cli.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := cli.Start(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
logger.GetLogger().Errorf("StreamClient reconnect error. error=[%s]", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
} else {
|
||||||
|
logger.GetLogger().Infof("StreamClient reconnect success")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) GetHandler(stype, stopic string) (handler.IFrameHandler, error) {
|
||||||
|
subs := cli.subscriptions[stype]
|
||||||
|
if subs == nil || subs[stopic] == nil {
|
||||||
|
return nil, errors.New("HandlerNotRegistedForTypeTopic_" + stype + "_" + stopic)
|
||||||
|
}
|
||||||
|
|
||||||
|
return subs[stopic], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) CheckConfigValid() error {
|
||||||
|
if err := cli.AppCredential.Valid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cli.UserAgent.Valid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.subscriptions == nil {
|
||||||
|
return errors.New("subscriptionsNil")
|
||||||
|
}
|
||||||
|
|
||||||
|
for ttype, subs := range cli.subscriptions {
|
||||||
|
if _, ok := utils.SubscriptionTypeSet[ttype]; !ok {
|
||||||
|
return errors.New("UnKnownSubscriptionType_" + ttype)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(subs) <= 0 {
|
||||||
|
return errors.New("NoHandlersRegistedForType_" + ttype)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ttopic, h := range subs {
|
||||||
|
if h == nil {
|
||||||
|
return errors.New("HandlerNilForTypeTopic_" + ttype + "_" + ttopic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) GetConnectionEndpoint(ctx context.Context) (*payload.ConnectionEndpointResponse, error) {
|
||||||
|
if err := cli.CheckConfigValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestModel := payload.ConnectionEndpointRequest{
|
||||||
|
ClientId: cli.AppCredential.ClientId,
|
||||||
|
ClientSecret: cli.AppCredential.ClientSecret,
|
||||||
|
UserAgent: cli.UserAgent.UserAgent,
|
||||||
|
Subscriptions: make([]*payload.SubscriptionModel, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for ttype, subs := range cli.subscriptions {
|
||||||
|
for ttopic, _ := range subs {
|
||||||
|
requestModel.Subscriptions = append(requestModel.Subscriptions, &payload.SubscriptionModel{
|
||||||
|
Type: ttype,
|
||||||
|
Topic: ttopic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJsonBody, _ := json.Marshal(requestModel)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, utils.GetConnectionEndpointAPIUrl, bytes.NewReader(requestJsonBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
Timeout: 5 * time.Second, //设置超时,包含connection时间、任意重定向时间、读取response body时间
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, utils.ErrorFromHttpResponseBody(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
responseJsonBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := &payload.ConnectionEndpointResponse{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(responseJsonBody, endpoint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := endpoint.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) OnDisconnect(ctx context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error) {
|
||||||
|
logger.GetLogger().Debugf("StreamClient.OnDisconnect")
|
||||||
|
|
||||||
|
cli.Close()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *StreamClient) OnPing(ctx context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error) {
|
||||||
|
dfPong := payload.NewDataFrameAckPong(df.GetMessageId())
|
||||||
|
dfPong.Data = df.Data
|
||||||
|
|
||||||
|
return dfPong, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回正常数据包
|
||||||
|
func (cli *StreamClient) SendDataFrameResponse(ctx context.Context, resp *payload.DataFrameResponse) error {
|
||||||
|
if resp == nil {
|
||||||
|
return errors.New("SendDataFrameResponseError_ResponseNil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.conn == nil {
|
||||||
|
logger.GetLogger().Errorf("SendDataFrameResponse error, conn nil, maybe disconnected.")
|
||||||
|
return errors.New("disconnected")
|
||||||
|
}
|
||||||
|
return cli.conn.WriteJSON(resp)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:23
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestNewDingtalkOpenStreamClient(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_Start(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_processDataFrame(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_Close(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_reconnect(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_GetHandler(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_CheckConfigValid(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_GetConnectionEndpoint(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_OnDisconnect(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_OnPing(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_SendDataFrameResponse(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkOpenStreamClient_SendErrorResponse(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:50
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 应用秘钥信息
|
||||||
|
type AppCredentialConfig struct {
|
||||||
|
ClientId string `json:"clientKey" yaml:"clientKey"` //自建应用appKey; 三方应用suiteKey
|
||||||
|
ClientSecret string `json:"clientSecret" yaml:"clientSecret"` //自建应用appSecret; 三方应用suiteSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAppCredentialConfig(clientId, clientSecret string) *AppCredentialConfig {
|
||||||
|
return &AppCredentialConfig{
|
||||||
|
ClientId: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AppCredentialConfig) Valid() error {
|
||||||
|
if c == nil {
|
||||||
|
return errors.New("AppCredentialConfigNil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ClientId == "" || c.ClientSecret == "" {
|
||||||
|
return errors.New("AppCredentialConfigEmpty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UA信息
|
||||||
|
type UserAgentConfig struct {
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDingtalkGoSDKUserAgent() *UserAgentConfig {
|
||||||
|
return &UserAgentConfig{
|
||||||
|
UserAgent: "dingtalk-sdk-go/0.1.0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserAgentConfig) Valid() error {
|
||||||
|
if c == nil {
|
||||||
|
return errors.New("UserAgentConfigNil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UserAgent == "" {
|
||||||
|
return errors.New("UserAgentConfigEmpty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:50
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestAppCredentialConfig_Valid(t *testing.T) {
|
||||||
|
conf := NewAppCredentialConfig("clientId", "clientSecret")
|
||||||
|
assert.Nil(t, conf.Valid())
|
||||||
|
|
||||||
|
conf.ClientId = ""
|
||||||
|
assert.NotNil(t, conf.Valid())
|
||||||
|
|
||||||
|
conf = nil
|
||||||
|
assert.NotNil(t, conf.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDingtalkGoSDKUserAgent_Valid(t *testing.T) {
|
||||||
|
conf := NewDingtalkGoSDKUserAgent()
|
||||||
|
assert.Nil(t, conf.Valid())
|
||||||
|
|
||||||
|
conf.UserAgent = ""
|
||||||
|
assert.NotNil(t, conf.Valid())
|
||||||
|
|
||||||
|
conf = nil
|
||||||
|
assert.NotNil(t, conf.Valid())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "github.com/open-dingtalk/dingtalk-stream-sdk-go/handler"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:48
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ClientOption func(*StreamClient)
|
||||||
|
|
||||||
|
func WithAutoReconnect(autoReconnect bool) ClientOption {
|
||||||
|
return func(c *StreamClient) {
|
||||||
|
c.AutoReconnect = autoReconnect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAppCredential(cred *AppCredentialConfig) ClientOption {
|
||||||
|
return func(c *StreamClient) {
|
||||||
|
c.AppCredential = cred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSubscription(stype, stopic string, frameHandler handler.IFrameHandler) ClientOption {
|
||||||
|
return func(c *StreamClient) {
|
||||||
|
if c.subscriptions == nil {
|
||||||
|
c.subscriptions = make(map[string]map[string]handler.IFrameHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.subscriptions[stype]; !ok {
|
||||||
|
c.subscriptions[stype] = make(map[string]handler.IFrameHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.subscriptions[stype][stopic] = frameHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithUserAgent(ua *UserAgentConfig) ClientOption {
|
||||||
|
return func(c *StreamClient) {
|
||||||
|
if ua.Valid() != nil {
|
||||||
|
ua = NewDingtalkGoSDKUserAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UserAgent = ua
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:48
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestWithAppCredential(t *testing.T) {
|
||||||
|
op := WithAppCredential(NewAppCredentialConfig("clientId", "clientSecret"))
|
||||||
|
|
||||||
|
c := NewStreamClient(op)
|
||||||
|
assert.Equal(t, "clientId", c.AppCredential.ClientId)
|
||||||
|
assert.Equal(t, "clientSecret", c.AppCredential.ClientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithSubscription(t *testing.T) {
|
||||||
|
op := WithSubscription("stype", "stopic", func(ctx context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
c := NewStreamClient(op)
|
||||||
|
h, err := c.GetHandler("stype", "stopic")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithUserAgent(t *testing.T) {
|
||||||
|
op := WithUserAgent(NewDingtalkGoSDKUserAgent())
|
||||||
|
c := NewStreamClient(op)
|
||||||
|
assert.NotNil(t, c.UserAgent)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/26 17:15
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
DataFrameHeaderKEventId = "eventId"
|
||||||
|
DataFrameHeaderKEventBornTime = "eventBornTime"
|
||||||
|
DataFrameHeaderKEventCorpId = "eventCorpId"
|
||||||
|
DataFrameHeaderKEventType = "eventType"
|
||||||
|
DataFrameHeaderKEventUnifiedAppId = "eventUnifiedAppId"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventHeader struct {
|
||||||
|
EventId string `json:"eventId"`
|
||||||
|
EventBornTime int64 `json:"eventBornTime"`
|
||||||
|
EventCorpId string `json:"eventCorpId"`
|
||||||
|
EventType string `json:"eventType"`
|
||||||
|
EventUnifiedAppId string `json:"eventUnifiedAppId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventHeaderFromDataFrame(df *payload.DataFrame) *EventHeader {
|
||||||
|
if df == nil {
|
||||||
|
return &EventHeader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventHeader := &EventHeader{
|
||||||
|
EventId: df.GetHeader(DataFrameHeaderKEventId),
|
||||||
|
EventBornTime: 0,
|
||||||
|
EventCorpId: df.GetHeader(DataFrameHeaderKEventCorpId),
|
||||||
|
EventType: df.GetHeader(DataFrameHeaderKEventType),
|
||||||
|
EventUnifiedAppId: df.GetHeader(DataFrameHeaderKEventUnifiedAppId),
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts, err := strconv.ParseInt(df.GetHeader(DataFrameHeaderKEventBornTime), 10, 64); err == nil {
|
||||||
|
eventHeader.EventBornTime = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventProcessStatusType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
EventProcessStatusKSuccess EventProcessStatusType = "SUCCESS"
|
||||||
|
EventProcessStatusKLater EventProcessStatusType = "LATER"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventProcessResult struct {
|
||||||
|
Status EventProcessStatusType `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventProcessResultSuccess() *EventProcessResult {
|
||||||
|
return &EventProcessResult{
|
||||||
|
Status: EventProcessStatusKSuccess,
|
||||||
|
Message: "success",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventProcessResultLater() *EventProcessResult {
|
||||||
|
return &EventProcessResult{
|
||||||
|
Status: EventProcessStatusKLater,
|
||||||
|
Message: "later",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/27 09:25
|
||||||
|
*/
|
||||||
|
|
||||||
|
type IEventHandler func(c context.Context, header *EventHeader, rawData []byte) (EventProcessStatusType, error)
|
||||||
|
|
||||||
|
func EventHandlerDoNothing(c context.Context, header *EventHeader, rawData []byte) (EventProcessStatusType, error) {
|
||||||
|
logger.GetLogger().Debugf("EventHandlerDoNothing header=[%s], rawData=[%s]",
|
||||||
|
header, rawData)
|
||||||
|
|
||||||
|
return EventProcessStatusKSuccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventHandlerSaveToRDS(c context.Context, header *EventHeader, rawData []byte) (EventProcessStatusType, error) {
|
||||||
|
// TODO save data to rds here
|
||||||
|
|
||||||
|
return EventProcessStatusKSuccess, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/27 09:25
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestEventHandlerDoNothing(t *testing.T) {
|
||||||
|
status, err := EventHandlerDoNothing(context.Background(), nil, []byte(""))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, EventProcessStatusKSuccess, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventHandlerSaveToRDS(t *testing.T) {
|
||||||
|
status, err := EventHandlerSaveToRDS(context.Background(), nil, []byte(""))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, EventProcessStatusKSuccess, status)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/utils"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/26 17:15
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestNewEventHeaderFromDataFrame(t *testing.T) {
|
||||||
|
assert.NotNil(t, NewEventHeaderFromDataFrame(nil))
|
||||||
|
|
||||||
|
df := &payload.DataFrame{
|
||||||
|
SpecVersion: "version",
|
||||||
|
Type: utils.SubscriptionTypeKEvent,
|
||||||
|
Time: 12345678,
|
||||||
|
Headers: payload.DataFrameHeader{
|
||||||
|
DataFrameHeaderKEventId: "eventId",
|
||||||
|
DataFrameHeaderKEventBornTime: "1234567890",
|
||||||
|
DataFrameHeaderKEventCorpId: "eventCorpId",
|
||||||
|
DataFrameHeaderKEventType: "eventType",
|
||||||
|
DataFrameHeaderKEventUnifiedAppId: "eventUnifiedAppId",
|
||||||
|
},
|
||||||
|
Data: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
eh := NewEventHeaderFromDataFrame(df)
|
||||||
|
assert.NotNil(t, eh)
|
||||||
|
assert.Equal(t, "eventId", eh.EventId)
|
||||||
|
assert.Equal(t, int64(1234567890), eh.EventBornTime)
|
||||||
|
assert.Equal(t, "eventCorpId", eh.EventCorpId)
|
||||||
|
assert.Equal(t, "eventType", eh.EventType)
|
||||||
|
assert.Equal(t, "eventUnifiedAppId", eh.EventUnifiedAppId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEventProcessResultSuccess(t *testing.T) {
|
||||||
|
assert.NotNil(t, NewEventProcessResultSuccess())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEventProcessResultLater(t *testing.T) {
|
||||||
|
assert.NotNil(t, NewEventProcessResultLater())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/26 17:15
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DefaultEventFrameHandler struct {
|
||||||
|
defaultHandler IEventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultEventFrameHandler(defaultHandler IEventHandler) *DefaultEventFrameHandler {
|
||||||
|
return &DefaultEventFrameHandler{
|
||||||
|
defaultHandler: defaultHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DefaultEventFrameHandler) OnEventReceived(ctx context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error) {
|
||||||
|
eventHeader := NewEventHeaderFromDataFrame(df)
|
||||||
|
|
||||||
|
if h.defaultHandler == nil {
|
||||||
|
logger.GetLogger().Warningf("No event handler found, drop this event. eventType=[%s], eventId=[%s], eventCorpId=[%s]",
|
||||||
|
eventHeader.EventType, eventHeader.EventId, eventHeader.EventCorpId)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := h.defaultHandler(ctx, eventHeader, []byte(df.Data))
|
||||||
|
if err != nil {
|
||||||
|
logger.GetLogger().Errorf("Event handler process error. eventType=[%s], eventId=[%s], eventCorpId=[%s] err=[%s]",
|
||||||
|
eventHeader.EventType, eventHeader.EventId, eventHeader.EventCorpId, err)
|
||||||
|
|
||||||
|
ret = EventProcessStatusKLater
|
||||||
|
}
|
||||||
|
|
||||||
|
result := NewEventProcessResultSuccess()
|
||||||
|
code := payload.DataFrameResponseStatusCodeKOK
|
||||||
|
if ret != EventProcessStatusKSuccess {
|
||||||
|
code = payload.DataFrameResponseStatusCodeKInternalError
|
||||||
|
result = NewEventProcessResultLater()
|
||||||
|
}
|
||||||
|
|
||||||
|
resultStr, _ := json.Marshal(result)
|
||||||
|
|
||||||
|
frameResp := &payload.DataFrameResponse{
|
||||||
|
Code: code,
|
||||||
|
Headers: payload.DataFrameHeader{
|
||||||
|
payload.DataFrameHeaderKContentType: payload.DataFrameContentTypeKJson,
|
||||||
|
payload.DataFrameHeaderKMessageId: df.GetMessageId(),
|
||||||
|
},
|
||||||
|
Message: "ok",
|
||||||
|
Data: string(resultStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameResp, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/26 17:15
|
||||||
|
*/
|
||||||
|
|
||||||
|
func EventHandlerSuccess(c context.Context, header *EventHeader, rawData []byte) (EventProcessStatusType, error) {
|
||||||
|
return EventProcessStatusKSuccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventHandlerLater(c context.Context, header *EventHeader, rawData []byte) (EventProcessStatusType, error) {
|
||||||
|
return EventProcessStatusKLater, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventHandlerLaterError(c context.Context, header *EventHeader, rawData []byte) (EventProcessStatusType, error) {
|
||||||
|
return EventProcessStatusKLater, errors.New("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultEventFrameHandler_OnEventReceived(t *testing.T) {
|
||||||
|
defh := NewDefaultEventFrameHandler(nil)
|
||||||
|
ret, err := defh.OnEventReceived(context.Background(), nil)
|
||||||
|
assert.Nil(t, ret)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
df := &payload.DataFrame{}
|
||||||
|
|
||||||
|
defh = NewDefaultEventFrameHandler(EventHandlerSuccess)
|
||||||
|
ret, err = defh.OnEventReceived(context.Background(), df)
|
||||||
|
assert.Equal(t, payload.DataFrameResponseStatusCodeKOK, ret.Code)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
defh = NewDefaultEventFrameHandler(EventHandlerLater)
|
||||||
|
ret, err = defh.OnEventReceived(context.Background(), df)
|
||||||
|
assert.Equal(t, payload.DataFrameResponseStatusCodeKInternalError, ret.Code)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
defh = NewDefaultEventFrameHandler(EventHandlerLaterError)
|
||||||
|
ret, err = defh.OnEventReceived(context.Background(), df)
|
||||||
|
assert.Equal(t, payload.DataFrameResponseStatusCodeKInternalError, ret.Code)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/client"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 18:30
|
||||||
|
*/
|
||||||
|
|
||||||
|
func OnBotCallback(ctx context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error) {
|
||||||
|
frameResp := &payload.DataFrameResponse{
|
||||||
|
Code: 200,
|
||||||
|
Headers: payload.DataFrameHeader{
|
||||||
|
payload.DataFrameHeaderKContentType: payload.DataFrameContentTypeKJson,
|
||||||
|
payload.DataFrameHeaderKMessageId: df.GetMessageId(),
|
||||||
|
},
|
||||||
|
Message: "ok",
|
||||||
|
Data: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunBotListener() {
|
||||||
|
logger.SetLogger(logger.NewStdTestLogger())
|
||||||
|
|
||||||
|
cli := client.NewStreamClient(
|
||||||
|
client.WithAppCredential(client.NewAppCredentialConfig("your-client-id", "your-client-secret")),
|
||||||
|
client.WithUserAgent(client.NewDingtalkGoSDKUserAgent()),
|
||||||
|
client.WithSubscription(utils.SubscriptionTypeKCallback, payload.BotMessageCallbackTopic, OnBotCallback),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := cli.Start(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/client"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/event"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 18:30
|
||||||
|
*/
|
||||||
|
|
||||||
|
func RunEventListener() {
|
||||||
|
logger.SetLogger(logger.NewStdTestLogger())
|
||||||
|
|
||||||
|
eventHandler := event.NewDefaultEventFrameHandler(event.EventHandlerDoNothing)
|
||||||
|
|
||||||
|
cli := client.NewStreamClient(
|
||||||
|
client.WithAppCredential(client.NewAppCredentialConfig("your-client-id", "your-client-secret")),
|
||||||
|
client.WithUserAgent(client.NewDingtalkGoSDKUserAgent()),
|
||||||
|
client.WithSubscription(utils.SubscriptionTypeKEvent, "*", eventHandler.OnEventReceived),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := cli.Start(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 18:30
|
||||||
|
*/
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
RunBotListener()
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
module github.com/open-dingtalk/dingtalk-stream-sdk-go
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/stretchr/testify v1.8.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:27
|
||||||
|
*/
|
||||||
|
|
||||||
|
type IFrameHandler func(c context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:27
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestOnEventReceived(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:30
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ILogger interface {
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
|
||||||
|
Warningf(format string, args ...interface{})
|
||||||
|
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
sdkLogger ILogger
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetLogger(customLogger ILogger) {
|
||||||
|
sdkLogger = customLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogger() ILogger {
|
||||||
|
if sdkLogger == nil {
|
||||||
|
sdkLogger = &doNothingLogger{}
|
||||||
|
}
|
||||||
|
return sdkLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
type doNothingLogger struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *doNothingLogger) Debugf(format string, args ...interface{}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *doNothingLogger) Infof(format string, args ...interface{}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *doNothingLogger) Warningf(format string, args ...interface{}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *doNothingLogger) Errorf(format string, args ...interface{}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *doNothingLogger) Fatalf(format string, args ...interface{}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:30
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestSetGetSDKLogger(t *testing.T) {
|
||||||
|
assert.NotNil(t, GetLogger())
|
||||||
|
|
||||||
|
stdLogger := NewStdTestLogger()
|
||||||
|
SetLogger(stdLogger)
|
||||||
|
assert.Equal(t, stdLogger, GetLogger())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:32
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This logger is only for debug. Do not use it online.
|
||||||
|
type StdTestLogger struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdTestLogger() *StdTestLogger {
|
||||||
|
return &StdTestLogger{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StdTestLogger) Debugf(format string, args ...interface{}) {
|
||||||
|
fmt.Printf("%s [Debug] ", time.Now().String())
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StdTestLogger) Infof(format string, args ...interface{}) {
|
||||||
|
fmt.Printf("%s [INFO] ", time.Now().String())
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StdTestLogger) Warningf(format string, args ...interface{}) {
|
||||||
|
fmt.Printf("%s [WARNING] ", time.Now().String())
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StdTestLogger) Errorf(format string, args ...interface{}) {
|
||||||
|
fmt.Printf("%s [ERROR] ", time.Now().String())
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StdTestLogger) Fatalf(format string, args ...interface{}) {
|
||||||
|
fmt.Printf("%s [FATAL] ", time.Now().String())
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 14:32
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestStdLogger_Output(t *testing.T) {
|
||||||
|
stdLogger := NewStdTestLogger()
|
||||||
|
|
||||||
|
stdLogger.Debugf("logger level: %s", "debug")
|
||||||
|
stdLogger.Infof("logger level: %s", "info")
|
||||||
|
stdLogger.Warningf("logger level: %s", "warning")
|
||||||
|
stdLogger.Errorf("logger level: %s", "error")
|
||||||
|
stdLogger.Fatalf("logger level: %s", "fatal")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 18:22
|
||||||
|
*/
|
||||||
|
|
||||||
|
type SubscriptionModel struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Topic string `json:"topic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长连接接入点请求
|
||||||
|
type ConnectionEndpointRequest struct {
|
||||||
|
ClientId string `json:"clientId"` //自建应用appKey; 三方应用suiteKey
|
||||||
|
ClientSecret string `json:"clientSecret"` //自建应用appSecret; 三方应用suiteSecret
|
||||||
|
Subscriptions []*SubscriptionModel `json:"subscriptions"`
|
||||||
|
UserAgent string `json:"ua"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长连接接入点参数
|
||||||
|
type ConnectionEndpointResponse struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Ticket string `json:"ticket"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConnectionEndpointResponse) Valid() error {
|
||||||
|
if r == nil {
|
||||||
|
return errors.New("ConnectionEndpointResponseNil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Endpoint == "" || r.Ticket == "" {
|
||||||
|
return errors.New("ConnectionEndpointResponseContentEmpty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 18:22
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestConnectionEndpointResponse_Valid(t *testing.T) {
|
||||||
|
resp := &ConnectionEndpointResponse{
|
||||||
|
Endpoint: "ep",
|
||||||
|
Ticket: "ti",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, resp.Valid())
|
||||||
|
|
||||||
|
resp.Endpoint = ""
|
||||||
|
assert.NotNil(t, resp.Valid())
|
||||||
|
|
||||||
|
resp = nil
|
||||||
|
assert.NotNil(t, resp.Valid())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/31 09:57
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DataFrameHeader map[string]string
|
||||||
|
|
||||||
|
func (h DataFrameHeader) Get(key string) string {
|
||||||
|
return h[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h DataFrameHeader) Set(key, value string) {
|
||||||
|
h[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataFrame struct {
|
||||||
|
SpecVersion string `json:"specVersion"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Headers DataFrameHeader `json:"headers""`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFrame) Encode() []byte {
|
||||||
|
if df == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(df)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFrame) GetTopic() string {
|
||||||
|
if df == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return df.Headers.Get(DataFrameHeaderKTopic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFrame) GetMessageId() string {
|
||||||
|
if df == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return df.Headers.Get(DataFrameHeaderKMessageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFrame) GetTimestamp() int64 {
|
||||||
|
if df == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
strTs := df.Headers.Get(DataFrameHeaderKTime)
|
||||||
|
ts, err := strconv.ParseInt(strTs, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFrame) GetHeader(header string) string {
|
||||||
|
if df == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return df.Headers.Get(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeDataFrame(rawData []byte) (*DataFrame, error) {
|
||||||
|
df := &DataFrame{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(rawData, df)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return df, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataFrameResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Headers DataFrameHeader `json:"headers"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFrameResponse) Encode() []byte {
|
||||||
|
if df == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(df)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeDataFrameResponse(rawData []byte) (*DataFrameResponse, error) {
|
||||||
|
resp := &DataFrameResponse{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(rawData, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataFrameAckPong(messageId string) *DataFrameResponse {
|
||||||
|
return &DataFrameResponse{
|
||||||
|
Code: DataFrameResponseStatusCodeKOK,
|
||||||
|
Headers: DataFrameHeader{
|
||||||
|
DataFrameHeaderKContentType: DataFrameContentTypeKJson,
|
||||||
|
DataFrameHeaderKMessageId: messageId,
|
||||||
|
},
|
||||||
|
Message: "ok",
|
||||||
|
Data: "", //TODO data内容暂留空
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErrorDataFrameResponse(messageId string, err error) *DataFrameResponse {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DataFrameResponse{
|
||||||
|
Code: 400, //TODO errorcode 细化
|
||||||
|
Headers: DataFrameHeader{
|
||||||
|
DataFrameHeaderKContentType: DataFrameContentTypeKJson,
|
||||||
|
DataFrameHeaderKMessageId: messageId,
|
||||||
|
},
|
||||||
|
Message: err.Error(),
|
||||||
|
Data: "", //TODO data内容暂留空
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/31 09:57
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestDataFrameHeader_GetSet(t *testing.T) {
|
||||||
|
h := &DataFrameHeader{}
|
||||||
|
h.Set("k1", "v1")
|
||||||
|
h.Set("k2", "v2")
|
||||||
|
|
||||||
|
assert.Equal(t, "v1", h.Get("k1"))
|
||||||
|
assert.Equal(t, "v2", h.Get("k2"))
|
||||||
|
assert.Equal(t, "", h.Get("k_notexist"))
|
||||||
|
|
||||||
|
h.Set("k1", "v11")
|
||||||
|
assert.Equal(t, "v11", h.Get("k1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataFrame_EncodeDecode(t *testing.T) {
|
||||||
|
df := &DataFrame{
|
||||||
|
SpecVersion: "sv",
|
||||||
|
Type: "t",
|
||||||
|
Time: 123456,
|
||||||
|
Headers: DataFrameHeader{"k1": "v1"},
|
||||||
|
Data: "data",
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := df.Encode()
|
||||||
|
assert.NotEqual(t, "", string(ss))
|
||||||
|
|
||||||
|
df0, err := DecodeDataFrame(ss)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.EqualValues(t, df, df0)
|
||||||
|
|
||||||
|
ss = []byte(`{"time":"time"}`)
|
||||||
|
_, err = DecodeDataFrame([]byte(ss))
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataFrameResponse_EncodeDecode(t *testing.T) {
|
||||||
|
resp := &DataFrameResponse{
|
||||||
|
Code: 200,
|
||||||
|
Headers: DataFrameHeader{"k1": "v1"},
|
||||||
|
Message: "msg",
|
||||||
|
Data: "data",
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := resp.Encode()
|
||||||
|
assert.NotEqual(t, "", string(ss))
|
||||||
|
|
||||||
|
resp0, err := DecodeDataFrameResponse(ss)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.EqualValues(t, resp, resp0)
|
||||||
|
|
||||||
|
ss = []byte(`{"code":"code"}`)
|
||||||
|
_, err = DecodeDataFrameResponse(ss)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDataFrameAckPong(t *testing.T) {
|
||||||
|
pong := NewDataFrameAckPong("messageId")
|
||||||
|
assert.NotNil(t, pong)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewErrorDataFrameResponse(t *testing.T) {
|
||||||
|
errResp := NewErrorDataFrameResponse("messageId", errors.New("error"))
|
||||||
|
assert.NotNil(t, errResp)
|
||||||
|
assert.Equal(t, "error", errResp.Message)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/7 15:13
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
DataFrameHeaderKTopic = "topic"
|
||||||
|
DataFrameHeaderKContentType = "contentType"
|
||||||
|
DataFrameHeaderKMessageId = "messageId"
|
||||||
|
DataFrameHeaderKTime = "time"
|
||||||
|
|
||||||
|
DataFrameContentTypeKJson = "application/json"
|
||||||
|
DataFrameContentTypeKBase64 = "base64String"
|
||||||
|
|
||||||
|
DataFrameResponseStatusCodeKOK = 200
|
||||||
|
DataFrameResponseStatusCodeKInternalError = 500
|
||||||
|
|
||||||
|
BotMessageCallbackTopic = "bot_got_msg" //机器人消息统一回调topic
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateMessageId(prefix string) string {
|
||||||
|
return fmt.Sprintf("%s-%s", prefix, uuid.New().String())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/4/7 15:13
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestGenerateMessageId(t *testing.T) {
|
||||||
|
assert.NotEqual(t, "", GenerateMessageId("prefix-"))
|
||||||
|
assert.True(t, strings.HasPrefix(GenerateMessageId("prefix-"), "prefix-"))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/31 09:51
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 把http response的内容转换成error对象
|
||||||
|
func ErrorFromHttpResponseBody(resp *http.Response) error {
|
||||||
|
if resp == nil {
|
||||||
|
return errors.New("HttpResponseNil")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
responseBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(string(responseBody))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/31 09:51
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestErrorFromHttpResponse(t *testing.T) {
|
||||||
|
assert.NotNil(t, ErrorFromHttpResponseBody(nil))
|
||||||
|
|
||||||
|
resp := &http.Response{
|
||||||
|
Body: io.NopCloser(strings.NewReader("error")),
|
||||||
|
}
|
||||||
|
assert.NotNil(t, ErrorFromHttpResponseBody(resp))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author linya.jj
|
||||||
|
* @Date 2023/3/22 15:23
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
GetConnectionEndpointAPIUrl = "https://api.dingtalk.com/v1.0/gateway/connections/open"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SubscriptionTypeKSystem = "SYSTEM" //系统请求
|
||||||
|
SubscriptionTypeKEvent = "EVENT" //事件
|
||||||
|
SubscriptionTypeKCallback = "CALLBACK" //回调
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SubscriptionTypeSet = map[string]bool{
|
||||||
|
SubscriptionTypeKSystem: true,
|
||||||
|
SubscriptionTypeKEvent: true,
|
||||||
|
SubscriptionTypeKCallback: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue