Browse Source

企业微信回调

v1_dev_2
超级管理员 3 years ago
parent
commit
0b4c9cf121
  1. 2
      apirouter/entry.go
  2. 5
      apirouter/wechaturl/entry.go
  3. 19
      apirouter/wechaturl/wechatrouter.go
  4. 15
      config/configApp/server.go
  5. 2
      initialization/app/run.go
  6. 5
      initialization/route/initRoute.go
  7. 13
      middleware/wechatapp/entry.go
  8. 125
      middleware/wechatapp/wechatcallback/response.go
  9. 27
      middleware/wechatapp/wechatcallback/type.go
  10. 4
      middleware/wechatapp/wechatsendmsg/type.go
  11. 140
      middleware/wechatapp/wechatstatice/method.go
  12. 10
      middleware/wechatapp/wechatstatice/type.go
  13. 310
      middleware/wechatapp/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go
  14. 315
      middleware/wechatapp/wxbizmsgcrypt/wxbizmsgcrypt.go
  15. 18
      models/wechatcallback/callbacklog.go

2
apirouter/entry.go

@ -10,6 +10,7 @@ import (
"key_performance_indicators/apirouter/v1/postseting"
"key_performance_indicators/apirouter/v1/systempower"
"key_performance_indicators/apirouter/verifyLogin"
"key_performance_indicators/apirouter/wechaturl"
// "key_performance_indicators/v1"
)
@ -24,6 +25,7 @@ type RouterGroup struct {
BookImg bookimg.ApiRouter
SystemPowerRouter systempower.ApiRouter
Empowerouter empowerrouter.ApiRouter
WechatRouter wechaturl.ApiRouter
}
var RouterGroupEntry = new(RouterGroup)

5
apirouter/wechaturl/entry.go

@ -0,0 +1,5 @@
package wechaturl
//企业微信回调
type ApiRouter struct{}

19
apirouter/wechaturl/wechatrouter.go

@ -0,0 +1,19 @@
package wechaturl
import (
"key_performance_indicators/middleware/wechatapp"
"github.com/gin-gonic/gin"
)
// 企业微信回调路由
func (a *ApiRouter) RouterGroup(router *gin.RouterGroup) {
apiRouter := router.Group("wechatcallback")
var methodBinding = wechatapp.WechatAppApi.WechatCallBackApi
{
apiRouter.GET("", methodBinding.Index) //入口
apiRouter.POST("", methodBinding.Index) //入口
apiRouter.GET("callback_message_api", methodBinding.CallbackMessageApi) //回调入口
}
}

15
config/configApp/server.go

@ -6,6 +6,9 @@ type Server struct {
Logsetup logsetup `mapstructure:"logconfig" json:"logconfig" yaml:"logconfig"`
Captcha captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
RedisPrefixStr redisPrefixStr `mapstructure:"redisprefix" json:"redisprefix" yaml:"redisprefix"`
WechatCompany wechatCompany `mapstructure:"wechatcompany" json:"wechatcompany" yaml:"wechatcompany"` //企业ID
WechatSchool wechatConfig `mapstructure:"wechatschool" json:"wechatschool" yaml:"wechatschool"` //知行学院
WechatKpi wechatConfig `mapstructure:"wechatkpi" json:"wechatkpi" yaml:"wechatkpi"` //绩效考核
}
//服务配置详情
@ -34,3 +37,15 @@ type redisPrefixStr struct {
PreFix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // redis键前缀
Alias string `mapstructure:"alias" json:"alias" yaml:"alias"` // redis键前缀
}
//企业微信基础配置
type wechatCompany struct {
CompanyId string `mapstructure:"companyid" json:"companyid" yaml:"companyid"` // 企业ID
}
type wechatConfig struct {
Agentid string `mapstructure:"agentid" json:"agentid" yaml:"agentid"` // Agentid
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // Secret
Token string `mapstructure:"token" json:"token" yaml:"token"` // Token
Encodingaeskey string `mapstructure:"encodingaeskey" json:"encodingaeskey" yaml:"encodingaeskey"` // EncodingAESKey
}

2
initialization/app/run.go

