소스 검색

刷新企业微信群聊记录

xiexiaoyuan 2 년 전
부모
커밋
9b0f568ec9

+ 140 - 0
controllers/wework.go

@@ -0,0 +1,140 @@
+package controllers
+
+import (
+	"encoding/xml"
+	"fmt"
+	"hongze/hongze_open_api/services"
+	"hongze/hongze_open_api/services/wework"
+	"hongze/hongze_open_api/services/wework/wxbizmsgcrypt"
+	"hongze/hongze_open_api/utils"
+	"strings"
+)
+
+//用户
+type WeworkController struct {
+	BaseCommon
+}
+
+// Notify
+// @Title 企业微信通知
+// @Description 企业微信通知
+// @router /notify [get,post]
+func (this *WeworkController) Notify()  {
+	method := this.Ctx.Input.Method()
+	token := wework.NotifyToken
+	receiverId := wework.WeWorkCorpID
+	encodingAeskey := wework.NotifyEncodingAESKey
+	wxcpt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType)
+
+	if method == "GET" {
+		// 解析出url上的参数值如下:
+		verifyMsgSign := this.GetString("msg_signature")
+		//verifyMsgSign := "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"
+		verifyTimestamp := this.GetString("timestamp")
+		//verifyTimestamp := "1409659589"
+		verifyNonce := this.GetString("nonce")
+		//verifyNonce := "263014780"
+		verifyEchoStr := this.GetString("echostr")
+		verifyEchoStr = strings.Replace(verifyEchoStr, " ", "+", -1)
+		//verifyEchoStr := "vbe33my07xk&echostr=tIoxz82erKm78d5tanP8pBCQFkIPq+zsSpHPphz23rJx5VZ9Y8h9jHgqfM5oZWJ3mxRjaGLTi0dPaneQoOI6Pg=="
+		echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr)
+		if nil != cryptErr {
+			utils.ApiLog.Println("verifyUrl fail", cryptErr)
+			this.Ctx.WriteString(fmt.Sprintf("verifyUrl fail %s", cryptErr))
+			return
+		}
+		utils.ApiLog.Println("verifyUrl success echoStr", string(echoStr))
+		// 验证URL成功,将sEchoStr返回
+		this.Ctx.WriteString(string(echoStr))
+		return
+	}else {
+		reqData := this.Ctx.Input.RequestBody
+		utils.ApiLog.Println("wework notify postBody:" + string(reqData))
+		/*
+		   	------------使用示例二:对用户回复的消息解密---------------
+		   	用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档
+		   	假设企业收到企业微信的回调消息如下:
+		   	POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6&timestamp=1409659813&nonce=1372623149 HTTP/1.1
+		   	Host: qy.weixin.qq.com
+		   	Content-Length: 613
+		   	<xml>		<ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt>
+		   	<AgentID><![CDATA[218]]></AgentID>
+		   	</xml>
+
+		   	企业收到post请求之后应该:
+		        1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce)
+		        2.验证消息体签名的正确性。
+		        3.将post请求的数据进行xml解析,并将<Encrypt>标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档
+		        第2,3步可以用企业微信提供的库函数DecryptMsg来实现。
+		*/
+		 reqMsgSign := this.GetString("msg_signature")
+		//reqMsgSign := "477715d11cdb4164915debcba66cb864d751f3e6"
+		 reqTimestamp := this.GetString("timestamp")
+		//reqTimestamp := "1409659813"
+		 reqNonce := this.GetString("nonce")
+		//reqNonce := "1372623149"
+		// post请求的密文数据
+		// reqData = HttpUtils.PostData()
+		//reqData := []byte("<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>")
+
+		msg, cryptErr := wxcpt.DecryptMsg(reqMsgSign, reqTimestamp, reqNonce, reqData)
+		if nil != cryptErr {
+			utils.ApiLog.Println("DecryptMsg fail", cryptErr)
+			this.Ctx.WriteString(fmt.Sprintf("DecryptMsg fail %s", cryptErr))
+			return
+		}
+		fmt.Println("after decrypt msg: ", string(msg))
+		// TODO: 解析出明文xml标签的内容进行处理
+		var msgContent wxbizmsgcrypt.MsgContent
+		err := xml.Unmarshal(msg, &msgContent)
+		if err != nil {
+			utils.ApiLog.Println("Unmarshal fail")
+			return
+		} else {
+			utils.ApiLog.Println("struct", msgContent)
+			if msgContent.MsgType == "event" {
+				switch msgContent.Event {
+				case "msgaudit_notify": _ = services.DayNewWeworkMsgRefresh()
+				}
+			}
+		}
+		// 验证URL成功,将sEchoStr返回
+		this.Ctx.WriteString(string(msg))
+		return
+	}
+
+
+	/*
+	   	------------使用示例三:企业回复用户消息的加密---------------
+	   	企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的xml串。
+	   	假设企业需要回复用户的明文如下:
+	   	<xml>
+	   	<ToUserName><![CDATA[mycreate]]></ToUserName>
+	   	<FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName>
+	   	<CreateTime>1348831860</CreateTime>
+	   	<MsgType><![CDATA[text]]></MsgType>
+	   	<Content><![CDATA[this is a test]]></Content>
+	   	<MsgId>1234567890123456</MsgId>
+	   	<AgentID>128</AgentID>
+	   	</xml>
+
+	   	为了将此段明文回复给用户,企业应:
+	        1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。
+	        2.将明文加密得到密文。
+	        3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。
+	        4.将密文,消息体签名,时间戳,随机数字串拼接成xml格式的字符串,发送给企业。
+	        以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。
+	*/
+	/*respData := "<xml><ToUserName><![CDATA[mycreate]]></ToUserName><FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId><AgentID>128</AgentID></xml>"
+	encryptMsg, cryptErr := wxcpt.EncryptMsg(respData, reqTimestamp, reqNonce)
+	if nil != cryptErr {
+		fmt.Println("DecryptMsg fail", cryptErr)
+	}
+
+	sEncryptMsg := string(encryptMsg)
+	fmt.Println("after encrypt sEncryptMsg: ", sEncryptMsg)
+	// 加密成功
+	// TODO:
+	// HttpUtils.SetResponse(sEncryptMsg)
+	 */
+}

+ 8 - 0
go.mod

