소스 검색

feat:封装钉钉回调相关接口。

Cheng Jian 3 년 전
부모
커밋
2a2b1e3d54

+ 0 - 44
opms_libary/db/db.go

@@ -1,44 +0,0 @@
-package db
-
-import (
-	"github.com/gogf/gf/database/gdb"
-	"github.com/gogf/gf/frame/g"
-
-	myerrors "dashoo.cn/opms_libary/myerrors"
-)
-
-type ServiceBase struct {
-	Tenant    string
-	SafeModel *gdb.Model
-	DB        gdb.DB
-	TableName string
-}
-
-func (s *ServiceBase) Init(tenant string, tableName string) error {
-	//db, err := gdb.Instance(tenant)
-	s.Tenant = tenant
-	db := g.DB(tenant)
-	if db == nil {
-		return myerrors.NewSysError(nil, "数据库初始化异常")
-	}
-	//if err != nil {
-	//	return gerror.New("获取数据库连接失败")
-	//}
-	s.DB = db
-	s.TableName = tableName
-	//s.Model = db.Table(tableName)
-	s.SafeModel = s.DB.Table(tableName).Safe()
-	return nil
-}
-
-// Exists 判断数据是否在数据表里已存在
-func (s *ServiceBase) Exists(tableName string, where ...interface{}) (bool, error) {
-	exist, err := s.DB.Table(tableName).Fields("1 as id").FindOne(where...)
-	if err != nil {
-		return false, myerrors.NewDbError(err)
-	}
-	if len(exist) > 0 {
-		return true, nil
-	}
-	return false, nil
-}

+ 0 - 115
opms_libary/db/db_test.go

