Browse Source

feature: 添加获取钉钉JSAPI鉴权信息

liuyaqi 2 years ago
parent
commit
a28ea0ec03

+ 3 - 3
opms_libary/plugin/dingtalk/crypto/crypto.go

@@ -73,7 +73,7 @@ func (c *DingTalkCrypto) GetDecryptMsg(signature, timestamp, nonce, secretMsg st
 
 func (c *DingTalkCrypto) GetEncryptMsg(msg string) (map[string]string, error) {
 	var timestamp = time.Now().UnixMilli()
-	var nonce = randomString(12)
+	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
@@ -81,7 +81,7 @@ func (c *DingTalkCrypto) GetEncryptMsg(msg string) (map[string]string, error) {
 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
+	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的倍数")
@@ -146,7 +146,7 @@ func pkCS7Padding(ciphertext []byte, blockSize int) []byte {
 }
 
 // 随机字符串
-func randomString(n int, alphabets ...byte) string {
+func RandomString(n int, alphabets ...byte) string {
 	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 	var bytes = make([]byte, n)
 	var randby bool

+ 112 - 4
opms_libary/plugin/dingtalk/jsapi/jsapi.go

@@ -1,7 +1,19 @@
 package jsapi
 
-import "dashoo.cn/opms_libary/plugin/dingtalk/base"
-import "dashoo.cn/opms_libary/plugin/dingtalk/context"
+import (
+	"crypto/sha1"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	neturl "net/url"
+	"strconv"
+	"time"
+
+	"dashoo.cn/opms_libary/plugin/dingtalk/base"
+	"dashoo.cn/opms_libary/plugin/dingtalk/context"
+)
 
 //Jsapi 包装
 type Jsapi struct {
@@ -15,7 +27,103 @@ func NewJsapi(context *context.Context) *Jsapi {
 	return jsapi
 }
 
-func (s *Jsapi) Sign(jsticket, nonceStr, url string, timeStamp int64) (result string, err error) {
+func (s *Jsapi) Sign(jsTicket, nonceStr, url string, timeStamp int64) (string, error) {
+	url, err := neturl.QueryUnescape(url)
+	if err != nil {
+		return "", fmt.Errorf("decode url 异常 %s", err.Error())
+	}
+
+	plain := "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + strconv.FormatInt(timeStamp, 10)+ "&url=" + url
+	sum := sha1.Sum([]byte(plain))
+	return hex.EncodeToString(sum[:]), nil
+}
+
+func (s *Jsapi) GetJsapiTicket(accessToken string) (string, error) {
+	cacheKey := fmt.Sprintf("jsapi_ticket_%s", s.Context.AppKey)
+	val, err := s.Context.Cache.Get(cacheKey)
+	if err != nil {
+		return "", fmt.Errorf("获取 jsapi_ticket 缓存异常 %s", err.Error())
+	}
+	if val != nil {
+		return val.(string), nil
+	}
 
-	return
+	resp, err := s.GetTicketFromServer(accessToken)
+	if err != nil {
+		return "", err
+	}
+
+	err = s.Context.Cache.Set(cacheKey, resp.Ticket,
+		time.Second*time.Duration(resp.ExpiresIn-1500))
+	if err != nil {
+		return "", fmt.Errorf("设置 jsapi_ticket 缓存异常 %s", err.Error())
+	}
+	return resp.Ticket, nil
+}
+
+type JsapiTicketResp struct {
+	Errcode   int    `json:"errcode"`
+	Ticket    string `json:"ticket"`
+	Errmsg    string `json:"errmsg"`
+	ExpiresIn int    `json:"expires_in"`
 }
+
+//GetTicketFromServer 强制从服务器获取 ticket
+func (s *Jsapi) GetTicketFromServer(accessToken string) (JsapiTicketResp, error) {
+	resp := JsapiTicketResp{}
+	url := fmt.Sprintf("https://oapi.dingtalk.com/get_jsapi_ticket?access_token=%s", accessToken)
+	signResp, err := http.Get(url)
+	if err != nil {
+		return resp, fmt.Errorf("调用钉钉 get_jsapi_ticket http 请求异常 :%s", err.Error())
+	}
+	defer signResp.Body.Close()
+
+	b, err := ioutil.ReadAll(signResp.Body)
+	if err != nil {
+		return resp, fmt.Errorf("调用钉钉 get_jsapi_ticket 读取返回异常 :%s", err.Error())
+	}
+	fmt.Println(string(b))
+
+	err = json.Unmarshal(b, &resp)
+	if err != nil {
+		return resp, fmt.Errorf("调用钉钉 get_jsapi_ticket 解析 json 异常 %s:%s", string(b), err.Error())
+	}
+	if resp.Errcode != 0 {
+		return resp, fmt.Errorf("调用钉钉 get_jsapi_ticket 返回异常 %s:%s", string(b), err.Error())
+	}
+	return resp, nil
+}
+
+// type DingTokenResp struct {
+// 	Errcode     int    `json:"errcode"`
+// 	AccessToken string `json:"access_token"`
+// 	Errmsg      string `json:"errmsg"`
+// 	ExpiresIn   int    `json:"expires_in"`
+// }
+
+// func AccessToken(){
+// 	url := fmt.Sprintf(
+// 		"https://oapi.dingtalk.com/gettoken?appkey=%s&appsecret=%s",
+// 		dingtalk.Client.Context.AppKey,
+// 		dingtalk.Client.Context.AppSecret)
+// 	signResp, err := http.Get(url)
+// 	if err != nil {
+// 		return fmt.Errorf("调用钉钉 gettoken http 请求异常 :%s", err.Error())
+// 	}
+// 	defer signResp.Body.Close()
+
+// 	b, err := ioutil.ReadAll(signResp.Body)
+// 	if err != nil {
+// 		return fmt.Errorf("调用钉钉 gettoken 读取返回异常 :%s", err.Error())
+// 	}
+// 	fmt.Println(string(b))
+
+// 	tokenResp := DingTokenResp{}
+// 	err = json.Unmarshal(b, &tokenResp)
+// 	if err != nil {
+// 		return fmt.Errorf("调用钉钉 gettoken 解析 json 异常 %s:%s", string(b), err.Error())
+// 	}
+// 	if tokenResp.Errcode != 0 {
+// 		return fmt.Errorf("调用钉钉 gettoken 返回异常 %s:%s", string(b), err.Error())
+// 	}
+// }

+ 46 - 0
opms_parent/app/handler/dingtalk/ding_upload.go

@@ -0,0 +1,46 @@
+package dingtalk
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"dashoo.cn/common_definition/comm_def"
+	dingModel "dashoo.cn/micro/app/model/dingtalk"
+	"dashoo.cn/opms_libary/plugin/dingtalk"
+	"dashoo.cn/opms_libary/plugin/dingtalk/crypto"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+type DingUploadHandler struct{}
+
+// Swagger:DingUpload 钉钉上传 获取签名
+func (h *DingUploadHandler) JsApiSign(ctx context.Context, req *dingModel.JsApiSignReq, resp *comm_def.CommonMsg) error {
+	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
+		return err
+	}
+
+	nonceStr := crypto.RandomString(10)
+	ts := time.Now().Unix()
+	accessToken, err := dingtalk.Client.Context.GetAccessToken()
+	if err != nil {
+		return fmt.Errorf("获取 accessToken 异常 %s", err.Error())
+	}
+	ticket, err := dingtalk.Client.GetJsapi().GetJsapiTicket(accessToken)
+	if err != nil {
+		return fmt.Errorf("获取 jsapiTicket 异常 %s", err.Error())
+	}
+
+	sign, err := dingtalk.Client.GetJsapi().Sign(ticket, nonceStr, req.Url, ts)
+	if err != nil {
+		return fmt.Errorf("生成签名异常 %s", err.Error())
+	}
+	resp.Data = dingModel.JsApiSignResp{
+		AgentId:   dingtalk.Client.Context.AgentId,
+		CorpId:    dingtalk.Client.Context.CorpId,
+		TimeStamp: ts,
+		NonceStr:  nonceStr,
+		Signature: sign,
+	}
+	return nil
+}

+ 12 - 0
opms_parent/app/model/dingtalk/ding_event.go

@@ -9,3 +9,15 @@ type DingReq struct {
 type DingEvent struct {
 	EventType string
 }
+
+type JsApiSignReq struct {
+	Url string `json:"url" v:"required#请输入当前页面 url"` // 当前页面 url
+}
+
+type JsApiSignResp struct {
+	AgentId   string `json:"agentId"`   // 必填,微应用ID
+	CorpId    string `json:"corpId"`    //必填,企业ID
+	TimeStamp int64  `json:"timeStamp"` // 必填,生成签名的时间戳
+	NonceStr  string `json:"nonceStr"`  // 必填,自定义固定字符串。
+	Signature string `json:"signature"` // 必填,签名
+}

+ 1 - 0
opms_parent/main.go

@@ -54,6 +54,7 @@ func main() {
 	s.RegisterName("WorkOrder", new(work.WorkOrderHandler), "")
 	s.RegisterName("Schedule", new(plat.ScheduleHeader), "")
 	s.RegisterName("PlatWorkflow", new(workflow.PlatWorkflowHandler), "")
+	s.RegisterName("DingUpload", new(dingtalk.DingUploadHandler), "")
 
 	// 钉钉回调接口
 	s.RegisterName("DingEvent", new(dingtalk.DingHandler), "")