@ -16,7 +16,7 @@ func RunItem() {
//加载基础配置
// var appConfig configApp.Server
initviper.RunViper(&overall.CONSTANT_CONFIG)
// fmt.Printf("----------->%v", overall.CONSTANT_CONFIG)
// fmt.Printf("CONSTANT_CONFIG----------->%v", overall.CONSTANT_CONFIG)
routers := route.InitialRouter()
portStr := fmt.Sprintf(":%d", overall.CONSTANT_CONFIG.Appsetup.Port)
startUp := InitServer(portStr, routers)

5
initialization/route/initRoute.go

@ -46,6 +46,11 @@ func InitialRouter() *gin.Engine {
// {
// mytestapi.RouterGroup(appLoadRouterGroup)
// }
//企业微信回调
wechaturlApi := apirouter.RouterGroupEntry.WechatRouter
{
wechaturlApi.RouterGroup(appLoadRouterGroup)
}
}
//验证身份接口 鉴权Url(主要应用端使用)

13
middleware/wechatapp/entry.go

@ -0,0 +1,13 @@
package wechatapp
import (
"key_performance_indicators/middleware/wechatapp/wechatcallback"
"key_performance_indicators/middleware/wechatapp/wechatsendmsg"
)
type WechatApp struct {
WechatCallBackApi wechatcallback.ApiRouter
WechatSendMsgApi wechatsendmsg.ApiRouter
}
var WechatAppApi = new(WechatApp)

125
middleware/wechatapp/wechatcallback/response.go

@ -0,0 +1,125 @@
package wechatcallback
import (
"fmt"
"key_performance_indicators/middleware/wechatapp/wechatstatice"
"key_performance_indicators/models/wechatcallback"
"key_performance_indicators/overall"
"key_performance_indicators/overall/publicmethod"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
// 企业微信回调入口
func (a *ApiRouter) Index(c *gin.Context) {
outputCont := publicmethod.MapOut[string]()
outputCont["index"] = "企业微信回调入口"
publicmethod.Result(0, outputCont, c)
}
/*
*
@ 作者: 秦东
@ 时间: 2022-09-27 11:33:29
@ 功能: 回调入口
@ 参数
#MsgSignature 企业微信加密签名msg_signature计算结合了企业填写的token请求中的timestampnonce加密的消息体
#Timestamp 时间戳与nonce结合使用用于防止请求重放攻击
#Nonce 随机数与timestamp结合使用用于防止请求重放攻击
#Echostr 加密的字符串需要解密得到消息内容明文解密后有randommsg_lenmsgreceiveid四个字段其中msg即为消息内容明文
@ 返回值
#
*/
func (a *ApiRouter) CallbackMessageApi(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即为消息内容明文
EchostrType := c.Query("type")
SystemApp := c.Query("systemapp")
if EchostrType == "" {
EchostrType = "json"
}
if SystemApp == "" {
SystemApp = "kpi"
}
// fmt.Printf("(1)SystemApp---------->%v--->EchostrType---------->%v--->MsgSignature---------->%v--->Timestamp---------->%v--->Nonce---------->%v--->Echostr---------->%v\n", SystemApp, EchostrType, MsgSignature, Timestamp, Nonce, Echostr)
var basicValueCallback CallBackData //企业微信回调基础参数
basicValueCallback.MsgSignature = MsgSignature
basicValueCallback.Timestamp = Timestamp
basicValueCallback.Nonce = Nonce
basicValueCallback.DataType = EchostrType
basicValueCallback.SystemApp = SystemApp
var msgStr string
if Echostr != "" {
//Api地址验证
basicValueCallback.Echostr = Echostr
msgStr = basicValueCallback.VerificationUrl()
c.String(200, msgStr)
} else {
//回调事件
fmt.Printf("回调事件")
}
// fmt.Printf("(3)CallbackMessageApi---------->%v------------------->%v\n", msgStr, basicValueCallback)
// publicmethod.Result(1, basicValueCallback, c, msgStr)
}
// 验证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)
// if nil != cryptErr {
// fmt.Println("verifyUrl fail", cryptErr)
// }
// msg = string(echoStr)
// jsonk, _ := json.Marshal(c)
// fmt.Printf("json--------->%v\n", string(jsonk))
switch c.DataType {
case "json":
wxcptJson := wechatstatice.WechatDecryptJson(c.SystemApp)
echoStr, cryptErr := wxcptJson.VerifyURL(c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr)
fmt.Printf("(2)wxcptJson---------->%v------------echoStr------->%v----cryptErr---->%v-----MsgSignature--->%v----Timestamp---->%v-----Nonce--->%v-----Echostr--->%v\n", wxcptJson, string(echoStr), cryptErr, c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr)
msg = string(echoStr)
var callbackLog wechatcallback.CallbackLog
callbackLog.MsgSignature = c.MsgSignature
TimestampInt, _ := strconv.ParseInt(c.Timestamp, 10, 64)
callbackLog.TimeStamp = TimestampInt
callbackLog.Nonce = c.Nonce
callbackLog.Echostr = c.Echostr
callbackLog.Xmlstr = string(echoStr)
// callbackLog.Reqdata = string(reqData)
callbackLog.AddTime = time.Now().Unix()
overall.CONSTANT_DB_WECHAT_LOG.Create(&callbackLog)
default:
wxcptXml := wechatstatice.WechatDecryptXml(c.SystemApp)
echoStr, cryptErr := wxcptXml.VerifyURL(c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr)
fmt.Printf("wxcptXml---------->%v------------echoStr------->%v----cryptErr---->%v-----MsgSignature--->%v----Timestamp---->%v-----Nonce--->%v-----Echostr--->%v\n", wxcptXml, string(echoStr), cryptErr, c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr)
msg = string(echoStr)
var callbackLog wechatcallback.CallbackLog
callbackLog.MsgSignature = c.MsgSignature
TimestampInt, _ := strconv.ParseInt(c.Timestamp, 10, 64)
callbackLog.TimeStamp = TimestampInt
callbackLog.Nonce = c.Nonce
callbackLog.Echostr = c.Echostr
callbackLog.Xmlstr = string(echoStr)
// callbackLog.Reqdata = string(reqData)
callbackLog.AddTime = time.Now().Unix()
overall.CONSTANT_DB_WECHAT_LOG.Create(&callbackLog)
}
return
// fmt.Println(string(echoStr))
// fmt.Print(string(echoStr))
}

27
middleware/wechatapp/wechatcallback/type.go

@ -0,0 +1,27 @@
package wechatcallback
//企业微信回调
type ApiRouter struct{}
//企业微信回调基础参数
type CallBackData struct {
MsgSignature string `json:"msg_signature"`
Timestamp string `json:"timestamp"`
Nonce string `json:"nonce"`
Echostr string `json:"echostr"`
ToUserName string `json:"tousername"`
AgentID string `json:"agentid"`
Encrypt string `json:"encrypt"`
DataType string `json:"datatype"`
SystemApp string `json:"systemapp"`
}
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"`
}