@@ -3,6 +3,12 @@ module hongze/hongze_open_api
 go 1.16
 
 require (
+	github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
+	github.com/alibabacloud-go/alimt-20181012/v2 v2.0.0
+	github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2
+	github.com/alibabacloud-go/tea v1.1.20
+	github.com/alibabacloud-go/tea-utils/v2 v2.0.1
+	github.com/aliyun/alibaba-cloud-sdk-go v1.62.35
 	github.com/aliyun/aliyun-oss-go-sdk v2.2.0+incompatible
 	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
 	github.com/beego/beego/v2 v2.0.2
@@ -10,6 +16,8 @@ require (
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/satori/go.uuid v1.2.0 // indirect
 	github.com/shopspring/decimal v1.3.1
+	github.com/silenceper/wechat/v2 v2.1.4
+	github.com/xen0n/go-workwx v1.2.0
 	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )

+ 211 - 0
go.sum

@@ -1,21 +1,75 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
+github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
+github.com/alibabacloud-go/alimt-20181012 v1.0.2 h1:glGG3Q6sTUK3getI8K5mZOV9AVBxv6ztVAj9DKH3GMU=
+github.com/alibabacloud-go/alimt-20181012/v2 v2.0.0 h1:RZF3WXYiPB/m1FiZS51udLbpAvg0urYi1wAfB18kiUQ=
+github.com/alibabacloud-go/alimt-20181012/v2 v2.0.0/go.mod h1:1J4N3YfuJjOdknqWFERjt82R3kTO6QXk/vuAbQtzc5I=
+github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2 h1:2kR1YkvQloHUstmPcG0Sjk24zTKbza7izzJfJNwBFSs=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
+github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
+github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
+github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
+github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/openapi-util v0.0.11 h1:iYnqOPR5hyEEnNZmebGyRMkkEJRWUEjDiiaOHZ5aNhA=
+github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
+github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 h1:L0TIjr9Qh/SLVc1yPhFkcB9+9SbCNK/jPq4ZKB5zmnc=
+github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1/go.mod h1:EKxBRDLcMzwl4VLF/1WJwlByZZECJawPXUvinKMsTTs=
+github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
+github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.10/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.12/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea v1.1.20 h1:wFK4xEbvGYMtzTyHhIju9D7ecWxvSUdoLO6y4vDLFik=
+github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea-fileform v1.1.1 h1:1YG6erAP3joQ0XdCXYIotuD7zyOM6qCR49xkp5FZDeU=
+github.com/alibabacloud-go/tea-fileform v1.1.1/go.mod h1:ZeCV91o4ISmxidd686f0ebdS5EDHWU+vW+TkjLhrsFE=
+github.com/alibabacloud-go/tea-oss-sdk v1.1.3 h1:EhAHI6edMeqgkZEqP7r4nc9iMWAUBKGxJHoBsOSKTtU=
+github.com/alibabacloud-go/tea-oss-sdk v1.1.3/go.mod h1:yUnodpR3Bf2rudLE7V/Gft5txjJF30Pk+hH77K/Eab0=
+github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS3IaDWE7M9nVLRc=
+github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY=
+github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils v1.3.6 h1:bVjrxHztM8hAs6nOfLWCgxQfAtKb9RgFFMV6J3rdvB4=
+github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.1 h1:K6kwgo+UiYx+/kr6CO0PN5ACZDzE3nnn9d77215AkTs=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
+github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
+github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M=
+github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
+github.com/aliyun/alibaba-cloud-sdk-go v1.62.35 h1:4gmuj8zP+l2SyBsuHm5pp2VvEs0R9JmjOSK/SPLSVIU=
+github.com/aliyun/alibaba-cloud-sdk-go v1.62.35/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.0+incompatible h1:ht2+VfbXtNLGhCsnTMc6/N26nSTBK6qdhktjYyjJQkk=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.0+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
+github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY=
+github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@@ -40,13 +94,25 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
+github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
 github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
+github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
@@ -66,12 +132,16 @@ github.com/couchbase/gomemcached v0.1.3/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9Hj
 github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 github.com/couchbase/goutils v0.1.0/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@@ -91,13 +161,19 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
 github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@@ -105,19 +181,25 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -151,11 +233,15 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -164,6 +250,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -188,19 +276,25 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -211,6 +305,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
@@ -239,8 +335,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -251,8 +349,13 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
+github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@@ -260,15 +363,27 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
@@ -326,6 +441,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
@@ -340,13 +456,25 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
 github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
+github.com/silenceper/wechat v1.2.6 h1:FED3ko2yD96YD153xIV0I0bDjII4GxWaggjsYKdjQQc=
+github.com/silenceper/wechat/v2 v2.1.4 h1:X+G9C/EiBET5AK0zhrflX3ESCP/yxhJUvoRoSXHm0js=
+github.com/silenceper/wechat/v2 v2.1.4/go.mod h1:F0PKqImb15THnwoqRNrZO1z3vpwyWuiHr5zzfnjdECY=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
+github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
+github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
+github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
+github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
@@ -354,23 +482,45 @@ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
+github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
+github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
 github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
+github.com/xen0n/go-workwx v1.2.0 h1:3sGUR9N5e3l8l5J6Pf+SrZwPZLvxFglSmlgHFYbk1no=
+github.com/xen0n/go-workwx v1.2.0/go.mod h1:Z5yFPlCgTsn/Ry5bRxgQy+7WgpB/TxZ3CPcYJ06E2KQ=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
 github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod h1:Nv7wKD2/bCdKUFNKcJRa99a+1+aSLlCRJFriFYdjz/I=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
@@ -385,6 +535,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@@ -398,17 +550,32 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -431,12 +598,19 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
+golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -446,6 +620,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -458,52 +633,73 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -511,6 +707,12 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
+gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -562,8 +764,14 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
+gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
+gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
+gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@@ -579,10 +787,13 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

+ 150 - 0
models/tables/day_new/wework_msg.go

@@ -0,0 +1,150 @@
+package day_new
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+// 企业微信会话存档表
+type WeworkMsg struct {
+	Id         uint64    `orm:"column(id);pk;" description:"自增ID"`
+	MsgId      string    `orm:"column(msg_id)" description:"企业微信消息id"`               //企业微信消息id,消息的唯一标识,企业可以使用此字段进行消息去重
+	Action     string    `orm:"column(action)" description:"消息动作"`                   //消息动作,目前有send(发送消息)/recall(撤回消息)/switch(切换企业日志)三种类型。String类型
+	From       string    `orm:"column(from)" description:"消息发送方id"`                  //消息发送方id。同一企业内容为userid,非相同企业为external_userid。消息如果是机器人发出,也为external_userid。String类型
+	ToList     string    `orm:"column(to_list)" description:"消息接收方列表"`               //消息接收方列表,可能是多个,同一个企业内容为userid,非相同企业为external_userid。数组,内容为string类型
+	RoomId     string    `orm:"column(room_id)" description:"群聊消息的群id"`              //群聊消息的群id。如果是单聊则为空。String类型
+	MsgTime    int64     `orm:"column(msg_time)" description:"消息发送时间戳"`              //消息发送时间戳,utc时间,ms单位。
+	MsgType    string    `orm:"column(msg_type)" description:"消息类型"`                 //消息类型文本消息为:text。
+	Content    string    `orm:"column(content)" description:"消息内容"`                  //消息内容
+	ContentEn  string    `orm:"column(content_en)" description:"翻译后的英文内容"`           //翻译后的英文内容
+	ReportId   int64     `orm:"column(report_id)" description:"英文研报ID"`              //英文研报ID
+	CreateTime time.Time `orm:"column(create_time)" description:"创建时间"`              //创建时间
+	IsAdd      int8      `orm:"column(is_add)" description:"是否已加入到报告当中: 0未加入,1已加入 "` //是否已加入到报告当中: 0未加入,1已加入
+	IsDelete   int8      `orm:"column(is_delete)" description:"是否被删除:0未删除,1已删除"`     //是否被删除:0未删除,1已删除
+	ModifyTime time.Time `orm:"column(modify_time)" description:"修改时间"`              //修改时间
+}
+
+type WeworkMsgListItem struct {
+	MsgId      string `description:"企业微信消息id"`
+	Content    string `description:"消息内容"`
+	ContentEn  string `description:"翻译后的英文内容"`
+	CreateTime string `description:"创建时间"`
+	IsAdd      int8   `description:"是否已加入到报告当中: 0未加入,1已加入 "`
+	IsDelete   int8   `description:"是否被删除:0未删除,1已删除"`
+	ModifyTime string `description:"修改时间"`
+	MsgTime    string `description:"消息发送时间"`
+	FromName   string `description:"消息发送者"`
+}
+
+// WeworkMsgListResp 分页列表响应体
+type WeworkMsgListResp struct {
+	List           []*WeworkMsgListItem
+	Paging         *paging.PagingItem `description:"分页数据"`
+	LastUpdateTime string             `description:"上次刷新时间"`
+}
+
+type DeleteWeworkMsgReq struct {
+	MsgId      string `description:"企业微信消息id"`
+}
+
+// AddWeworkMsg 新增消息
+func AddWeworkMsg(item *WeworkMsg) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(&item)
+	return
+}
+
+// AddWeworkMsgMulti 批量新增消息
+func AddWeworkMsgMulti(list []*WeworkMsg) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(list), list)
+	return
+}
+
+// GetWeworkMsgList 获取企业微信群消息列表
+func GetWeworkMsgList(condition string, pars []interface{}, startSize, pageSize int) (total int, list []*WeworkMsg, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_msg WHERE 1 = 1 `
+	sql += condition
+	totalSQl := `SELECT COUNT(1) total FROM (` + sql + `) z`
+	if err = o.Raw(totalSQl, pars).QueryRow(&total); err != nil {
+		return
+	}
+	sql += ` ORDER BY msg_time DESC, id DESC`
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// GetWeworkMsgByCondition 获取企业微信群消息列表
+func GetWeworkMsgByCondition(condition string, pars []interface{}) (list []*WeworkMsg, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_msg WHERE 1 = 1 `
+	sql += condition
+	sql += ` ORDER BY id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// GetWeworkMsgByConditionLimit 获取企业微信群消息列表
+func GetWeworkMsgByConditionLimit(condition string, pars []interface{}, limit int) (list []*WeworkMsg, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_msg WHERE 1 = 1 `
+	sql += condition
+	sql += ` ORDER BY id asc LIMIT 0, ?`
+	_, err = o.Raw(sql, pars, limit).QueryRows(&list)
+	return
+}
+
+func MultiUpdateContentEn(multiSql string, updateSendIds string) (err error)  {
+	o := orm.NewOrm()
+	sql := `UPDATE wework_msg 
+SET modify_time=NOW(), content_en =
+CASE id `+multiSql+` END 
+WHERE 
+	id IN (`+updateSendIds+` ) `
+	_, err = o.Raw(sql).Exec()
+	return
+}
+
+// DeleteWeworkMsgByMsgId 删除消息
+func DeleteWeworkMsgByMsgId(msgId string) (err error)  {
+	o := orm.NewOrm()
+	sql := `UPDATE wework_msg 
+SET modify_time=NOW(), is_delete = 1
+WHERE 
+	 msg_id = ?`
+	_, err = o.Raw(sql, msgId).Exec()
+	return
+}
+
+// GetWeworkMsgByMsgId 根据消息Id查询
+func GetWeworkMsgByMsgId(msgId string) (item *WeworkMsg, err error)  {
+	o := orm.NewOrm()
+	sql := `select * from wework_msg where msg_id = ?`
+	err = o.Raw(sql, msgId).QueryRow(&item)
+	return
+}
+
+type AddEnglishReportReq struct {
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+}
+
+// UpdateWeworkMsgIsAdd 更新消息状态为已加入报告
+func UpdateWeworkMsgIsAdd(msgIds string, reportId int64) (err error)  {
+	o := orm.NewOrm()
+	sql := `UPDATE wework_msg 
+SET modify_time=NOW(), is_add = 1, report_id= ?
+WHERE 
+	 msg_id in (`+msgIds+`) and is_delete = 0 and is_add = 0`
+	_, err = o.Raw(sql, reportId).Exec()
+	return
+}

