42 changed files with 2912 additions and 115 deletions
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
// 使用 IntelliSense 了解相关属性。 |
||||
|
// 悬停以查看现有属性的描述。 |
||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
|
"version": "0.2.0", |
||||
|
"configurations": [] |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
# weworkapi_cplusplus |
||||
|
official lib of wework api https://work.weixin.qq.com/api/doc |
||||
|
|
||||
|
# 注意事项 |
||||
|
|
||||
|
* 1.回调sdk json版本 |
||||
|
|
||||
|
* 2.wxbizjsonmsgcrypt.go文件中声明并实现了WXBizJsonMsgCrypt类,提供用户接入企业微信的三个接口。sample.go文件提供了如何使用这三个接口的示例。 |
||||
|
|
||||
|
* 3.WXBizJsonMsgCrypt类封装了VerifyURL, DecryptMsg, EncryptMsg三个接口,分别用于开发者验证回调url,收到用户回复消息的解密以及开发者回复消息的加密过程。使用方法可以参考sample.go文件。 |
||||
|
|
||||
|
* 4.加解密协议请参考企业微信官方文档。 |
||||
@ -0,0 +1,127 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"io/ioutil" |
||||
|
"log" |
||||
|
"net/http" |
||||
|
"net/url" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizjsonmsgcrypt" |
||||
|
) |
||||
|
|
||||
|
const token = "gY1AGR3mjBhzy" |
||||
|
const receiverId = "wwabfd0cec7171e769" |
||||
|
const encodingAeskey = "g8VGfQEqluUhoKOlyjmmll8Q9C5tVFUTX5T2qkmI9Sv" |
||||
|
|
||||
|
func getString(str, endstr string, start int, msg *string) int { |
||||
|
end := strings.Index(str, endstr) |
||||
|
*msg = str[start:end] |
||||
|
return end + len(endstr) |
||||
|
} |
||||
|
|
||||
|
func VerifyURL(w http.ResponseWriter, r *http.Request) { |
||||
|
//httpstr := `&{GET /?msg_signature=825075c093249d5a60967fe4a613cae93146636b×tamp=1597998748&nonce=1597483820&echostr=neLB8CftccHiz19tluVb%2BUBnUVMT3xpUMZU8qvDdD17eH8XfEsbPYC%2FkJyPsZOOc6GdsCeu8jSIa2noSJ%2Fez2w%3D%3D HTTP/1.1 1 1 map[Cache-Control:[no-cache] Accept:[*/*] Pragma:[no-cache] User-Agent:[Mozilla/4.0]] 0x86c180 0 [] false 100.108.211.112:8893 map[] map[] <nil> map[] 100.108.79.233:59663 /?msg_signature=825075c093249d5a60967fe4a613cae93146636b×tamp=1597998748&nonce=1597483820&echostr=neLB8CftccHiz19tluVb%2BUBnUVMT3xpUMZU8qvDdD17eH8XfEsbPYC%2FkJyPsZOOc6GdsCeu8jSIa2noSJ%2Fez2w%3D%3D <nil>}`
|
||||
|
fmt.Println(r, r.Body) |
||||
|
httpstr := r.URL.RawQuery |
||||
|
start := strings.Index(httpstr, "msg_signature=") |
||||
|
start += len("msg_signature=") |
||||
|
|
||||
|
var msg_signature string |
||||
|
next := getString(httpstr, "×tamp=", start, &msg_signature) |
||||
|
|
||||
|
var timestamp string |
||||
|
next = getString(httpstr, "&nonce=", next, ×tamp) |
||||
|
|
||||
|
var nonce string |
||||
|
next = getString(httpstr, "&echostr=", next, &nonce) |
||||
|
|
||||
|
echostr := httpstr[next:len(httpstr)] |
||||
|
|
||||
|
echostr, _ = url.QueryUnescape(echostr) |
||||
|
fmt.Println(msg_signature, timestamp, nonce, echostr, next) |
||||
|
|
||||
|
wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizjsonmsgcrypt.JsonType) |
||||
|
echoStr, cryptErr := wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("verifyUrl fail", cryptErr) |
||||
|
} |
||||
|
fmt.Println("verifyUrl success echoStr", string(echoStr)) |
||||
|
fmt.Fprintf(w, string(echoStr)) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
type MsgContent struct { |
||||
|
ToUsername string `json:"ToUserName"` |
||||
|
FromUsername string `json:"FromUserName"` |
||||
|
CreateTime uint32 `json:"CreateTime"` |
||||
|
MsgType string `json:"MsgType"` |
||||
|
Content string `json:"Content"` |
||||
|
Msgid uint64 `json:"MsgId"` |
||||
|
Agentid uint32 `json:"AgentId"` |
||||
|
} |
||||
|
|
||||
|
func MsgHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
httpstr := r.URL.RawQuery |
||||
|
start := strings.Index(httpstr, "msg_signature=") |
||||
|
start += len("msg_signature=") |
||||
|
|
||||
|
var msg_signature string |
||||
|
next := getString(httpstr, "×tamp=", start, &msg_signature) |
||||
|
|
||||
|
var timestamp string |
||||
|
next = getString(httpstr, "&nonce=", next, ×tamp) |
||||
|
|
||||
|
nonce := httpstr[next:len(httpstr)] |
||||
|
fmt.Println(msg_signature, timestamp, nonce) |
||||
|
|
||||
|
body, err := ioutil.ReadAll(r.Body) |
||||
|
fmt.Println(string(body), err) |
||||
|
wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizjsonmsgcrypt.JsonType) |
||||
|
|
||||
|
msg, err_ := wxcpt.DecryptMsg(msg_signature, timestamp, nonce, body) |
||||
|
fmt.Println(string(msg), err_) |
||||
|
var msgContent MsgContent |
||||
|
err = json.Unmarshal(msg, &msgContent) |
||||
|
if nil != err { |
||||
|
fmt.Println("Unmarshal fail", err) |
||||
|
} else { |
||||
|
fmt.Println("struct", msgContent) |
||||
|
} |
||||
|
|
||||
|
fmt.Println(msgContent, err) |
||||
|
ToUsername := msgContent.ToUsername |
||||
|
msgContent.ToUsername = msgContent.FromUsername |
||||
|
msgContent.FromUsername = ToUsername |
||||
|
fmt.Println("replaymsg", msgContent) |
||||
|
replayJson, err := json.Marshal(&msgContent) |
||||
|
|
||||
|
encryptMsg, cryptErr := wxcpt.EncryptMsg(string(replayJson), "1409659589", "1409659589") |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("DecryptMsg fail", cryptErr) |
||||
|
} |
||||
|
|
||||
|
sEncryptMsg := string(encryptMsg) |
||||
|
|
||||
|
fmt.Println("after encrypt sEncryptMsg: ", sEncryptMsg) |
||||
|
fmt.Fprintf(w, sEncryptMsg) |
||||
|
} |
||||
|
|
||||
|
func CallbackHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
httpstr := r.URL.RawQuery |
||||
|
echo := strings.Index(httpstr, "echostr") |
||||
|
if echo != -1 { |
||||
|
VerifyURL(w, r) |
||||
|
} else { |
||||
|
MsgHandler(w, r) |
||||
|
} |
||||
|
|
||||
|
fmt.Println("finished CallbackHandler", httpstr) |
||||
|
} |
||||
|
|
||||
|
func main() { |
||||
|
http.HandleFunc("/", CallbackHandler) // 设置访问路由
|
||||
|
log.Fatal(http.ListenAndServe(":8893", nil)) |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
|
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizjsonmsgcrypt" |
||||
|
) |
||||
|
|
||||
|
// type MsgContent struct {
|
||||
|
// ToUsername string `json:"ToUserName"`
|
||||
|
// FromUsername string `json:"FromUserName"`
|
||||
|
// CreateTime uint32 `json:"CreateTime"`
|
||||
|
// MsgType string `json:"MsgType"`
|
||||
|
// Content string `json:"Content"`
|
||||
|
// Msgid uint64 `json:"MsgId"`
|
||||
|
// Agentid uint32 `json:"AgentId"`
|
||||
|
// }
|
||||
|
|
||||
|
func mains() { |
||||
|
token := "QDG6eK" |
||||
|
receiverId := "wx5823bf96d3bd56c7" |
||||
|
encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" |
||||
|
wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizjsonmsgcrypt.JsonType) |
||||
|
/* |
||||
|
------------使用示例一:验证回调URL--------------- |
||||
|
*企业开启回调模式时,企业微信会向验证url发送一个get请求 |
||||
|
假设点击验证时,企业收到类似请求: |
||||
|
* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D |
||||
|
* HTTP/1.1 Host: qy.weixin.qq.com |
||||
|
|
||||
|
接收到该请求时,企业应 |
||||
|
1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), |
||||
|
这一步注意作URL解码。 |
||||
|
2.验证消息体签名的正确性 |
||||
|
3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 |
||||
|
第2,3步可以用企业微信提供的库函数VerifyURL来实现。 |
||||
|
|
||||
|
*/ |
||||
|
// 解析出url上的参数值如下:
|
||||
|
// verifyMsgSign := HttpUtils.ParseUrl("msg_signature")
|
||||
|
verifyMsgSign := "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3" |
||||
|
// verifyTimestamp := HttpUtils.ParseUrl("timestamp")
|
||||
|
verifyTimestamp := "1409659589" |
||||
|
// verifyNonce := HttpUtils.ParseUrl("nonce")
|
||||
|
verifyNonce := "263014780" |
||||
|
// verifyEchoStr := HttpUtils.ParseUrl("echoStr")
|
||||
|
verifyEchoStr := "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==" |
||||
|
echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("verifyUrl fail", cryptErr) |
||||
|
} |
||||
|
fmt.Println("verifyUrl success echoStr", string(echoStr)) |
||||
|
// 验证URL成功,将sEchoStr返回
|
||||
|
// HttpUtils.SetResponse(sEchoStr)
|
||||
|
|
||||
|
/* |
||||
|
------------使用示例二:对用户回复的消息解密--------------- |
||||
|
用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 |
||||
|
假设企业收到企业微信的回调消息如下: |
||||
|
POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 |
||||
|
Host: qy.weixin.qq.com |
||||
|
Content-Length: 613 |
||||
|
{ |
||||
|
"tousername":"wx5823bf96d3bd56c7", |
||||
|
"encrypt":"CZWs4CWRpI4VolQlvn4dlPBlXke6+HgmuI7p0LueFp1fKH40TNL+YHWJZwqIiYV+3kTrhdNU7fZwc+PmtgBvxSczkFeRz+oaVSsomrrtP2Z91LE313djjbWujqInRT+7ChGbCeo7ZzszByf8xnDSunPBxRX1MfX3kAxpKq7dqduW1kpMAx8O8xUzZ9oC0TLuZchbpxaml4epzGfF21O+zyXDwTxbCEiO0E87mChtzuh/VPlznXYbfqVrnyLNZ5pr", |
||||
|
"agentid":"218" |
||||
|
} |
||||
|
|
||||
|
企业收到post请求之后应该: |
||||
|
1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) |
||||
|
2.验证消息体签名的正确性。 |
||||
|
3.将post请求的数据进行json解析,并将"Encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 |
||||
|
第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 |
||||
|
*/ |
||||
|
|
||||
|
// reqMsgSign := HttpUtils.ParseUrl("msg_signature")
|
||||
|
reqMsgSign := "0623cbc5a8cbee5bcc137c70de99575366fc2af3" |
||||
|
// reqTimestamp := HttpUtils.ParseUrl("timestamp")
|
||||
|
reqTimestamp := "1409659813" |
||||
|
// reqNonce := HttpUtils.ParseUrl("nonce")
|
||||
|
reqNonce := "1372623149" |
||||
|
// post请求的密文数据
|
||||
|
// reqData = HttpUtils.PostData()
|
||||
|
|
||||
|
reqData := []byte(`{"tousername":"wx5823bf96d3bd56c7","encrypt":"CZWs4CWRpI4VolQlvn4dlEC1alN2MUEY2VklGehgBVLBrlVF7SyT+SV+Toj43l4ayJ9UMGKphktKKmP7B2j/P1ey67XB8PBgS7Wr5/8+w/yWriZv3Vmoo/MH3/1HsIWZrPQ3N2mJrelStIfI2Y8kLKXA7EhfZgZX4o+ffdkZDM76SEl79Ib9mw7TGjZ9Aw/x/A2VjNbV1E8BtEbRxYYcQippYNw7hr8sFfa3nW1xLdxokt8QkRX83vK3DFP2F6TQFPL2Tu98UwhcUpPvdJBuu1/yiOQIScppV3eOuLWEsko=","agentid":"218"}`) |
||||
|
|
||||
|
msg, cryptErr := wxcpt.DecryptMsg(reqMsgSign, reqTimestamp, reqNonce, reqData) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("DecryptMsg fail", cryptErr) |
||||
|
} |
||||
|
fmt.Println("after decrypt msg: ", string(msg)) |
||||
|
// TODO: 解析出明文json标签的内容进行处理
|
||||
|
// For example:
|
||||
|
|
||||
|
var msgContent MsgContent |
||||
|
err := json.Unmarshal(msg, &msgContent) |
||||
|
if nil != err { |
||||
|
fmt.Println("Unmarshal fail", err) |
||||
|
} else { |
||||
|
fmt.Println("struct", msgContent) |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
------------使用示例三:企业回复用户消息的加密--------------- |
||||
|
企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。 |
||||
|
假设企业需要回复用户的明文如下: |
||||
|
|
||||
|
{ |
||||
|
"ToUserName": "mycreate", |
||||
|
"FromUserName":"wx5823bf96d3bd56c7", |
||||
|
"CreateTime": 1348831860, |
||||
|
"MsgType": "text", |
||||
|
"Content": "this is a test", |
||||
|
"MsgId": 1234567890123456, |
||||
|
"AgentID": 128 |
||||
|
} |
||||
|
|
||||
|
为了将此段明文回复给用户,企业应: |
||||
|
1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 |
||||
|
2.将明文加密得到密文。 |
||||
|
3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 |
||||
|
4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业。 |
||||
|
以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 |
||||
|
*/ |
||||
|
respData := "{\"ToUserName\":\"wx5823bf96d3bd56c7\",\"FromUserName\":\"mycreate\",\"CreateTime\": 1409659813,\"MsgType\":\"text\",\"Content\":\"hello\",\"MsgId\":4561255354251345929,\"AgentID\": 218}" |
||||
|
//respData := `{"ToUserName":"wx5823bf96d3bd56c7","FromUserName":"mycreate","CreateTime": 1409659813,"MsgType":"text","Content":"hello","MsgId":4561255354251345929,"AgentID": 218}`
|
||||
|
//respData := `{"FromUserName":"mycreate","CreateTime": 1409659813,"MsgType":"text","Content":"hello","MsgId":4561255354251345929,"AgentID": 218}`
|
||||
|
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)
|
||||
|
} |
||||
@ -0,0 +1,313 @@ |
|||||
|
package wxbizjsonmsgcrypt |
||||
|
|
||||
|
import( |
||||
|
"crypto/sha1" |
||||
|
"crypto/aes" |
||||
|
"crypto/cipher" |
||||
|
"bytes" |
||||
|
"strings" |
||||
|
"fmt" |
||||
|
"sort" |
||||
|
"encoding/base64" |
||||
|
"math/rand" |
||||
|
"encoding/binary" |
||||
|
"encoding/json" |
||||
|
) |
||||
|
|
||||
|
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
||||
|
|
||||
|
const ( |
||||
|
ValidateSignatureError int = -40001 |
||||
|
ParseJsonError 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 |
||||
|
GenJsonError int = -40011 |
||||
|
IllegalProtocolType int = -40012 |
||||
|
) |
||||
|
|
||||
|
type ProtocolType int |
||||
|
const ( |
||||
|
JsonType 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 WXBizJsonMsg4Recv struct { |
||||
|
Tousername string `json:"tousername"` |
||||
|
Encrypt string `json:"encrypt"` |
||||
|
Agentid string `json:"agentid"` |
||||
|
} |
||||
|
|
||||
|
|
||||
|
type WXBizJsonMsg4Send struct { |
||||
|
Encrypt string `json:"encrypt"` |
||||
|
Signature string `json:"msgsignature"` |
||||
|
Timestamp string `json:"timestamp"` |
||||
|
Nonce string `json:"nonce"` |
||||
|
} |
||||
|
|
||||
|
func NewWXBizJsonMsg4Send(encrypt, signature, timestamp, nonce string) * WXBizJsonMsg4Send { |
||||
|
return &WXBizJsonMsg4Send{Encrypt:encrypt, Signature:signature, Timestamp:timestamp, Nonce:nonce} |
||||
|
} |
||||
|
|
||||
|
type ProtocolProcessor interface { |
||||
|
parse(src_data []byte) (* WXBizJsonMsg4Recv, * CryptError) |
||||
|
serialize(msg_send * WXBizJsonMsg4Send) ([]byte, * CryptError) |
||||
|
} |
||||
|
|
||||
|
type WXBizMsgCrypt struct{ |
||||
|
token string |
||||
|
encoding_aeskey string |
||||
|
receiver_id string |
||||
|
protocol_processor ProtocolProcessor |
||||
|
} |
||||
|
|
||||
|
type JsonProcessor struct{ |
||||
|
} |
||||
|
|
||||
|
func (self * JsonProcessor) parse(src_data []byte) (* WXBizJsonMsg4Recv, * CryptError) { |
||||
|
var msg4_recv WXBizJsonMsg4Recv |
||||
|
err := json.Unmarshal(src_data, &msg4_recv) |
||||
|
if nil != err { |
||||
|
fmt.Println("Unmarshal fail", err) |
||||
|
return nil, NewCryptError(ParseJsonError, "json to msg fail") |
||||
|
} |
||||
|
return &msg4_recv, nil |
||||
|
} |
||||
|
|
||||
|
func (self * JsonProcessor) serialize(msg4_send * WXBizJsonMsg4Send) ([]byte, * CryptError){ |
||||
|
json_msg, err := json.Marshal(msg4_send) |
||||
|
if nil != err { |
||||
|
return nil, NewCryptError(GenJsonError, err.Error()) |
||||
|
} |
||||
|
|
||||
|
return json_msg, nil |
||||
|
} |
||||
|
|
||||
|
func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) * WXBizMsgCrypt{ |
||||
|
var protocol_processor ProtocolProcessor |
||||
|
if protocol_type != JsonType { |
||||
|
panic("unsupport protocal") |
||||
|
} else { |
||||
|
protocol_processor = new(JsonProcessor) |
||||
|
} |
||||
|
|
||||
|
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 := NewWXBizJsonMsg4Send(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 |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,30 @@ |
|||||
|
package wechatcallback |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"encoding/xml" |
||||
|
"fmt" |
||||
|
|
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/commonus" |
||||
|
) |
||||
|
|
||||
|
func templateEventPush(eventMsg []byte) { |
||||
|
var msgContent TemplateCardPush |
||||
|
err := xml.Unmarshal(eventMsg, &msgContent) |
||||
|
if nil != err { |
||||
|
fmt.Println("***********Unmarshal fail") |
||||
|
} |
||||
|
fmt.Printf("button======>%v\n", msgContent) |
||||
|
|
||||
|
jsonStr, _ := json.Marshal(msgContent) |
||||
|
fmt.Printf("jsonStr======>%v\n", string(jsonStr)) |
||||
|
|
||||
|
var updateButtonNotClickable commonus.UpdateButtonNotClickable |
||||
|
updateButtonNotClickable.Userids = append(updateButtonNotClickable.Userids, "KaiXinGuo") |
||||
|
updateButtonNotClickable.Atall = 0 |
||||
|
updateButtonNotClickable.Agentid = msgContent.Agentid |
||||
|
updateButtonNotClickable.ResponseCode = msgContent.ResponseCode |
||||
|
updateButtonNotClickable.Button.ReplaceName = "已提交更新" |
||||
|
callbakcMsg, isTrueCall, callBackCont := updateButtonNotClickable.UpdateSendButtonMessage() |
||||
|
fmt.Printf("更新销售发送信息返回:%v-----------%v----------->%v\n", string(callbakcMsg), isTrueCall, callBackCont) |
||||
|
} |
||||
@ -1,31 +1,255 @@ |
|||||
package wechatcallback |
package wechatcallback |
||||
|
|
||||
import ( |
import ( |
||||
|
"encoding/json" |
||||
|
"encoding/xml" |
||||
|
"fmt" |
||||
|
"net/http" |
||||
|
"strconv" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/commonus" |
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/global" |
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response" |
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response" |
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/model/wechatcallback" |
||||
"github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizmsgcrypt" |
"github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizmsgcrypt" |
||||
"github.com/gin-gonic/gin" |
"github.com/gin-gonic/gin" |
||||
) |
) |
||||
|
|
||||
type WeChatCallBackApi struct{} |
|
||||
|
|
||||
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"` |
|
||||
} |
|
||||
|
|
||||
//回调首页
|
//回调首页
|
||||
func (w *WeChatCallBackApi) Index(c *gin.Context) { |
func (w *WeChatCallBackApi) Index(c *gin.Context) { |
||||
|
|
||||
|
// token := global.GVA_CONFIG.WorkWechatId.Token
|
||||
|
// receiverId := global.GVA_CONFIG.WorkWechatId.CompanyId
|
||||
|
// encodingAeskey := global.GVA_CONFIG.WorkWechatId.EncodingAESKey
|
||||
|
|
||||
token := "QDG6eK" |
token := "QDG6eK" |
||||
receiverId := "wx5823bf96d3bd56c7" |
receiverId := "wx5823bf96d3bd56c7" |
||||
encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" |
encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" |
||||
|
|
||||
wxcpt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) |
wxcpt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) |
||||
|
|
||||
response.Result(0, wxcpt, "获取成功", c) |
MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。
|
||||
|
Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。
|
||||
|
Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。
|
||||
|
Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文
|
||||
|
|
||||
|
var xmlStr CallBackVerificationXml |
||||
|
xmlErr := c.ShouldBindXML(&xmlStr) |
||||
|
|
||||
|
sendData := commonus.MapOut() |
||||
|
|
||||
|
sendData["wxcpt"] = wxcpt |
||||
|
|
||||
|
sendData["msg_signature"] = MsgSignature |
||||
|
sendData["timestamp"] = Timestamp |
||||
|
sendData["nonce"] = Nonce |
||||
|
sendData["echostr"] = Echostr |
||||
|
|
||||
|
sendData["xmlErr"] = xmlErr |
||||
|
sendData["xmlStr"] = xmlStr |
||||
|
|
||||
|
sendData["StatusBadRequest"] = http.StatusBadRequest |
||||
|
|
||||
|
echoStrs, cryptErr := wxcpt.VerifyURL(MsgSignature, Timestamp, Nonce, Echostr) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("verifyUrl fail", cryptErr) |
||||
|
} |
||||
|
// fmt.Println("verifyUrl success echoStr", string(echoStrs))
|
||||
|
sendData["echoStrsErr"] = cryptErr |
||||
|
sendData["echoStrrr"] = string(echoStrs) |
||||
|
|
||||
|
reqData := []byte("<xml><ToUserName><![CDATA[" + xmlStr.ToUserName.Text + "]]></ToUserName><Encrypt><![CDATA[" + xmlStr.Encrypt.Text + "]]></Encrypt><AgentID><![CDATA[" + xmlStr.AgentID.Text + "]]></AgentID></xml>") |
||||
|
msg, cryptErr := wxcpt.DecryptMsg(MsgSignature, Timestamp, Nonce, reqData) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("DecryptMsg fail", cryptErr) |
||||
|
} |
||||
|
// fmt.Println("after decrypt msg: ", string(msg))
|
||||
|
|
||||
|
sendData["cryptErr"] = cryptErr |
||||
|
sendData["cryptErrmsg"] = string(msg) |
||||
|
sendData["reqDataesddd"] = string(reqData) |
||||
|
|
||||
|
response.Result(0, sendData, "获取成功", c) |
||||
|
} |
||||
|
|
||||
|
//API接收消息
|
||||
|
func (w *WeChatCallBackApi) CallbackApiMessage(c *gin.Context) { |
||||
|
MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。
|
||||
|
Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。
|
||||
|
Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。
|
||||
|
Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文
|
||||
|
|
||||
|
var decryptCont CallBackData |
||||
|
decryptCont.MsgSignature = MsgSignature |
||||
|
timeStampInt, timeStampIntErr := strconv.ParseInt(Timestamp, 10, 64) |
||||
|
if timeStampIntErr == nil { |
||||
|
decryptCont.Timestamp = timeStampInt |
||||
|
} |
||||
|
decryptCont.Nonce = Nonce |
||||
|
|
||||
|
if Echostr != "" { |
||||
|
|
||||
|
decryptCont.Echostr = Echostr |
||||
|
msgStr := decryptCont.VerificationUrl() |
||||
|
c.String(200, msgStr) |
||||
|
// fmt.Printf("1------------>%v\n", Echostr)
|
||||
|
} else { |
||||
|
// fmt.Printf("2------------>%v\n", Echostr)
|
||||
|
var xmlMessageStr CallBackVerificationXml |
||||
|
xmlErr := c.ShouldBindXML(&xmlMessageStr) |
||||
|
if xmlErr != nil { |
||||
|
response.Result(101, xmlErr, "XML获取错误!", c) |
||||
|
return |
||||
|
} |
||||
|
decryptCont.ToUserName = xmlMessageStr.ToUserName.Text |
||||
|
decryptCont.Encrypt = xmlMessageStr.Encrypt.Text |
||||
|
decryptCont.AgentID = xmlMessageStr.AgentID.Text |
||||
|
decryptCont.DecryptMessage() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//启动企业微信验证程序
|
||||
|
func WechatVerification() (wxcpt *wxbizmsgcrypt.WXBizMsgCrypt) { |
||||
|
//正式数据
|
||||
|
token := "kkUA3s2s3" |
||||
|
// tokenEs := global.GVA_CONFIG.WorkWechatSchool.WechatTokening
|
||||
|
receiverId := global.GVA_CONFIG.WorkWechatId.CompanyId |
||||
|
encodingAeskey := global.GVA_CONFIG.WorkWechatSchool.EncodingAESKey |
||||
|
|
||||
|
// fmt.Printf("11111========>%v----->%v------>%v\n", token, receiverId, encodingAeskey)
|
||||
|
//测试数据
|
||||
|
// token := "QDG6eK"
|
||||
|
// receiverId := "wx5823bf96d3bd56c7"
|
||||
|
// encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"
|
||||
|
wxcpt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
//验证URL
|
||||
|
func (c *CallBackData) VerificationUrl() (msg string) { |
||||
|
wecahtCpt := WechatVerification() |
||||
|
timestampStr := strconv.FormatInt(c.Timestamp, 10) |
||||
|
echoStr, cryptErr := wecahtCpt.VerifyURL(c.MsgSignature, timestampStr, c.Nonce, c.Echostr) |
||||
|
var callbackLog wechatcallback.CallbackLog |
||||
|
//
|
||||
|
callbackLog.MsgSignature = c.MsgSignature |
||||
|
callbackLog.TimeStamp = c.Timestamp |
||||
|
callbackLog.Nonce = c.Nonce |
||||
|
callbackLog.Echostr = c.Echostr |
||||
|
callbackLog.Xmlstr = string(echoStr) |
||||
|
// callbackLog.Reqdata = string(reqData)
|
||||
|
callbackLog.AddTime = time.Now().Unix() |
||||
|
global.GVA_DB_WechatCallBack.Create(&callbackLog) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("verifyUrl fail", cryptErr) |
||||
|
} |
||||
|
msg = string(echoStr) |
||||
|
return |
||||
|
// fmt.Println(string(echoStr))
|
||||
|
// fmt.Print(string(echoStr))
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
//解析消息结构
|
||||
|
func (c *CallBackData) DecryptMessage() { |
||||
|
wecahtCpt := WechatVerification() |
||||
|
timestampStr := strconv.FormatInt(c.Timestamp, 10) |
||||
|
reqData := []byte("<xml><ToUserName><![CDATA[" + c.ToUserName + "]]></ToUserName><Encrypt><![CDATA[" + c.Encrypt + "]]></Encrypt><AgentID><![CDATA[" + c.AgentID + "]]></AgentID></xml>") |
||||
|
msg, cryptErr := wecahtCpt.DecryptMsg(c.MsgSignature, timestampStr, c.Nonce, reqData) |
||||
|
// fmt.Printf("%v=====>%v=====>%v\n: ", c.ToUserName, c.Encrypt, c.AgentID)
|
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("DecryptMsg fail", cryptErr) |
||||
|
} |
||||
|
// fmt.Println("after decrypt msg: ", string(msg))
|
||||
|
var msgContent MsgContent |
||||
|
err := xml.Unmarshal(msg, &msgContent) |
||||
|
if nil != err { |
||||
|
fmt.Println("Unmarshal fail") |
||||
|
} |
||||
|
fmt.Printf("1========>%v========>%v\n ", msgContent.MsgType, msgContent.Event) |
||||
|
switch msgContent.MsgType { |
||||
|
/*消息格式类型 |
||||
|
*/ |
||||
|
case "text": //文本
|
||||
|
case "image": //图片
|
||||
|
case "voice": //语音
|
||||
|
case "video": //视频
|
||||
|
case "location": //位置
|
||||
|
case "link": //链接
|
||||
|
/*事件格式类型*/ |
||||
|
case "event": |
||||
|
/* |
||||
|
事件附属格式 |
||||
|
*/ |
||||
|
EventProcessing(msgContent.Event, msg) |
||||
|
|
||||
|
default: |
||||
|
} |
||||
|
|
||||
|
var callbackLog wechatcallback.CallbackLog |
||||
|
//
|
||||
|
callbackLog.MsgSignature = c.MsgSignature |
||||
|
callbackLog.TimeStamp = c.Timestamp |
||||
|
callbackLog.Nonce = c.Nonce |
||||
|
callbackLog.Echostr = c.Echostr |
||||
|
callbackLog.Xmlstr = string(msg) |
||||
|
callbackLog.Reqdata = string(reqData) |
||||
|
msgCont, jsonErr := json.Marshal(msgContent) |
||||
|
if jsonErr == nil { |
||||
|
callbackLog.Jsonstr = string(msgCont) |
||||
|
} |
||||
|
callbackLog.AddTime = time.Now().Unix() |
||||
|
global.GVA_DB_WechatCallBack.Create(&callbackLog) |
||||
|
} |
||||
|
|
||||
|
//企业微信事件处理
|
||||
|
func EventProcessing(event string, decryptMsg []byte) { |
||||
|
var msgContent MsgContentMailList |
||||
|
err := xml.Unmarshal(decryptMsg, &msgContent) |
||||
|
if nil != err { |
||||
|
fmt.Println("Unmarshal fail") |
||||
|
} |
||||
|
switch event { |
||||
|
case "subscribe": //关注
|
||||
|
case "unsubscribe": //取消关注
|
||||
|
case "enter_agent": //本事件在成员进入企业微信的应用时触发
|
||||
|
case "LOCATION": //上报地理位置
|
||||
|
case "batch_job_result": //异步任务完成事件推送
|
||||
|
case "change_contact": //通讯录变更事件
|
||||
|
WorkWechatMailList(msgContent.ChangeType, decryptMsg) |
||||
|
case "click": //点击菜单拉取消息的事件推送
|
||||
|
case "view": //点击菜单跳转链接的事件推送
|
||||
|
case "scancode_push": //扫码推事件的事件推送
|
||||
|
case "scancode_waitmsg": //扫码推事件且弹出“消息接收中”提示框的事件推送
|
||||
|
case "pic_sysphoto": //弹出系统拍照发图的事件推送
|
||||
|
case "pic_photo_or_album": //弹出拍照或者相册发图的事件推送
|
||||
|
case "pic_weixin": //弹出微信相册发图器的事件推送
|
||||
|
case "location_select": //弹出地理位置选择器的事件推送
|
||||
|
case "open_approval_change": //审批状态通知事件
|
||||
|
case "share_agent_change": //企业互联共享应用事件回调
|
||||
|
case "share_chain_change": //上下游共享应用事件回调
|
||||
|
case "template_card_event": //模板卡片事件推送
|
||||
|
templateEventPush(decryptMsg) |
||||
|
case "template_card_menu_event": //通用模板卡片右上角菜单事件推送
|
||||
|
default: |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//企业微信通讯录变更事件处理
|
||||
|
func WorkWechatMailList(changeType string, decryptMsg []byte) { |
||||
|
switch changeType { |
||||
|
case "create_party": //新增部门事件
|
||||
|
case "update_party": //更新部门事件
|
||||
|
case "delete_party": //删除部门事件
|
||||
|
case "update_tag": //标签成员变更事件
|
||||
|
case "batch_job_result": //异步任务完成事件推送
|
||||
|
case "change_contact": //通讯录变更事件
|
||||
|
|
||||
|
case "create_user": //新增成员事件
|
||||
|
case "update_user": //更新成员事件
|
||||
|
case "delete_user": //删除成员事件
|
||||
|
|
||||
|
default: |
||||
|
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,14 @@ |
|||||
|
package wechatcallback |
||||
|
|
||||
|
//企业微信回调基础参数
|
||||
|
type CallBackData struct { |
||||
|
MsgSignature string `json:"msg_signature"` |
||||
|
Timestamp int64 `json:"timestamp"` |
||||
|
Nonce string `json:"nonce"` |
||||
|
Echostr string `json:"echostr"` |
||||
|
ToUserName string `json:"tousername"` |
||||
|
AgentID string `json:"agentid"` |
||||
|
Encrypt string `json:"encrypt"` |
||||
|
} |
||||
|
|
||||
|
//更新按钮为不可点击状态
|
||||
@ -0,0 +1,77 @@ |
|||||
|
package wechatcallback |
||||
|
|
||||
|
type WeChatCallBackApi struct{} |
||||
|
|
||||
|
//通用更新切片
|
||||
|
type CurrencyMessage struct { |
||||
|
ToUsername string `xml:"ToUserName"` |
||||
|
FromUsername string `xml:"FromUserName"` |
||||
|
CreateTime uint32 `xml:"CreateTime"` |
||||
|
MsgType string `xml:"MsgType"` |
||||
|
Agentid uint32 `xml:"AgentID"` |
||||
|
Event string `xml:"Event"` |
||||
|
EventKey string `xml:"EventKey"` |
||||
|
// TaskId string `xml:"TaskId"`
|
||||
|
} |
||||
|
|
||||
|
type MsgContent struct { |
||||
|
CurrencyMessage |
||||
|
Content string `xml:"Content"` |
||||
|
Msgid string `xml:"MsgId"` |
||||
|
} |
||||
|
|
||||
|
//通讯录主表
|
||||
|
type MsgContentMailList struct { |
||||
|
MsgContent |
||||
|
ChangeType string `xml:"ChangeType"` |
||||
|
} |
||||
|
|
||||
|
//企业微信回调验证
|
||||
|
type CallBackVerificationXml struct { |
||||
|
ToUserName struct { |
||||
|
CallBackText |
||||
|
} `xml:"ToUserName"` |
||||
|
AgentID struct { |
||||
|
CallBackText |
||||
|
} `xml:"AgentID"` |
||||
|
Encrypt struct { |
||||
|
CallBackText |
||||
|
} `xml:"Encrypt"` |
||||
|
} |
||||
|
type CallBackText struct { |
||||
|
Text string `xml:",chardata"` |
||||
|
} |
||||
|
|
||||
|
type Login struct { |
||||
|
User string `form:"user" json:"user" xml:"user" binding:"required"` |
||||
|
Password string `form:"password" json:"password" xml:"password" binding:"required"` |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
模板卡片事件推送 |
||||
|
*/ |
||||
|
//通用模板卡片右上角菜单事件推送
|
||||
|
type TemplateCardPushCurrency struct { |
||||
|
CurrencyMessage |
||||
|
TaskId string `xml:"TaskId"` |
||||
|
CardType string `xml:"CardType"` |
||||
|
ResponseCode string `xml:"ResponseCode"` |
||||
|
} |
||||
|
|
||||
|
//模板卡片事件推送
|
||||
|
type TemplateCardPush struct { |
||||
|
TemplateCardPushCurrency |
||||
|
SelectedItems SelectedItemStruct `xml:"SelectedItems"` |
||||
|
} |
||||
|
|
||||
|
//卡片右上角事件
|
||||
|
type SelectedItemStruct struct { |
||||
|
SelectedItem []SelectedItemList `xml:"SelectedItem"` |
||||
|
} |
||||
|
type SelectedItemList struct { |
||||
|
QuestionKey string `xml:"QuestionKey"` |
||||
|
OptionIds []OptionIdsStr `xml:"OptionIds"` |
||||
|
} |
||||
|
type OptionIdsStr struct { |
||||
|
OptionId string `xml:"OptionId"` |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
package callback |
||||
|
|
||||
|
//企业微信回调
|
||||
|
type WorkWeChatCallBackApi struct{} |
||||
@ -0,0 +1,6 @@ |
|||||
|
package callback |
||||
|
|
||||
|
//企业微信回调
|
||||
|
type ApiGroup struct { |
||||
|
WorkWeChatApi WorkWeChatCallBackApi |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
package callback |
||||
|
|
||||
|
import "github.com/gin-gonic/gin" |
||||
|
|
||||
|
//验证URL
|
||||
|
func (w *WorkWeChatCallBackApi) Index(c *gin.Context) { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
package workwechatcallback |
||||
|
|
||||
|
import "github.com/flipped-aurora/gin-vue-admin/server/api/workwechatcallback/callback" |
||||
|
|
||||
|
//企业微信回调
|
||||
|
type ApiGroup struct { |
||||
|
WorkWeChatApi callback.ApiGroup |
||||
|
} |
||||
|
|
||||
|
var ApiGroupApp = new(ApiGroup) |
||||
@ -0,0 +1,17 @@ |
|||||
|
# Binaries for programs and plugins |
||||
|
*.exe |
||||
|
*.exe~ |
||||
|
*.dll |
||||
|
*.so |
||||
|
*.dylib |
||||
|
|
||||
|
# Test binary, build with `go test -c` |
||||
|
*.test |
||||
|
|
||||
|
# Output of the go coverage tool, specifically when used with LiteIDE |
||||
|
*.out |
||||
|
|
||||
|
# IDE |
||||
|
.idea |
||||
|
.vs |
||||
|
.vscode |
||||
@ -0,0 +1,36 @@ |
|||||
|
## weworkapi_golang |
||||
|
|
||||
|
weworkapi_golang 是为了简化开发者对企业微信API接口的使用而设计的,API调用加解密库之golang版本 |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
将本项目下载到你的目录,既可直接引用相关文件 |
||||
|
详细使用方法参考[sample.go](https://github.com/sbzhu/weworkapi_golang/blob/master/sample.go)代码 |
||||
|
|
||||
|
## About |
||||
|
|
||||
|
**本库仅做示范用,并不保证完全无bug** |
||||
|
|
||||
|
作者会不定期更新本库,但不保证与官方API接口文档同步,因此一切以[官方文档](https://work.weixin.qq.com/api/doc)为准。 |
||||
|
|
||||
|
更多来自个人开发者的其它语言的库推荐: |
||||
|
|
||||
|
python: |
||||
|
|
||||
|
* https://github.com/sbzhu/weworkapi_python abelzhu@tencent.com(企业微信团队) |
||||
|
|
||||
|
ruby: |
||||
|
|
||||
|
* https://github.com/mycolorway/wework MyColorway(个人开发者) |
||||
|
|
||||
|
php: |
||||
|
|
||||
|
* https://github.com/sbzhu/weworkapi_php abelzhu@tencent.com; xiqunpan@tencent.com(企业微信团队) |
||||
|
|
||||
|
golang: |
||||
|
|
||||
|
* https://github.com/sbzhu/weworkapi_golang ryanjelin@tencent.com(企业微信团队) |
||||
|
* https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail.com(个人开发者) |
||||
|
|
||||
|
## Contact us |
||||
|
ryanjelin@tencent.com |
||||
@ -0,0 +1,5 @@ |
|||||
|
module github.com/sbzhu/weworkapi_golang |
||||
|
|
||||
|
go 1.14 |
||||
|
|
||||
|
replace github.com/sbzhu/weworkapi_golang/wxbizmsgcrypt => ./wxbizmsgcrypt |
||||
@ -0,0 +1,132 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/xml" |
||||
|
"fmt" |
||||
|
|
||||
|
"github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizmsgcrypt" |
||||
|
) |
||||
|
|
||||
|
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"` |
||||
|
} |
||||
|
|
||||
|
func main() { |
||||
|
token := "QDG6eK" |
||||
|
receiverId := "wx5823bf96d3bd56c7" |
||||
|
encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" |
||||
|
wxcpt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) |
||||
|
/* |
||||
|
------------使用示例一:验证回调URL--------------- |
||||
|
*企业开启回调模式时,企业微信会向验证url发送一个get请求 |
||||
|
假设点击验证时,企业收到类似请求: |
||||
|
* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D |
||||
|
* HTTP/1.1 Host: qy.weixin.qq.com |
||||
|
|
||||
|
接收到该请求时,企业应 |
||||
|
1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), |
||||
|
这一步注意作URL解码。 |
||||
|
2.验证消息体签名的正确性 |
||||
|
3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 |
||||
|
第2,3步可以用企业微信提供的库函数VerifyURL来实现。 |
||||
|
|
||||
|
*/ |
||||
|
// 解析出url上的参数值如下:
|
||||
|
// verifyMsgSign := HttpUtils.ParseUrl("msg_signature")
|
||||
|
verifyMsgSign := "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3" |
||||
|
// verifyTimestamp := HttpUtils.ParseUrl("timestamp")
|
||||
|
verifyTimestamp := "1409659589" |
||||
|
// verifyNonce := HttpUtils.ParseUrl("nonce")
|
||||
|
verifyNonce := "263014780" |
||||
|
// verifyEchoStr := HttpUtils.ParseUrl("echoStr")
|
||||
|
verifyEchoStr := "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==" |
||||
|
echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr) |
||||
|
if nil != cryptErr { |
||||
|
fmt.Println("verifyUrl fail", cryptErr) |
||||
|
} |
||||
|
fmt.Println("verifyUrl success echoStr", string(echoStr)) |
||||
|
// 验证URL成功,将sEchoStr返回
|
||||
|
// HttpUtils.SetResponse(sEchoStr)
|
||||
|
|
||||
|
/* |
||||
|
------------使用示例二:对用户回复的消息解密--------------- |
||||
|
用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 |
||||
|
假设企业收到企业微信的回调消息如下: |
||||
|
POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=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 := HttpUtils.ParseUrl("msg_signature")
|
||||
|
reqMsgSign := "477715d11cdb4164915debcba66cb864d751f3e6" |
||||
|
// reqTimestamp := HttpUtils.ParseUrl("timestamp")
|
||||
|
reqTimestamp := "1409659813" |
||||
|
// reqNonce := HttpUtils.ParseUrl("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 { |
||||
|
fmt.Println("DecryptMsg fail", cryptErr) |
||||
|
} |
||||
|
fmt.Println("after decrypt msg: ", string(msg)) |
||||
|
// TODO: 解析出明文xml标签的内容进行处理
|
||||
|
// For example:
|
||||
|
|
||||
|
var msgContent MsgContent |
||||
|
err := xml.Unmarshal(msg, &msgContent) |
||||
|
if nil != err { |
||||
|
fmt.Println("Unmarshal fail") |
||||
|
} else { |
||||
|
fmt.Println("struct", msgContent) |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
------------使用示例三:企业回复用户消息的加密--------------- |
||||
|
企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的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)
|
||||
|
} |
||||
@ -0,0 +1,315 @@ |
|||||
|
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"` |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
package commonus |
||||
|
|
||||
|
//更新卡片公共部门
|
||||
|
type UpdateTemplateCardPublic struct { |
||||
|
Touser string `json:"touser" form:"touser"` |
||||
|
ToParty string `json:"toparty" form:"toparty"` |
||||
|
ToTag string `json:"totag" form:"totag"` |
||||
|
AgentId int64 `json:"agentid" form:"agentid"` |
||||
|
ResponseCode string `json:"response_code" form:"response_code"` |
||||
|
} |
||||
|
|
||||
|
//更新按钮为不可点击状态
|
||||
|
type UpdateTemplateCardNotClickAll struct { |
||||
|
UpdateTemplateCardPublic |
||||
|
TemplateCard UpdateButtonTempClickAll `json:"template_card"` |
||||
|
} |
||||
|
type UpdateTemplateCardNotClick struct { |
||||
|
UpdateTemplateCardPublic |
||||
|
TemplateCard UpdateButtonTempClick `json:"template_card"` |
||||
|
} |
||||
|
|
||||
|
//更新按钮模板
|
||||
|
type UpdateButtonTempClickAll struct { |
||||
|
CurrencyButtonTemplateAll |
||||
|
ReplaceText string `json:"replace_text" form:"replace_text"` |
||||
|
} |
||||
|
type UpdateButtonTempClick struct { |
||||
|
CurrencyButtonTemplateStruct |
||||
|
ReplaceText string `json:"replace_text" form:"replace_text"` |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
更新模版卡片消息 |
||||
|
*/ |
||||
|
//更新模板卡片通用部门
|
||||
|
type UpdateTemplateCardCurrency struct { |
||||
|
Userids []string `json:"userids"` //企业的成员ID列表(最多支持1000个)
|
||||
|
Partyids []int `json:"partyids"` //企业的部门ID列表(最多支持100个)
|
||||
|
Tagids []int `json:"tagids"` //企业的标签ID列表(最多支持100个)
|
||||
|
Atall int `json:"atall"` //更新整个任务接收人员
|
||||
|
Agentid uint32 `json:"agentid"` //应用的agentid
|
||||
|
ResponseCode string `json:"response_code"` |
||||
|
} |
||||
|
|
||||
|
//更新按钮为不可点击状态
|
||||
|
type UpdateButtonNotClickable struct { |
||||
|
UpdateTemplateCardCurrency |
||||
|
Button ButtonNotClick `json:"button"` |
||||
|
} |
||||
|
type ButtonNotClick struct { |
||||
|
ReplaceName string `json:"replace_name"` |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
package wechatcallback |
||||
|
|
||||
|
//企业微信回调记录
|
||||
|
type CallbackLog struct { |
||||
|
Id int64 `json:"id" gorm:"column:id;type:bigint(20);;primaryKey;unique;not null;autoIncrement;index"` |
||||
|
MsgSignature string `json:"msgSignature" gorm:"column:msg_signature;type:varchar(255);not null;comment:组织名称"` |
||||
|
TimeStamp int64 `json:"timestamp" gorm:"column:timestamp;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` |
||||
|
Nonce string `json:"nonce" gorm:"column:nonce;type:varchar(255);not null;comment:组织名称"` |
||||
|
Echostr string `json:"echostr" gorm:"column:echostr;type:varchar(255);not null;comment:组织名称"` |
||||
|
Xmlstr string `json:"xmlstr" gorm:"column:xmlstr;type:text;comment:组织名称"` |
||||
|
Jsonstr string `json:"jsonstr" gorm:"column:jsonstr;type:text;comment:组织名称"` |
||||
|
AddTime int64 `json:"addtime" gorm:"column:addtime;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` |
||||
|
Reqdata string `json:"reqdata" gorm:"column:reqdata;type:text;comment:组织名称"` |
||||
|
} |
||||
|
|
||||
|
func (CallbackLog *CallbackLog) TableName() string { |
||||
|
return "callback_log" |
||||
|
} |
||||
Loading…
Reference in new issue