4
middleware/wechatapp/wechatsendmsg/type.go

@ -0,0 +1,4 @@
package wechatsendmsg
//企业微信发送消息
type ApiRouter struct{}

140
middleware/wechatapp/wechatstatice/method.go

@ -0,0 +1,140 @@
package wechatstatice
import (
"encoding/json"
"fmt"
"key_performance_indicators/middleware/grocerystore"
"key_performance_indicators/middleware/wechatapp/wxbizjsonmsgcrypt"
"key_performance_indicators/middleware/wechatapp/wxbizmsgcrypt"
"key_performance_indicators/overall"
"key_performance_indicators/overall/publicmethod"
)
/*
获取企业微信token
@systemApp 系统
*/
func GetWechatToken(systemApp string) (token string, err error) {
redisClient := grocerystore.RunRedis(overall.CONSTANT_REDIS3)
companyId := overall.CONSTANT_CONFIG.WechatCompany.CompanyId
redisFileKey := fmt.Sprintf("Wechat:GainToken:%v_%v", overall.CONSTANT_CONFIG.RedisPrefixStr.Alias, companyId)
var secretStr string
switch systemApp {
case "kpi":
redisFileKey = fmt.Sprintf("%v_%v_%v", redisFileKey, systemApp, overall.CONSTANT_CONFIG.WechatKpi.Agentid)
secretStr = overall.CONSTANT_CONFIG.WechatKpi.Secret
case "school":
redisFileKey = fmt.Sprintf("%v_%v_%v", redisFileKey, systemApp, overall.CONSTANT_CONFIG.WechatSchool.Agentid)
secretStr = overall.CONSTANT_CONFIG.WechatSchool.Secret
default:
redisFileKey = fmt.Sprintf("%v_%v", redisFileKey, systemApp)
}
isTrue, token := redisClient.Get(redisFileKey)
if isTrue == true {
return
}
//重新获取token
getTokenUrl := "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + companyId + "&corpsecret=" + secretStr
tokenByte := publicmethod.CurlGet(getTokenUrl)
var callBackCont weChatCallBack
err = json.Unmarshal(tokenByte, &callBackCont)
if err != nil {
return
}
if callBackCont.Errcode != 0 {
return
}
token = callBackCont.Accesstoken
redisClient.SetRedisTime(7200)
redisClient.Set(redisFileKey, token)
return
}
/*
企业微信方式应用消息URL组装
@systemApp 系统
*/
/**
@ 作者: 秦东
@ 时间: 2022-09-27 10:41:33
@ 功能: 企业微信发送应用消息URL组装
@ 参数
#systemApp 系统
#class 类型
@ 返回值
#sendUrl 发送应用消息URL
#token token
#err 系统信息
*/
func GetSendMsgTokenUrl(systemApp, class string) (sendUrl string, token string, err error) {
token, err = GetWechatToken(systemApp)
if err != nil {
return
}
switch class {
case "update":
sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/update_template_card?access_token=%v", token)
case "recall":
sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=%v", token)
default:
sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%v", token)
}
return
}
//企业微信解密
/**
@ 作者: 秦东
@ 时间: 2022-09-27 11:57:56
@ 功能: 企业微信加解密-json
@ 参数
#token 应用Token
#encodingAesKey 应用Encodingaeskey
@ 返回值
#wxcpt 初始化加解密类
*/
func WechatDecryptJson(systemApp string) (wxcpt *wxbizjsonmsgcrypt.WXBizMsgCrypt) {
var token string
var encodingAesKey string
switch systemApp {
case "kpi":
token = overall.CONSTANT_CONFIG.WechatKpi.Token
encodingAesKey = overall.CONSTANT_CONFIG.WechatKpi.Encodingaeskey
default:
token = overall.CONSTANT_CONFIG.WechatSchool.Token
encodingAesKey = overall.CONSTANT_CONFIG.WechatSchool.Encodingaeskey
}
fmt.Printf("WechatDecryptJson------->%v------->%v------->%v\n", token, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, encodingAesKey)
wxcpt = wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, wxbizjsonmsgcrypt.JsonType)
return
}
/*
*
@ 作者: 秦东
@ 时间: 2022-09-27 13:21:07
@ 功能: 企业微信加解密-XML
@ 参数
#token 应用Token
#encodingAesKey 应用Encodingaeskey
@ 返回值
#wxcpt 初始化加解密类
*/
func WechatDecryptXml(systemApp string) (wxcpt *wxbizmsgcrypt.WXBizMsgCrypt) {
var token string
var encodingAesKey string
switch systemApp {
case "kpi":
token = overall.CONSTANT_CONFIG.WechatKpi.Token
encodingAesKey = overall.CONSTANT_CONFIG.WechatKpi.Encodingaeskey
default:
token = overall.CONSTANT_CONFIG.WechatSchool.Token
encodingAesKey = overall.CONSTANT_CONFIG.WechatSchool.Encodingaeskey
}
fmt.Printf("WechatDecryptXml------->%v------->%v------->%v\n", token, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, encodingAesKey)
wxcpt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, wxbizmsgcrypt.XmlType)
return
}

10
middleware/wechatapp/wechatstatice/type.go

@ -0,0 +1,10 @@
package wechatstatice
//组织架构返回统类
type weChatCallBack struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Accesstoken string `json:"access_token"`
Expiresin int64 `json:"expires_in"`
Ticket string `json:"ticket"`
}

310
middleware/wechatapp/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go

@ -0,0 +1,310 @@
package wxbizjsonmsgcrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"math/rand"
"sort"
"strings"
)
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
}

315
middleware/wechatapp/wxbizmsgcrypt/wxbizmsgcrypt.go

@ -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
}

18
models/wechatcallback/callbacklog.go

@ -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…
Cancel
Save