@@ -1,115 +0,0 @@
-package db
-
-import (
-	"reflect"
-	"testing"
-
-	"github.com/gogf/gf/database/gdb"
-)
-
-func NewDB() (gdb.DB, error) {
-	configNode := gdb.ConfigNode{
-		Host:             "192.168.0.252",
-		Port:             "3306",
-		User:             "root",
-		Pass:             "Dashoo#190801@ali",
-		Name:             "lims_dev",
-		Type:             "mysql",
-		Role:             "master",
-		Charset:          "utf8",
-		Weight:           1,
-		MaxIdleConnCount: 10,
-		MaxOpenConnCount: 10,
-		MaxConnLifeTime:  600,
-	}
-	gdb.AddConfigNode("test", configNode)
-	return gdb.New()
-}
-
-func NewModel(table string) ServiceBase {
-	db, _ := NewDB()
-	sv := ServiceBase{
-		Tenant:    "test",
-		SafeModel: db.Model(table).Safe(),
-		DB:        db,
-		TableName: table,
-	}
-	return sv
-}
-
-func TestServiceBase_Exists(t *testing.T) {
-	type args struct {
-		tableName string
-		where     []interface{}
-	}
-	wheres := make([]interface{}, 0)
-	wheres = append(wheres, "name = ?", "dongdong")
-	tests := []struct {
-		name    string
-		fields  ServiceBase
-		args    args
-		want    bool
-		wantErr bool
-	}{
-		{"db exists", NewModel("test"), args{
-			tableName: "",
-			where:     wheres,
-		}, false, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			s := &ServiceBase{
-				Tenant:    tt.fields.Tenant,
-				SafeModel: tt.fields.SafeModel,
-				DB:        tt.fields.DB,
-				TableName: tt.fields.TableName,
-			}
-			got, err := s.Exists(tt.args.tableName, tt.args.where...)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Exists() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if got != tt.want {
-				t.Errorf("Exists() got = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-//
-func TestServiceBase_GetDictListForWhere(t *testing.T) {
-	type fields struct {
-		Tenant    string
-		SafeModel *gdb.Model
-		DB        gdb.DB
-		TableName string
-	}
-	type args struct {
-		req DictReq
-	}
-	tests := []struct {
-		name    string
-		fields  ServiceBase
-		args    args
-		want    []Dict
-		wantErr bool
-	}{}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			s := &ServiceBase{
-				Tenant:    tt.fields.Tenant,
-				SafeModel: tt.fields.SafeModel,
-				DB:        tt.fields.DB,
-				TableName: tt.fields.TableName,
-			}
-			got, err := s.GetDictListForWhere(tt.args.req)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("GetDictListForWhere() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("GetDictListForWhere() got = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}

+ 0 - 95
opms_libary/db/dict.go

@@ -1,95 +0,0 @@
-package db
-
-import (
-	"dashoo.cn/opms_libary/myerrors"
-	"errors"
-)
-
-// 新增页面请求参数
-type DictEntity struct {
-	Id   string `json:"id"`
-	Name string `json:"name"`
-}
-
-// DictReq 字典返回项的请求参数
-// v:"name     @required|length:6,30#请输入用户名称|用户名称长度非法"`
-type DictReq struct {
-	TableName    string        `json:"table_name" v:"table_name@required#表名称为必填项"`      // 表名称
-	DisplayField []string      `json:"display_field" v:"display_field@required#显示字段必填"` // 显示字段
-	SortFields   string        `json:"sort_fields"`                                     // 排序字段
-	Where        interface{}   `json:"where"`                                           // 条件
-	Args         []interface{} `json:"args"`                                            // 参数
-}
-
-// Dict 字典项
-type Dict struct {
-	Label string `json:"label"`
-	Val   string `json:"val"`
-}
-
-// GetDictList 获取字典项列表(有效的) FileldParams可选,第一个为IdField,第二个为SortField,第三个为EnabledField
-func (s *ServiceBase) GetDictList(tableName string, NameField string, FileldParams ...string) ([]DictEntity, error) {
-	idField := "Id"
-	sortField := "SortCode"
-	enabledField := "Enabled"
-
-	if len(FileldParams) > 0 {
-		for index, param := range FileldParams {
-			if param == "" {
-				continue
-			}
-
-			if index == 0 {
-				idField = param
-			} else if index == 1 {
-				sortField = param
-			} else if index == 2 {
-				enabledField = param
-			}
-
-		}
-	}
-
-	var entity []DictEntity
-	err := s.DB.Model(tableName).
-		Fields(idField+" as Id", NameField+" as Name").
-		Where(enabledField, 1).
-		Order(sortField + " ASC").
-		Structs(&entity)
-	if err != nil {
-		return nil, myerrors.NewDbError(err)
-	}
-	return entity, nil
-}
-
-// GetDictListForWhere 根据条件返回字典项 (tableName:表名称, displayField 显示字段, where, args 条件参数)
-// 如果 displayField 字段只有一个,默认加 Id是key
-func (s *ServiceBase) GetDictListForWhere(req DictReq) ([]Dict, error) {
-	if len(req.DisplayField) == 0 {
-		return nil, errors.New("显示字段参数不能为空")
-	}
-	defaultField := "Id"
-	model := s.DB.Model(req.TableName)
-	if len(req.DisplayField) == 1 {
-		model = model.Fields(defaultField+" AS Label", req.DisplayField[0]+" AS Val")
-	} else {
-		model = model.Fields(req.DisplayField[0]+" AS Label", req.DisplayField[1]+" AS Val")
-	}
-	// 排序
-	if req.SortFields != "" {
-		model = model.OrderAsc(req.SortFields)
-	}
-	dictList := make([]Dict, 0)
-	if req.Where != nil {
-		if len(req.Args) > 0 {
-			model = model.Where(req.Where, req.Args)
-		} else {
-			model = model.Where(req.Where)
-		}
-	}
-	err := model.Scan(&dictList)
-	if err != nil {
-		return nil, err
-	}
-	return dictList, nil
-}

+ 50 - 6
opms_libary/plugin/dingtalk/bridge/msg_handler.go

@@ -1,18 +1,62 @@
 package bridge
 
 import (
-	"dashoo.cn/common_definition/comm_def"
-	"dashoo.cn/opms_libary/plugin/dingtalk/context"
-	"github.com/smallnest/rpcx/protocol"
+	dingcontext "dashoo.cn/opms_libary/plugin/dingtalk/context"
+	"dashoo.cn/opms_libary/plugin/dingtalk/crypto"
+	"dashoo.cn/opms_libary/plugin/dingtalk/message"
+	"fmt"
+	"github.com/gogf/gf/encoding/gjson"
+	"github.com/gogf/gf/frame/g"
+	"github.com/smallnest/rpcx/share"
 )
 
 //DingTalkHandler struct
 type DingTalkHandler struct {
-	*context.Context
+	*dingcontext.Context
+	handleMessageFunc func(*message.MixMessage) string
+}
+
+//NewDingTalkHandler init
+func NewDingTalkHandler(context *dingcontext.Context) *DingTalkHandler {
+	srv := new(DingTalkHandler)
+	fmt.Println("NewMsgHandler:", srv)
+	srv.Context = context
+	return srv
 }
 
 //Handle 处理微信的请求消息
-func (h *DingTalkHandler) Handle(ctx context.Context, msg protocol.Message, commonMsg comm_def.CommonMsg) error {
+func (h *DingTalkHandler) Handle() (reply message.Reply, err error) {
+	ctx := h.SubsMessage.Ctx
+	meta := ctx.Value(share.ReqMetaDataKey).(map[string]string)
+	encrypt := h.Context.SubsMessage.Encrypt
+	var ding = crypto.NewDingTalkCrypto(h.Token, h.AESKey, h.AppKey)
+	decMsg, err := ding.GetDecryptMsg(meta["msg_signature"], meta["timestamp"], meta["nonce"], encrypt)
+	if err != nil {
+		g.Log().Error(err)
+		return reply, err
+	}
+
+	data := new(message.MixMessage)
+	if j, err := gjson.DecodeToJson(decMsg); err != nil {
+		g.Log().Error(err)
+		return reply, err
+	} else {
+		if err := j.Scan(data); err != nil {
+			g.Log().Error(err)
+			return reply, err
+		}
+	}
+
+	msgData := h.handleMessageFunc(data)
+	enMsg, _ := ding.GetEncryptMsg(msgData)
+	reply.MsgSignature = enMsg["msg_signature"]
+	reply.TimeStamp = enMsg["timeStamp"]
+	reply.Nonce = enMsg["nonce"]
+	reply.Encrypt = enMsg["encrypt"]
+	return reply, nil
+}
 
-	return nil
+//SetHandleMessageFunc 设置用户自定义的回调方法
+func (h *DingTalkHandler) SetHandleMessageFunc(handler func(*message.MixMessage) string) {
+	h.handleMessageFunc = handler
 }

+ 10 - 0
opms_libary/plugin/dingtalk/client.go

@@ -1,7 +1,9 @@
 package dingtalk
 
 import (
+	"dashoo.cn/opms_libary/plugin/dingtalk/bridge"
 	"dashoo.cn/opms_libary/plugin/dingtalk/context"
+	"dashoo.cn/opms_libary/plugin/dingtalk/message"
 	"dashoo.cn/opms_libary/plugin/dingtalk/workflow"
 	"github.com/gogf/gf/os/gcache"
 	"sync"
@@ -25,6 +27,8 @@ func NewClient() *ClientImpl {
 		//微信公众平台,需要填写的信息
 		AppKey:    "dinguytykawticadfoht",                                             //g.Config().GetString("dingtalk.app-key"),     //"your app id",
 		AppSecret: "zPlj4ZpITsUbeq2C0GrwJ78-e8knH_kIeyvznaNQacqtrSb9zbeZcOajgBKdolky", //g.Config().GetString("dingtalk.app-secret"), //"your app secret",
+		AESKey:    "oUjmeWea8Ow1jsdK4UHoDthy6EMQKq3RGbM2rEeTgnm",
+		Token:     "WaasHsYk8V3wqwN5xRGsCmiiRDB",
 	}
 	return newClient(config)
 }
@@ -56,3 +60,9 @@ func (c *ClientImpl) GetAccessToken() (string, error) {
 func (c *ClientImpl) GetWorkflow() *workflow.Workflow {
 	return workflow.NewWorkflow(c.Context)
 }
+
+// GetDingTalkHandler 消息管理
+func (c *ClientImpl) GetDingTalkHandler(msg *message.SubsMessage) *bridge.DingTalkHandler {
+	c.Context.SubsMessage = msg
+	return bridge.NewDingTalkHandler(c.Context)
+}

+ 15 - 0
opms_libary/plugin/dingtalk/client_test.go

@@ -1,6 +1,9 @@
 package dingtalk
 
 import (
+	"dashoo.cn/common_definition/comm_def"
+	"fmt"
+	"github.com/smallnest/rpcx/codec"
 	"testing"
 )
 
@@ -10,3 +13,15 @@ func TestSendMsg(t *testing.T) {
 	//w.GetFormSchema("PROC-7A5F6215-A8CF-4DD1-AB2C-5B1AB84C4E19")
 	w.CreateProcessInstance("47073111989114", "PROC-7A5F6215-A8CF-4DD1-AB2C-5B1AB84C4E19", 435711466)
 }
+
+func TestName(t *testing.T) {
+	data := new(comm_def.CommonMsg)
+	data.Code = 200
+	data.Msg = "成功"
+	data.Data = "111111111"
+	ser := codec.MsgpackCodec{}
+	bt, _ := ser.Encode(data)
+	var reply interface{}
+	err := ser.Decode(bt, &reply)
+	fmt.Println(err)
+}

+ 1 - 0
opms_libary/plugin/dingtalk/context/config.go

@@ -6,6 +6,7 @@ import "github.com/gogf/gf/os/gcache"
 type Config struct {
 	AppKey    string
 	AppSecret string
+	AESKey    string
 	Token     string
 	Cache     *gcache.Cache
 }

+ 3 - 0
opms_libary/plugin/dingtalk/context/context.go

@@ -1,6 +1,7 @@
 package context
 
 import (
+	"dashoo.cn/opms_libary/plugin/dingtalk/message"
 	"encoding/json"
 	"fmt"
 	"github.com/gogf/gf/frame/g"
@@ -26,6 +27,8 @@ type Context struct {
 
 	//accessTokenLock 读写锁 同一个AppKey一个
 	accessTokenLock *sync.RWMutex
+
+	SubsMessage *message.SubsMessage
 }
 
 //SetAccessTokenLock 设置读写锁(一个appID一个读写锁)

+ 173 - 0
opms_libary/plugin/dingtalk/crypto/crypto.go

@@ -0,0 +1,173 @@
+package crypto
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	r "math/rand"
+	"sort"
+	"strings"
+	"time"
+)
+
+type DingTalkCrypto struct {
+	Token          string
+	EncodingAESKey string
+	SuiteKey       string
+	BKey           []byte
+	Block          cipher.Block
+}
+
+func NewDingTalkCrypto(token, encodingAESKey, suiteKey string) *DingTalkCrypto {
+	fmt.Println(len(encodingAESKey))
+	if len(encodingAESKey) != int(43) {
+		panic("不合法的EncodingAESKey")
+	}
+	bkey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=")
+	if err != nil {
+		panic(err.Error())
+	}
+	block, err := aes.NewCipher(bkey)
+	if err != nil {
+		panic(err.Error())
+	}
+	c := &DingTalkCrypto{
+		Token:          token,
+		EncodingAESKey: encodingAESKey,
+		SuiteKey:       suiteKey,
+		BKey:           bkey,
+		Block:          block,
+	}
+	return c
+}
+
+func (c *DingTalkCrypto) GetDecryptMsg(signature, timestamp, nonce, secretMsg string) (string, error) {
+	if !c.VerificationSignature(c.Token, timestamp, nonce, secretMsg, signature) {
+		return "", errors.New("ERROR: 签名不匹配")
+	}
+	decode, err := base64.StdEncoding.DecodeString(secretMsg)
+	if err != nil {
+		return "", err
+	}
+	if len(decode) < aes.BlockSize {
+		return "", errors.New("ERROR: 密文太短")
+	}
+	blockMode := cipher.NewCBCDecrypter(c.Block, c.BKey[:c.Block.BlockSize()])
+	plantText := make([]byte, len(decode))
+	blockMode.CryptBlocks(plantText, decode)
+	plantText = pkCS7UnPadding(plantText)
+	size := binary.BigEndian.Uint32(plantText[16:20])
+	plantText = plantText[20:]
+	corpID := plantText[size:]
+	if string(corpID) != c.SuiteKey {
+		return "", errors.New("ERROR: CorpID匹配不正确")
+	}
+	return string(plantText[:size]), nil
+}
+
+func (c *DingTalkCrypto) GetEncryptMsg(msg string) (map[string]string, error) {
+	var timestamp = time.Now().UnixMilli()
+	var nonce = randomString(12)
+	str, sign, err := c.GetEncryptMsgDetail(msg, fmt.Sprint(timestamp), nonce)
+
+	return map[string]string{"nonce": nonce, "timeStamp": fmt.Sprint(timestamp), "encrypt": str, "msg_signature": sign}, err
+}
+func (c *DingTalkCrypto) GetEncryptMsgDetail(msg, timestamp, nonce string) (string, string, error) {
+	size := make([]byte, 4)
+	binary.BigEndian.PutUint32(size, uint32(len(msg)))
+	msg = randomString(16) + string(size) + msg + c.SuiteKey
+	plantText := pkCS7Padding([]byte(msg), c.Block.BlockSize())
+	if len(plantText)%aes.BlockSize != 0 {
+		return "", "", errors.New("ERROR: 消息体size不为16的倍数")
+	}
+	blockMode := cipher.NewCBCEncrypter(c.Block, c.BKey[:c.Block.BlockSize()])
+	chipherText := make([]byte, len(plantText))
+	blockMode.CryptBlocks(chipherText, plantText)
+	outMsg := base64.StdEncoding.EncodeToString(chipherText)
+	signature := c.CreateSignature(c.Token, timestamp, nonce, string(outMsg))
+	return string(outMsg), signature, nil
+}
+
+func sha1Sign(s string) string {
+	// The pattern for generating a hash is `sha1.New()`,
+	// `sha1.Write(bytes)`, then `sha1.Sum([]byte{})`.
+	// Here we start with a new hash.
+	h := sha1.New()
+
+	// `Write` expects bytes. If you have a string `s`,
+	// use `[]byte(s)` to coerce it to bytes.
+	h.Write([]byte(s))
+
+	// This gets the finalized hash result as a byte
+	// slice. The argument to `Sum` can be used to append
+	// to an existing byte slice: it usually isn't needed.
+	bs := h.Sum(nil)
+
+	// SHA1 values are often printed in hex, for example
+	// in git commits. Use the `%x` format verb to convert
+	// a hash results to a hex string.
+	return fmt.Sprintf("%x", bs)
+}
+
+// CreateSignature 数据签名
+func (c *DingTalkCrypto) CreateSignature(token, timestamp, nonce, msg string) string {
+	params := make([]string, 0)
+	params = append(params, token)
+	params = append(params, timestamp)
+	params = append(params, nonce)
+	params = append(params, msg)
+	sort.Strings(params)
+	return sha1Sign(strings.Join(params, ""))
+}
+
+// VerificationSignature 验证数据签名
+func (c *DingTalkCrypto) VerificationSignature(token, timestamp, nonce, msg, sigture string) bool {
+	return c.CreateSignature(token, timestamp, nonce, msg) == sigture
+}
+
+// 解密补位
+func pkCS7UnPadding(plantText []byte) []byte {
+	length := len(plantText)
+	unpadding := int(plantText[length-1])
+	return plantText[:(length - unpadding)]
+}
+
+// 加密补位
+func pkCS7Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padtext...)
+}
+
+// 随机字符串
+func randomString(n int, alphabets ...byte) string {
+	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+	var bytes = make([]byte, n)
+	var randby bool
+	if num, err := rand.Read(bytes); num != n || err != nil {
+		r.Seed(time.Now().UnixNano())
+		randby = true
+	}
+	for i, b := range bytes {
+		if len(alphabets) == 0 {
+			if randby {
+				bytes[i] = alphanum[r.Intn(len(alphanum))]
+			} else {
+				bytes[i] = alphanum[b%byte(len(alphanum))]
+			}
+		} else {
+			if randby {
+				bytes[i] = alphabets[r.Intn(len(alphabets))]
+			} else {
+				bytes[i] = alphabets[b%byte(len(alphabets))]
+			}
+		}
+	}
+	return string(bytes)
+}

+ 14 - 0
opms_libary/plugin/dingtalk/crypto/crypto_test.go

@@ -0,0 +1,14 @@
+package crypto
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestCrypto(t *testing.T) {
+	var ding = NewDingTalkCrypto("123456", "4g5j64qlyl3zvetqxz5jiocdr586fn2zvjpa8zls3ij", "suite4xxxxxxxxxxxxxxx")
+	msg, _ := ding.GetEncryptMsg("success")
+	fmt.Println(msg)
+	success, _ := ding.GetDecryptMsg("5a65ceeef9aab2d149439f82dc191dd6c5cbe2c0", "1445827045067", "nEXhMP4r", "1a3NBxmCFwkCJvfoQ7WhJHB+iX3qHPsc9JbaDznE1i03peOk1LaOQoRz3+nlyGNhwmwJ3vDMG+OzrHMeiZI7gTRWVdUBmfxjZ8Ej23JVYa9VrYeJ5as7XM/ZpulX8NEQis44w53h1qAgnC3PRzM7Zc/D6Ibr0rgUathB6zRHP8PYrfgnNOS9PhSBdHlegK+AGGanfwjXuQ9+0pZcy0w9lQ==")
+	fmt.Println(success)
+}

+ 45 - 0
opms_libary/plugin/dingtalk/message/message.go

@@ -0,0 +1,45 @@
+package message
+
+import "context"
+
+// EventType 事件类型
+type EventType string
+
+const (
+	//EventCheckUrl 验证URL
+	EventCheckUrl EventType = "check_url"
+	//EventSubscribe 订阅
+	EventSubscribe = "subscribe"
+	//EventUnsubscribe 取消订阅
+	EventUnsubscribe = "unsubscribe"
+)
+
+// SubsMessage 订阅消息
+type SubsMessage struct {
+	Ctx     context.Context
+	Encrypt string `json:"encrypt"`
+}
+
+//MixMessage 存放所有钉钉发送过来的消息和事件
+type MixMessage struct {
+	EventType EventType `json:"EventType"`
+	// 审批通知
+	ProcessInstanceId string `json:"processInstanceId"`
+	CorpId            string `json:"corpId"`
+	CreateTime        string `json:"createTime"`
+	FinishTime        string `json:"finishTime"`
+	Title             string `json:"title"`
+	ProcessType       string `json:"type"`
+	StaffId           string `json:"staffId"`
+	Url               string `json:"url"`
+	Result            string `json:"result"`
+	ProcessCode       string `json:"processCode"`
+}
+
+//Reply 消息回复
+type Reply struct {
+	MsgSignature string `json:"msg_signature"`
+	TimeStamp    string `json:"timeStamp"`
+	Nonce        string `json:"nonce"`
+	Encrypt      string `json:"encrypt"`
+}