+ 36 - 0
models/tables/day_new/wework_msg_log.go

@@ -0,0 +1,36 @@
+package day_new
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// 企业微信群消息拉取日志表
+type WeworkMsgLog struct {
+	Id         uint64    `orm:"column(id);pk;auto" description:"自增ID"`
+	Seq        uint64    `orm:"column(seq)" description:"本次请求获取消息记录开始的seq值。首次访问填写0"`
+	Limit      int       `orm:"column(limit)" description:"一次调用限制的limit值,不能超过1000"`
+	Total      int       `orm:"column(total)" description:"实际拉取到的消息数"`
+	ReqResult  int8      `orm:"column(req_result)" description:"请求结果:1成功,2失败"`
+	CreateTime time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime time.Time `orm:"column(modify_time)" description:"修改时间"`
+}
+func (w *WeworkMsgLog) TableName() string {
+	return "wework_msg_log"
+}
+
+// AddMsgReqLog 新增调用记录
+func AddMsgReqLog(item *WeworkMsgLog) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(item)
+	return
+}
+
+// GetLasReqLog 获取最近一次成功调用记录
+func GetLasReqLog() (item *WeworkMsgLog, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_msg_log WHERE req_result = 1 order by id desc`
+	err = o.Raw(sql).QueryRow(&item)
+	return
+}
+

+ 67 - 0
models/tables/day_new/wework_user.go

@@ -0,0 +1,67 @@
+package day_new
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// 企业微信用户信息表
+type WeworkUser struct {
+	Id             uint64    `orm:"column(id);pk;" description:"自增ID"`
+	WwUserId       string    `orm:"column(ww_user_id)" description:"企业微信成员userid"`
+	WwExtendUserId string    `orm:"column(ww_extend_user_id)" description:"非企业成员id(external_userid)"`
+	WwNickName     string    `orm:"column(ww_nick_name)" description:"企业微信昵称/微信昵称"`
+	WwDeptId       int       `orm:"column(ww_dept_id)" description:"企业微信内部门ID"`
+	CreateTime     time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime     time.Time `orm:"column(modify_time)" description:"修改时间"`
+}
+
+// AddWeworkUserMulti 批量新增用户
+func AddWeworkUserMulti(list []*WeworkUser) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(list), list)
+	return
+}
+
+// GetAllWeworkUser 获取所有企业微信用户
+func GetAllWeworkUser() (list []*WeworkUser, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_user`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetEmptyNickNameWeworkUser 获取所有企业微信昵称为空的用户
+func GetEmptyNickNameWeworkUser() (list []*WeworkUser, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_user where (ww_nick_name = "" or ww_nick_name is null)`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetWeworkUserByUserIds 根据员工ID查询员工姓名
+func GetWeworkUserByUserIds(userIds string) (list []*WeworkUser, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_user where ww_user_id in (`+userIds+`)`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetWeworkUserByExtendUserIds 根据客户联系人ID查询昵称
+func GetWeworkUserByExtendUserIds(extendUserIds string) (list []*WeworkUser, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wework_user where ww_extend_user_id in (`+extendUserIds+`)`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+// MultiUpdateWeworkUserName 批量更新用户姓名
+func MultiUpdateWeworkUserName(multiSql string, updateSendIds string) (err error)  {
+	o := orm.NewOrm()
+	sql := `UPDATE wework_user 
+SET modify_time=NOW(), ww_nick_name =
+CASE id `+multiSql+` END 
+WHERE 
+	id IN (`+updateSendIds+` ) `
+	_, err = o.Raw(sql).Exec()
+	return
+}

+ 115 - 0
services/aliyun_translate.go

@@ -0,0 +1,115 @@
+package services
+
+import (
+	"errors"
+	"fmt"
+	alimt20181012 "github.com/alibabacloud-go/alimt-20181012/v2/client"
+	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+	util "github.com/alibabacloud-go/tea-utils/v2/service"
+	"github.com/alibabacloud-go/tea/tea"
+)
+
+/**
+* 使用STS鉴权方式初始化账号Client,推荐此方式。本示例默认使用AK&SK方式。
+* @param accessKeyId
+* @param accessKeySecret
+* @param securityToken
+* @return Client
+* @throws Exception
+ */
+func CreateClientWithSTS (accessKeyId *string, accessKeySecret *string, securityToken *string) (_result *alimt20181012.Client, _err error) {
+	config := &openapi.Config{
+		// 必填,您的 AccessKey ID
+		AccessKeyId: accessKeyId,
+		// 必填,您的 AccessKey Secret
+		AccessKeySecret: accessKeySecret,
+		// 必填,您的 Security Token
+		SecurityToken: securityToken,
+		// 必填,表明使用 STS 方式
+		Type: tea.String("sts"),
+	}
+	// 访问的域名
+	config.Endpoint = tea.String("mt.cn-hangzhou.aliyuncs.com")
+	_result = &alimt20181012.Client{}
+	_result, _err = alimt20181012.NewClient(config)
+	return _result, _err
+}
+
+func handler (accessKeyId, accessKeySecret, stsToken *string, content string) (resp *alimt20181012.TranslateGeneralResponseBody, _err error) {
+	// 初始化 Client,采用 AK&SK 鉴权访问的方式,此方式可能会存在泄漏风险,建议使用 STS 方式。鉴权访问方式请参考:https://help.aliyun.com/document_detail/378661.html
+	//client, _err := CreateClient(accessKeyId, accessKeySecret)
+	client, _err := CreateClientWithSTS(accessKeyId, accessKeySecret, stsToken)
+	if _err != nil {
+		_err = errors.New(fmt.Sprintf("创建翻译客户端失败 err: %v", _err))
+		return
+	}
+
+	translateGeneralRequest := &alimt20181012.TranslateGeneralRequest{
+		FormatType: tea.String("text"),
+		SourceLanguage: tea.String("zh"),
+		TargetLanguage: tea.String("en"),
+		SourceText: tea.String(content),
+		Scene: tea.String("general"),
+	}
+	runtime := &util.RuntimeOptions{}
+	ret, tryErr := func()(ret *alimt20181012.TranslateGeneralResponse, _e error) {
+		defer func() {
+			if r := tea.Recover(recover()); r != nil {
+				_e = r
+				return
+			}
+		}()
+		// 复制代码运行请自行打印 API 的返回值
+		ret, _e = client.TranslateGeneralWithOptions(translateGeneralRequest, runtime)
+		if _e != nil {
+			return
+		}
+		return
+	}()
+
+
+	if tryErr != nil {
+		var e = &tea.SDKError{}
+		if _t, ok := tryErr.(*tea.SDKError); ok {
+			e = _t
+		} else {
+			e.Message = tea.String(tryErr.Error())
+		}
+		// 如有需要,请打印 e
+		_, _err = util.AssertAsString(e.Message)
+		if _err != nil {
+			_err =  errors.New(fmt.Sprintf("翻译失败 err: %v", _err))
+			return
+		}
+		_err =  errors.New(fmt.Sprintf("翻译失败 err: %v", _err))
+		return
+	}
+	if *ret.StatusCode != 200 {
+		_err = errors.New(fmt.Sprintf(" %v", ret.StatusCode))
+		return
+	}
+	resp = ret.Body
+	return
+}
+
+
+func AliTranslate(content string) (contentEn string, err error) {
+	stsToken, err := GetOssSTSToken()
+	if err != nil {
+		err = errors.New(fmt.Sprintf("阿里云机器翻译失败 err %v", err))
+		return
+	}
+	fmt.Println(stsToken)
+	resp, err := handler(&stsToken.AccessKeyId, &stsToken.AccessKeySecret, &stsToken.SecurityToken, content)
+	if err != nil {
+		err = errors.New(fmt.Sprintf("阿里云机器翻译失败 err %v", err))
+		return
+	}
+	if *resp.Code != 200 {
+		err = errors.New(fmt.Sprintf("阿里云机器翻译失败 code: %v; msg: %v",*resp.Code, *resp.Message))
+		return
+	}
+
+	contentEn = *resp.Data.Translated
+	return
+}

+ 335 - 0
services/day_new.go

@@ -0,0 +1,335 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/silenceper/wechat/v2/work/msgaudit"
+	"hongze/hongze_open_api/models/tables/day_new"
+	"hongze/hongze_open_api/services/alarm_msg"
+	"hongze/hongze_open_api/services/wework"
+	"hongze/hongze_open_api/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func DayNewWeworkMsgRefresh() (err error)  {
+	cacheKey := utils.CACHE_KEY_DAYNEW_REFRESH
+	//设置redis 防止频繁操作
+	if !utils.Rc.SetNX(cacheKey, 1, 1*time.Minute) {
+		err = errors.New(fmt.Sprintf("系统处理中,请1分钟之后再试"))
+		return
+	}
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("企业微信 会议存档消息刷新失败, Err:"+err.Error(), 1)
+		}
+	}()
+	list := make([]msgaudit.TextMessage, 0)
+	//获取最新的拉取日志,得到seq和limit的值
+	lastReq, e := day_new.GetLasReqLog()
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = errors.New(fmt.Sprintf("获取最新请求日志失败 err: %v", e))
+		return
+	}
+
+	now := time.Now()
+	seq := uint64(0)
+	limit := uint64(100)
+	timeout := 5
+	seqRes := uint64(0)
+	if e == nil {
+		seq = lastReq.Seq
+		limit = uint64(lastReq.Limit)
+	}
+
+	msgAuditClient := wework.NewWeWorkMsgAuditClient()
+	list, seqRes, err = msgAuditClient.GetMsgAuditContent(seq, limit, timeout)
+	if err != nil {
+		err = errors.New(fmt.Sprintf("查询会话存档信息失败 err: %v", err))
+		return
+	}
+	if len(list) == 0 { //本次无消息更新
+		return
+	}
+	//查询5天内的消息,用来判断消息是否已存在
+	before5Time := time.Now().Add(-120*time.Hour).Unix() * 1000
+	cond := " and msg_time > ?"
+	var pars []interface{}
+	pars = append(pars, before5Time)
+	existList, err := day_new.GetWeworkMsgByCondition(cond, pars)
+	if err != nil {
+		return
+	}
+	existMsgMap := make(map[string]struct{}, 0)
+	for _, v := range existList {
+		existMsgMap[v.MsgId] = struct{}{}
+	}
+	//如果成功获取到值,则新增一条新的请求日志,是否需要加入事务,防止请求成功后,数据没有正常插入引起
+	userMap := make(map[string]struct{}, 0)  //企业内用户
+	extendUserMap := make(map[string]struct{}, 0) //企业外联系人
+	insertList := make([]*day_new.WeworkMsg, 0)
+
+	for _, v := range list {
+		if _, ok := existMsgMap[v.MsgID]; ok {
+			continue
+		}
+		toList, _ := json.Marshal(v.ToList)
+		v.ToList = append(v.ToList, v.From) //把发言者的userid也加入到用户map里
+		for _, u := range v.ToList {
+			if len(u) > 10 && strings.HasPrefix(u, "wm") {
+				if _, ok := extendUserMap[u]; !ok {
+					extendUserMap[u] = struct{}{}
+				}
+			}else{
+				if _, ok := userMap[u]; !ok {
+					userMap[u] = struct{}{}
+				}
+			}
+		}
+		tmp := &day_new.WeworkMsg{
+			MsgId:      v.MsgID,
+			Action:     v.Action,
+			From:       v.From,
+			ToList:     string(toList),
+			RoomId:     v.RoomID,
+			MsgTime:    v.MsgTime,
+			MsgType:    v.MsgType,
+			Content:    v.Text.Content,
+			ContentEn:  "",
+			ReportId:   0,
+			CreateTime: now,
+			IsAdd:      0,
+			IsDelete:   0,
+			ModifyTime: now,
+		}
+		insertList = append(insertList, tmp)
+	}
+	//批量插入数据库
+	err = day_new.AddWeworkMsgMulti(insertList)
+	if err != nil {
+		err = errors.New(fmt.Sprintf("批量新增模版消息失败 err: %v", err))
+		return
+	}
+
+	reqLog := &day_new.WeworkMsgLog{
+		Seq:        seqRes,
+		Limit:      int(limit),
+		Total:      len(list),
+		ReqResult:  int8(1),
+		CreateTime: now,
+		ModifyTime: now,
+	}
+	err = day_new.AddMsgReqLog(reqLog)
+	if err != nil {
+		err = errors.New(fmt.Sprintf("新增请求日志失败 err: %v", err))
+		return
+	}
+
+	//批量插入用户信息
+	//查询已有的用户信息,判断是否需要新增
+	allUsers, err := day_new.GetAllWeworkUser()
+	if err != nil {
+		err = errors.New(fmt.Sprintf("查询所有企业微信用户失败 err: %v", err))
+		return
+	}
+	insertUserList := make([]*day_new.WeworkUser, 0)
+	userExistMap := make(map[string]struct{}, 0)  //企业内用户
+	extendUseExistrMap := make(map[string]struct{}, 0) //企业外联系人
+	for _, v := range allUsers {
+		if v.WwExtendUserId != "" {
+			extendUseExistrMap[v.WwExtendUserId] = struct{}{}
+		}
+		if v.WwUserId != "" {
+			userExistMap[v.WwUserId] = struct{}{}
+		}
+	}
+	for k := range extendUserMap {
+		if _, ok := extendUseExistrMap[k]; !ok {
+			tmp := &day_new.WeworkUser{
+				WwUserId:       "",
+				WwExtendUserId: k,
+				WwNickName:     "",
+				WwDeptId:       0,
+				CreateTime:     now,
+				ModifyTime:     now,
+			}
+			insertUserList = append(insertUserList, tmp)
+		}
+	}
+
+	for k := range userMap {
+		if _, ok := userExistMap[k]; !ok {
+			tmp := &day_new.WeworkUser{
+				WwUserId:       k,
+				WwExtendUserId: "",
+				WwNickName:     "",
+				WwDeptId:       0,
+				CreateTime:     now,
+				ModifyTime:     now,
+			}
+			insertUserList = append(insertUserList, tmp)
+		}
+	}
+	if len(insertUserList) > 0 {
+		err = day_new.AddWeworkUserMulti(insertUserList)
+		if err != nil {
+			err = errors.New(fmt.Sprintf("新增企业微信用户失败 err: %v", err))
+			return
+		}
+	}
+
+	//批量翻译
+	go DayNewTranslateContent()
+
+	go GetWeWorkUsersNickName()
+
+	return
+}
+
+func GetWeWorkUsersNickName() (err error)  {
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("企业微信 查询用户昵称失败, Err:"+err.Error(), 1)
+		}
+	}()
+	//查询没有昵称的用户库,循环调用查询昵称接口
+	users, err := day_new.GetEmptyNickNameWeworkUser()
+	if err != nil {
+		return
+	}
+	if len(users) == 0{
+		return
+	}
+	multi := ""
+	needChangeIds := ""
+
+	client := wework.NewWeWorkUserNameClient()
+	ExtendClient := wework.NewWeWorkExternalContactClient()
+	//设置批量更新
+	for _, v := range users {
+		if v.WwUserId != "" {
+			ret, e := client.GetUser(v.WwUserId)
+			if e != nil {
+				err = e
+				return
+			}
+			if ret.Name != "" {
+				needChangeIds += strconv.Itoa(int(v.Id)) + ","
+				multi += ` WHEN `+strconv.Itoa(int(v.Id))+` THEN "`+ ret.Name +`"`
+			}
+		}else if v.WwExtendUserId != "" {
+			// todo 获取外部联系人姓名
+			ret1, e := ExtendClient.GetExternalContact(v.WwExtendUserId)
+			if e != nil {
+				err = e
+				return
+			}
+			if ret1.ExternalContact.Name != "" {
+				needChangeIds += strconv.Itoa(int(v.Id)) + ","
+				multi += ` WHEN `+strconv.Itoa(int(v.Id))+` THEN "`+ ret1.ExternalContact.Name +`"`
+			}
+		}
+	}
+
+	if needChangeIds != "" {
+		needChangeIds = strings.Trim(needChangeIds, ",")
+		err = day_new.MultiUpdateWeworkUserName(multi, needChangeIds)
+		if err != nil {
+			err = errors.New(fmt.Sprintf("更新企业微信成员姓名失败 err: %v", err))
+			return
+		}
+	}
+
+	return
+}
+
+func DayNewTranslateContent() (err error) {
+	cacheKey := utils.CACHE_KEY_DAYNEW_TRANSLATE
+	//设置redis 防止频繁操作,控制每分钟执行1次
+	if !utils.Rc.SetNX(cacheKey, 1, 1*time.Minute) {
+		err = errors.New(fmt.Sprintf("系统处理中,请1分钟之后再试"))
+		return
+	}
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("企业微信 中翻英操作失败, Err:"+err.Error(), 1)
+		}
+	}()
+	//查询待翻译的内容列表
+	condition := ` and (content_en = "" or content_en is null)`
+	var pars []interface{}
+	list, err := day_new.GetWeworkMsgByConditionLimit(condition, pars, 2000) //默认最多查询2000条
+	if err != nil {
+		err = errors.New(fmt.Sprintf("查询未翻译的聊天记录失败 err: %v", err))
+		return
+	}
+	multi := ""
+	needChangeIds := ""
+	count := 0
+	contentMap := make(map[string]string, 0)
+	contentEnMap := make(map[string]string)
+	for _, v := range list {
+		//如果单条翻译的字符数超过1000,则直接翻译,否则批量翻译
+		if len(v.Content) > 1000 {
+			en, e := AliTranslate(v.Content)
+			if e != nil {
+				err = e
+				return
+			}
+			needChangeIds += strconv.Itoa(int(v.Id)) + ","
+			multi += ` WHEN `+strconv.Itoa(int(v.Id))+` THEN "`+ en +`"`
+		}else{
+			if count >= 50 {  //待翻译的条数不能超过50; 单条翻译字符数不能超过1000字符
+				contentEnMap, err = batchTranslateHandler(contentMap)
+				if err != nil {
+					return
+				}
+				// 拼接更新sql
+				for rk, rv := range contentEnMap {
+					needChangeIds += rk + ","
+					multi += ` WHEN `+rk+` THEN "`+ rv +`"`
+				}
+				contentMap = make(map[string]string, 0)
+				count = 0
+			}
+			contentMap[strconv.Itoa(int(v.Id))] = v.Content
+			count += 1
+		}
+	}
+	//剩余不满50条的content
+	if count > 0 {
+		contentEnMap, err = batchTranslateHandler(contentMap)
+		if err != nil {
+			return
+		}
+		// 拼接更新sql
+		for rk, rv := range contentEnMap {
+			needChangeIds += rk + ","
+			multi += ` WHEN `+rk+` THEN "`+ rv +`"`
+		}
+	}
+
+	if needChangeIds != "" {
+		needChangeIds = strings.Trim(needChangeIds, ",")
+		err = day_new.MultiUpdateContentEn(multi, needChangeIds)
+		if err != nil {
+			err = errors.New(fmt.Sprintf("更新翻译后的内容失败 err: %v", err))
+			return
+		}
+	}
+	return
+}
+
+func batchTranslateHandler(contentMap map[string]string) (contentEnMap map[string]string, err error)  {
+	bytes,_ := json.Marshal(contentMap)
+	content := string(bytes)
+	en, err := AliTranslate(content)
+	if err != nil {
+		return
+	}
+	//json转为map数据结构
+	err = json.Unmarshal([]byte(en), &contentEnMap)
+	return
+}

+ 141 - 1
services/oss.go

@@ -1,10 +1,13 @@
 package services
 
 import (
+	"encoding/json"
+	"errors"
 	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+	"hongze/hongze_open_api/services/alarm_msg"
 	"os"
 	"time"
-
+	"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
 	"hongze/hongze_open_api/utils"
 )
 
@@ -89,3 +92,140 @@ func UploadVideoAliyun(filename, filepath, savePath string) error {
 	//return path,err
 	return err
 }
+
+var (
+	RoleArn            = "acs:ram::1884217364581072:role/hzossrole"
+	RoleSessionName    = "hzossRole"
+	RAMAccessKeyId     = "LTAI5t9S36LXduhTnECVY6Hn"
+	RAMAccessKeySecret = "V6FG5bdzjKKqMpqEqxeWijJCdzDCuL"
+	STSTokenCacheKey   = "hongze_admin_sts_token"
+)
+
+type STSToken struct {
+	AccessKeyId     string
+	AccessKeySecret string
+	SecurityToken   string
+	ExpiredTime     string
+}
+
+// GetOssSTSToken 获取STSToken
+func GetOssSTSToken() (item *STSToken, err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info(err.Error())
+			go alarm_msg.SendAlarmMsg("获取STSToken失败, ErrMsg: "+err.Error(), 3)
+		}
+	}()
+	item = new(STSToken)
+	// 获取缓存中的Token
+	recent, _ := utils.Rc.RedisString(STSTokenCacheKey)
+	if recent != "" {
+		lastToken := new(STSToken)
+		if e := json.Unmarshal([]byte(recent), &lastToken); e != nil {
+			err = errors.New("GetOssSTSToken lastToken Unmarshal Err: " + e.Error())
+			return
+		}
+		// 未防止正在上传大文件时Token过期, 将判定的过期时间提前10分钟
+		afterTime := time.Now().Local().Add(10 * time.Minute)
+		expired, e := time.ParseInLocation(utils.FormatDateTime, lastToken.ExpiredTime, time.Local)
+		if e != nil {
+			err = errors.New("GetOssSTSToken expiredTime Parse Err: " + e.Error())
+			return
+		}
+		if expired.After(afterTime) {
+			item.AccessKeyId = lastToken.AccessKeyId
+			item.AccessKeySecret = lastToken.AccessKeySecret
+			item.SecurityToken = lastToken.SecurityToken
+			item.ExpiredTime = lastToken.ExpiredTime
+			return
+		}
+	}
+	// 已过期则获取新的token
+	newToken, e := NewSTSToken()
+	if e != nil {
+		err = errors.New("GetOssSTSToken NewSTSToken Err: " + e.Error())
+		return
+	}
+	newTokenJson, e := json.Marshal(newToken)
+	if e != nil {
+		err = errors.New("GetOssSTSToken NewToken JSON Err: " + e.Error())
+		return
+	}
+	// 覆盖缓存
+	if e := utils.Rc.Put(STSTokenCacheKey, newTokenJson, time.Hour); e != nil {
+		err = errors.New("GetOssSTSToken SetRedis Err: " + e.Error())
+		return
+	}
+	item = newToken
+	return
+}
+
+// NewSTSToken 获取一个新的STSToken
+func NewSTSToken() (item *STSToken, err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info(err.Error())
+		}
+	}()
+	item = new(STSToken)
+	client, e := sts.NewClientWithAccessKey("cn-shanghai", RAMAccessKeyId, RAMAccessKeySecret)
+	if e != nil {
+		err = errors.New("NewSTSToken NewClient Err: " + e.Error())
+		return
+	}
+	request := sts.CreateAssumeRoleRequest()
+	request.Scheme = "https"
+	request.RegionId = "cn-shanghai"
+	request.RoleArn = RoleArn
+	now := time.Now().Format(utils.FormatDateTimeUnSpace)
+	request.RoleSessionName = RoleSessionName + now
+	request.DurationSeconds = "3600"
+
+	response, e := client.AssumeRole(request)
+	if e != nil {
+		err = errors.New("NewSTSToken AssumeRole Err: " + e.Error())
+		return
+	}
+	if response != nil {
+		item.AccessKeyId = response.Credentials.AccessKeyId
+		item.AccessKeySecret = response.Credentials.AccessKeySecret
+		item.SecurityToken = response.Credentials.SecurityToken
+		t, _ := time.Parse(time.RFC3339, response.Credentials.Expiration)
+		expiration := t.In(time.Local)
+		item.ExpiredTime = expiration.Format(utils.FormatDateTime)
+	}
+	return
+}
+
+//后台用于传研报小程序二维码
+var (
+	Bucketname      string = "hzchart"
+	Endpoint        string = "oss-cn-shanghai.aliyuncs.com"
+	Imghost         string = "https://hzstatic.hzinsights.com/"
+	UploadDir       string = "static/images/"
+	AccessKeyId     string = "LTAIFMZYQhS2BTvW"
+	AccessKeySecret string = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
+)
+
+// UploadAliyunToDir
+func UploadAliyunToDir(filename, filepath, fileDir string) (string, error) {
+	client, err := oss.New(Endpoint, AccessKeyId, AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(Bucketname)
+	if err != nil {
+		return "2", err
+	}
+	if fileDir == "" {
+		fileDir = time.Now().Format("200601/20060102/")
+	}
+	path := UploadDir + fileDir
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = Imghost + path
+	return path, err
+}

+ 159 - 0
services/wework/wework.go

@@ -0,0 +1,159 @@
+package wework
+
+import (
+	"fmt"
+	"github.com/silenceper/wechat/v2"
+	"github.com/silenceper/wechat/v2/cache"
+	"github.com/silenceper/wechat/v2/work/config"
+	"github.com/silenceper/wechat/v2/work/msgaudit"
+	"github.com/xen0n/go-workwx"
+	_ "github.com/xen0n/go-workwx" // package workwx
+)
+
+var (
+	WeWorkCorpID = "ww5008ae926e352838"
+	WeWorkFinanceCorpSecret = "-A48aGB1zZYURAPJBgYk5Lpaqx_j7sGgUL7gUVfzfcc" //企业微信 会话存档secret
+	WeWorkExternalContactCorpSecret = "LgKgtar8gxnKlh_GO8VcvDa9V1oEO_A8o5qPjQoj4to" //企业微信 客户联系 secret
+	//WeWorkContactCorpSecret = "ER4eEIski4oGVXjSwZ2uCTL_r0h5qERQz8vvOxE3xsM" //企业微信 通讯录 secret
+	agentID = int64(0)
+	UserNameAgentID = int64(1000011)
+	WeWorkUserNameAgentSecret = "fkzl42sVb4OuwePxJ1WaUvhCAfpwiCyIG_1hLJzF8Qc" //企业微信 自建应用的获取员工姓名的 secret
+
+	NotifyToken = "uPK8fG9xHfQaWuhNnGGEhN"
+	NotifyEncodingAESKey = "N57BVy29HoxTlSlCJfm7cwLWu1KUjvEu5oqnyJXuMfm"
+	WeWorkFinanceCorpRasPrivateKey = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAtbdCeRcXRrq/4sa8k/1aCRmSjZxHYKy/wG/iqX5GkX86T8xi
+ZOUgD+2eVKGwa8PJK2aH6DGWnGTDUgPr4/vTyToSf4roA+pJADOA4dtIlxUv3Rh2
+n+fI/O8spk+IZnZshhQIkpfYQUH9XKOm8EHAA9yYk3fmZh60iJ9R3cvQoscqEC+6
+K0VnlKvfiz3XpB+zK9yIOWWpqdtjIHWOCKzwZKqTjSheLBfDDKmu7N/YMquFBLvf
+rIPW9A1cBAo4b4tR8fbciuhlQymZQjn1RQAidZDwjnGZCTm10G/3xZSw8BNeTqjt
+TThTk0YTxE1fnEVKwfSFk6BxQDVcg40u3K960QIDAQABAoIBAAOMCHbjIb/ucMTt
+Mqyj1yBS2La9bHJBZPvNY0wCLD/2Tw3UND56b3oIHh1SS6aK25Amj/QTFbjHSb29
+2FqqpXtp0Acfz8AV0Gd52ePuuxfB4N2MtvIPnO98/q7fYg739E3YscMkxiKd9mNa
+yW0qp2Qb5/mG718ibc016OvuqMuolYy96M6hmlLhbE0n6WQbw5i1t2QUPhLbABpa
+crBWSdWtJwku7WVETX9TGe3LZDNnjFWv95UnBsyajkI21NTLmY4VlqvUeT/ASftG
+su2aujY3EjNu++aGeAcI5f1kPi+w0cI02GDUA9VAe8tsR9GZ8G53K/kAUAfmwHKG
+81e8wi0CgYEAt5UwxXbCYS7aAVaselAm4U+uP8yAi+S+/kOZYCn4x2j/ceTSLAKT
+vhcsj1+f5Y3ScmLZXin58hkG3v7xS5B0WJ+yR8i8PEarEmbrttY707P5mfsXJS2x
+b7R5zhcIs3mvkHOH1dhZkyNlqo2YCMkUxCZdWnxde5DOx64Lz0X3v+UCgYEA/WWK
+oL9vrDK9kdhFaSGC6OD4lM+gS6CTHzOAKrYI00o3kkGiRlXNTzob9zLuzRzeU/k5
+wMXoAj8pvI2b/jTNws12G+dqVKq9He6hgHXwntCLGQnixACuIrAQLFNL/Kze4unE
+XtBLiIA5DbqSEfVO1kYFC4mr6i+E7e4ws8AfKH0CgYBmDyjH/lfd00AbUmYcsYaL
+59JFYZltLEQJ8ubHkU3e/j5UwRMeqGgeW5/ILS8lXQzLzqxtLwTnVIZ/Xlgi3DHy
+OwXP5obPM7JTTqhoQv2Lmhh/RBn/70EfisPUkPWjYVj7A19WwSo7JIiWopUM/Uzw
+Jyvq7AzEbyKl29pw5sLvCQKBgQC0GZuyLN4+UcITCtgqpLrasit7+SZBGlv27S/H
+K6KSKkndFfE0dH98NBz0UasQ9de/b3/w17jS//m2HiAlSXqFXmr34j7c9o173MPE
+/g6LkkWLAk++Z41c8i5MkteLO4MfXG9JxForJpdmfpibUvM5sIKddLhPtn6QbZ8H
+4yFCMQKBgQCgojMUEo3YQLh35sFFGuUemqmCU7/xj6BrjRX49o90MWXIeDIR2EhP
+nCGRqeOfiUR69ZrjOohlt//2PWmlE1Y8v/pKmwfqZdB/xtn3klxJaAy708TrjDe2
++0PzPXuFP5iyIxa44mEakRvw9RbCvXg5VNiwztHoWj+Om8P9+LMCgw==
+-----END RSA PRIVATE KEY-----`
+)
+
+type WeWorkMsgAuditClient struct {
+	Client  *msgaudit.Client
+}
+
+type WeWorkExternalContactClient struct {
+	Client *workwx.WorkwxApp
+}
+
+type WeWorkMsgAuditApiClient struct {
+	Client *workwx.WorkwxApp
+}
+
+type WeWorkUserNameClient struct {
+	Client *workwx.WorkwxApp
+}
+
+func NewWeWorkMsgAuditClient() *WeWorkMsgAuditClient {
+	wc := wechat.NewWechat()
+	memory := cache.NewMemory()
+	//memory := cache.NewRedis(global.Redis)
+	cfg := &config.Config{
+		CorpID:         WeWorkCorpID,
+		CorpSecret:     WeWorkFinanceCorpSecret,
+		AgentID:        "",
+		Cache:          memory,
+		RasPrivateKey:  WeWorkFinanceCorpRasPrivateKey,
+		Token:          "",
+		EncodingAESKey: "",
+	}
+
+	wework := wc.GetWork(cfg)
+	client, err := wework.GetMsgAudit()
+	if err != nil {
+		return nil
+	}
+	return &WeWorkMsgAuditClient{
+		Client: client,
+	}
+}
+
+func NewWeWorkExternalContactClient() *WeWorkExternalContactClient {
+	client := workwx.New(WeWorkCorpID)
+
+	// work with individual apps
+	app := client.WithApp(WeWorkExternalContactCorpSecret, agentID)
+	return &WeWorkExternalContactClient{
+		Client: app,
+	}
+}
+
+func NewWeWorkUserNameClient() *WeWorkUserNameClient {
+	client := workwx.New(WeWorkCorpID)
+
+	// work with individual apps
+	app := client.WithApp(WeWorkUserNameAgentSecret, UserNameAgentID)
+	return &WeWorkUserNameClient{
+		Client: app,
+	}
+}
+
+func NewWeWorkMsgAuditApiClient() *WeWorkMsgAuditApiClient {
+	client := workwx.New(WeWorkCorpID)
+
+	// work with individual apps
+	app := client.WithApp(WeWorkFinanceCorpSecret, agentID)
+	return &WeWorkMsgAuditApiClient{
+		Client: app,
+	}
+}
+
+func (we *WeWorkExternalContactClient) GetExternalContact(externalUserid string) (ret *workwx.ExternalContactInfo, err error) {
+	//ret, err := we.Client.GetExternalContact("wmPhSiBwAA8bqgBzwlPcttfPZbZh7heQ")
+	ret, err = we.Client.GetExternalContact(externalUserid)
+	return
+}
+
+func (we *WeWorkExternalContactClient) BatchListExternalContact(userId string) {
+	//userId := "YanLiNa"
+	ret, err := we.Client.BatchListExternalContact(userId, "", 100)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	fmt.Println(ret)
+}
+
+func (wa *WeWorkMsgAuditApiClient) GetMsgAuditGroupChat(roomId string) (ret *workwx.MsgAuditGroupChat, err error){
+//	roomId := "wrPhSiBwAAqC4Ctn430KPc-TEu8UtmAw"
+	ret, err = wa.Client.GetMsgAuditGroupChat(roomId)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	fmt.Println(ret)
+	return
+}
+
+func (we *WeWorkUserNameClient) GetUser(userId string) (ret *workwx.UserInfo, err error) {
+	ret, err = we.Client.GetUser(userId)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	fmt.Println(ret)
+	return
+}

+ 84 - 0
services/wework/wework_audit_linux.go

@@ -0,0 +1,84 @@
+//go:build linux && cgo && msgaudit
+// +build linux,cgo,msgaudit
+
+package wework
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"github.com/silenceper/wechat/v2/work/msgaudit"
+	"hongze/hongze_open_api/utils"
+	"io/ioutil"
+	"os"
+	"path"
+)
+
+func (w *WeWorkMsgAuditClient) GetMsgAuditContent(seq, limit uint64, timeout int) (list []msgaudit.TextMessage, seqRes uint64, errMsg error) {
+	client := w.Client
+	//同步消息
+	chatDataList, err := client.GetChatData(seq, limit, "", "", timeout)
+	if err != nil {
+		utils.FileLog.Info(fmt.Sprintf("企业微信 会议存档消息同步失败:%v \n\n", err))
+		errMsg = errors.New("企业微信 会议存档消息同步失败:"+err.Error())
+		return
+	}
+	if len(chatDataList) == 0 {
+		return
+	}
+	for _, chatData := range chatDataList {
+		seqRes = chatData.Seq
+		//消息解密
+		chatInfo, err := client.DecryptData(chatData.EncryptRandomKey, chatData.EncryptChatMsg)
+		if err != nil {
+			utils.FileLog.Info(fmt.Sprintf("消息解密失败:%v \n", err))
+			errMsg = errors.New("企业微信 消息解密失败:"+err.Error())
+			return
+		}
+		if chatInfo.Type == "image" {
+			image, _ := chatInfo.GetImageMessage()
+			sdkFileID := image.Image.SdkFileID
+
+			isFinish := false
+			buffer := bytes.Buffer{}
+			indexBuf := ""
+			for !isFinish {
+				//获取媒体数据
+				mediaData, err := client.GetMediaData(indexBuf, sdkFileID, "", "", 5)
+				if err != nil {
+					utils.FileLog.Info(fmt.Sprintf("媒体数据拉取失败:%v \n", err))
+					errMsg = errors.New("企业微信 媒体数据拉取失败:"+err.Error())
+					return
+				}
+				buffer.Write(mediaData.Data)
+				if mediaData.IsFinish {
+					isFinish = mediaData.IsFinish
+				}
+				indexBuf = mediaData.OutIndexBuf
+			}
+			filePath, _ := os.Getwd()
+			filePath = path.Join(filePath, "test.png")
+			err := ioutil.WriteFile(filePath, buffer.Bytes(), 0666)
+			if err != nil {
+				utils.FileLog.Info(fmt.Sprintf("文件存储失败:%v \n", err))
+				errMsg = errors.New("企业微信 文件存储失败:"+err.Error())
+				return
+			}
+			break
+		}else if chatInfo.Type == "text" {
+			msg, err := chatInfo.GetTextMessage()
+			if err != nil {
+				utils.FileLog.Info(fmt.Sprintf("查询消息类型失败:%v \n", err))
+				errMsg = errors.New("企业微信 查询消息类型失败:"+err.Error())
+				return
+			}
+			utils.FileLog.Info(fmt.Sprintf("明文:%s \n", msg))
+			list = append(list, msg)
+		}
+	}
+
+	//释放SDK实例
+	client.Free()
+	return
+}
+

+ 14 - 0
services/wework/wework_audit_unsupport.go

@@ -0,0 +1,14 @@
+//go:build !linux || !cgo || !msgaudit
+// +build !linux !cgo !msgaudit
+
+package wework
+
+import (
+	"errors"
+	"github.com/silenceper/wechat/v2/work/msgaudit"
+)
+
+func (w *WeWorkMsgAuditClient) GetMsgAuditContent(seq, limit uint64, timeout int) (list []msgaudit.TextMessage, seqRes uint64, errMsg error) {
+	errMsg = errors.New("会话存档功能目前只支持Linux平台运行,并且打开设置CGO_ENABLED=1")
+	return
+}

+ 326 - 0
services/wework/wxbizmsgcrypt/wxbizmsgcrypt.go

@@ -0,0 +1,326 @@
+package wxbizmsgcrypt
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/xml"
+	"fmt"
+	"math/rand"
+	"sort"
+	"strings"
+)
+
+const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+const (
+	ValidateSignatureError int = -40001
+	ParseXmlError          int = -40002
+	ComputeSignatureError  int = -40003
+	IllegalAesKey          int = -40004
+	ValidateCorpidError    int = -40005
+	EncryptAESError        int = -40006
+	DecryptAESError        int = -40007
+	IllegalBuffer          int = -40008
+	EncodeBase64Error      int = -40009
+	DecodeBase64Error      int = -40010
+	GenXmlError            int = -40010
+	ParseJsonError         int = -40012
+	GenJsonError           int = -40013
+	IllegalProtocolType    int = -40014
+)
+
+type ProtocolType int
+
+const (
+	XmlType ProtocolType = 1
+)
+
+type CryptError struct {
+	ErrCode int
+	ErrMsg  string
+}
+
+func NewCryptError(err_code int, err_msg string) *CryptError {
+	return &CryptError{ErrCode: err_code, ErrMsg: err_msg}
+}
+
+type WXBizMsg4Recv struct {
+	Tousername string `xml:"ToUserName"`
+	Encrypt    string `xml:"Encrypt"`
+	Agentid    string `xml:"AgentID"`
+}
+
+type CDATA struct {
+	Value string `xml:",cdata"`
+}
+
+type WXBizMsg4Send struct {
+	XMLName   xml.Name `xml:"xml"`
+	Encrypt   CDATA    `xml:"Encrypt"`
+	Signature CDATA    `xml:"MsgSignature"`
+	Timestamp string   `xml:"TimeStamp"`
+	Nonce     CDATA    `xml:"Nonce"`
+}
+
+type MsgContent struct {
+	ToUsername   string `xml:"ToUserName"`
+	FromUsername string `xml:"FromUserName"`
+	CreateTime   uint32 `xml:"CreateTime"`
+	MsgType      string `xml:"MsgType"`
+	Content      string `xml:"Content"`
+	MsgId        string `xml:"MsgId"`
+	AgentId      uint32 `xml:"AgentId"`
+	Event        string `xml:"Event"`
+}
+
+func NewWXBizMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizMsg4Send {
+	return &WXBizMsg4Send{Encrypt: CDATA{Value: encrypt}, Signature: CDATA{Value: signature}, Timestamp: timestamp, Nonce: CDATA{Value: nonce}}
+}
+
+type ProtocolProcessor interface {
+	parse(src_data []byte) (*WXBizMsg4Recv, *CryptError)
+	serialize(msg_send *WXBizMsg4Send) ([]byte, *CryptError)
+}
+
+type WXBizMsgCrypt struct {
+	token              string
+	encoding_aeskey    string
+	receiver_id        string
+	protocol_processor ProtocolProcessor
+}
+
+type XmlProcessor struct {
+}
+
+func (self *XmlProcessor) parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) {
+	var msg4_recv WXBizMsg4Recv
+	err := xml.Unmarshal(src_data, &msg4_recv)
+	if nil != err {
+		return nil, NewCryptError(ParseXmlError, "xml to msg fail")
+	}
+	return &msg4_recv, nil
+}
+
+func (self *XmlProcessor) serialize(msg4_send *WXBizMsg4Send) ([]byte, *CryptError) {
+	xml_msg, err := xml.Marshal(msg4_send)
+	if nil != err {
+		return nil, NewCryptError(GenXmlError, err.Error())
+	}
+	return xml_msg, nil
+}
+
+func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt {
+	var protocol_processor ProtocolProcessor
+	if protocol_type != XmlType {
+		panic("unsupport protocal")
+	} else {
+		protocol_processor = new(XmlProcessor)
+	}
+
+	return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor}
+}
+
+func (self *WXBizMsgCrypt) randString(n int) string {
+	b := make([]byte, n)
+	for i := range b {
+		b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
+	}
+	return string(b)
+}
+
+func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte {
+	padding := block_size - (len(plaintext) % block_size)
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	var buffer bytes.Buffer
+	buffer.WriteString(plaintext)
+	buffer.Write(padtext)
+	return buffer.Bytes()
+}
+
+func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) {
+	plaintext_len := len(plaintext)
+	if nil == plaintext || plaintext_len == 0 {
+		return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero")
+	}
+	if plaintext_len%block_size != 0 {
+		return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size")
+	}
+	padding_len := int(plaintext[plaintext_len-1])
+	return plaintext[:plaintext_len-padding_len], nil
+}
+
+func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) {
+	aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey)
+	if nil != err {
+		return nil, NewCryptError(DecodeBase64Error, err.Error())
+	}
+	const block_size = 32
+	pad_msg := self.pKCS7Padding(plaintext, block_size)
+
+	block, err := aes.NewCipher(aeskey)
+	if err != nil {
+		return nil, NewCryptError(EncryptAESError, err.Error())
+	}
+
+	ciphertext := make([]byte, len(pad_msg))
+	iv := aeskey[:aes.BlockSize]
+
+	mode := cipher.NewCBCEncrypter(block, iv)
+
+	mode.CryptBlocks(ciphertext, pad_msg)
+	base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext)))
+	base64.StdEncoding.Encode(base64_msg, ciphertext)
+
+	return base64_msg, nil
+}
+
+func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) {
+	aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey)
+	if nil != err {
+		return nil, NewCryptError(DecodeBase64Error, err.Error())
+	}
+
+	encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg)
+	if nil != err {
+		return nil, NewCryptError(DecodeBase64Error, err.Error())
+	}
+
+	block, err := aes.NewCipher(aeskey)
+	if err != nil {
+		return nil, NewCryptError(DecryptAESError, err.Error())
+	}
+
+	if len(encrypt_msg) < aes.BlockSize {
+		return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid")
+	}
+
+	iv := aeskey[:aes.BlockSize]
+
+	if len(encrypt_msg)%aes.BlockSize != 0 {
+		return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size")
+	}
+
+	mode := cipher.NewCBCDecrypter(block, iv)
+
+	mode.CryptBlocks(encrypt_msg, encrypt_msg)
+
+	return encrypt_msg, nil
+}
+
+func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string {
+	sort_arr := []string{self.token, timestamp, nonce, data}
+	sort.Strings(sort_arr)
+	var buffer bytes.Buffer
+	for _, value := range sort_arr {
+		buffer.WriteString(value)
+	}
+
+	sha := sha1.New()
+	sha.Write(buffer.Bytes())
+	signature := fmt.Sprintf("%x", sha.Sum(nil))
+	return string(signature)
+}
+
+func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) {
+	const block_size = 32
+	plaintext, err := self.pKCS7Unpadding(plaintext, block_size)
+	if nil != err {
+		return nil, 0, nil, nil, err
+	}
+
+	text_len := uint32(len(plaintext))
+	if text_len < 20 {
+		return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1")
+	}
+	random := plaintext[:16]
+	msg_len := binary.BigEndian.Uint32(plaintext[16:20])
+	if text_len < (20 + msg_len) {
+		return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2")
+	}
+
+	msg := plaintext[20 : 20+msg_len]
+	receiver_id := plaintext[20+msg_len:]
+
+	return random, msg_len, msg, receiver_id, nil
+}
+
+func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) {
+	signature := self.calSignature(timestamp, nonce, echostr)
+
+	if strings.Compare(signature, msg_signature) != 0 {
+		return nil, NewCryptError(ValidateSignatureError, "signature not equal")
+	}
+
+	plaintext, err := self.cbcDecrypter(echostr)
+	if nil != err {
+		return nil, err
+	}
+
+	_, _, msg, receiver_id, err := self.ParsePlainText(plaintext)
+	if nil != err {
+		return nil, err
+	}
+
+	if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 {
+		fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id))
+		return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil")
+	}
+
+	return msg, nil
+}
+
+func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) {
+	rand_str := self.randString(16)
+	var buffer bytes.Buffer
+	buffer.WriteString(rand_str)
+
+	msg_len_buf := make([]byte, 4)
+	binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg)))
+	buffer.Write(msg_len_buf)
+	buffer.WriteString(reply_msg)
+	buffer.WriteString(self.receiver_id)
+
+	tmp_ciphertext, err := self.cbcEncrypter(buffer.String())
+	if nil != err {
+		return nil, err
+	}
+	ciphertext := string(tmp_ciphertext)
+
+	signature := self.calSignature(timestamp, nonce, ciphertext)
+
+	msg4_send := NewWXBizMsg4Send(ciphertext, signature, timestamp, nonce)
+	return self.protocol_processor.serialize(msg4_send)
+}
+
+func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) {
+	msg4_recv, crypt_err := self.protocol_processor.parse(post_data)
+	if nil != crypt_err {
+		return nil, crypt_err
+	}
+
+	signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt)
+
+	if strings.Compare(signature, msg_signature) != 0 {
+		return nil, NewCryptError(ValidateSignatureError, "signature not equal")
+	}
+
+	plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt)
+	if nil != crypt_err {
+		return nil, crypt_err
+	}
+
+	_, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext)
+	if nil != crypt_err {
+		return nil, crypt_err
+	}
+
+	if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 {
+		return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil")
+	}
+
+	return msg, nil
+}

+ 2 - 0
utils/constants.go

@@ -101,6 +101,8 @@ const (
 //缓存key
 const (
 	CACHE_KEY_ADMIN = "calendar:admin:list" //系统用户列表缓存key
+	CACHE_KEY_DAYNEW_REFRESH     = "admin:day_new:refresh"           //每日资讯拉取企业微信聊天记录
+	CACHE_KEY_DAYNEW_TRANSLATE   = "admin:day_new:translate"         //每日资讯中翻英
 )
 
 const ALIYUN_YBIMG_HOST = "https://hzstatic.hzinsights.com/static/yb_wx/"

BIN
utils/lib/libWeWorkFinanceSdk_C.so