123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- package v1
- import (
- "bytes"
- "context"
- "crypto/aes"
- "crypto/cipher"
- "crypto/rand"
- "crypto/sha1"
- "encoding/base64"
- "encoding/binary"
- "encoding/hex"
- "encoding/xml"
- "errors"
- "fmt"
- "github.com/gin-gonic/gin"
- "io"
- "log"
- "property-callback-gateway/parser"
- "property-callback-gateway/pb"
- "property-callback-gateway/pb/v1"
- "sort"
- "strings"
- "time"
- )
- // CheckSignature 微信公众号签名检查
- func CheckSignature(signature, timestamp, nonce, token string) bool {
- arr := []string{timestamp, nonce, token}
- // 字典序排序
- sort.Strings(arr)
- n := len(timestamp) + len(nonce) + len(token)
- var b strings.Builder
- b.Grow(n)
- for i := 0; i < len(arr); i++ {
- b.WriteString(arr[i])
- }
- return Sha1(b.String()) == signature
- }
- // 进行Sha1编码
- func Sha1(str string) string {
- h := sha1.New()
- h.Write([]byte(str))
- return hex.EncodeToString(h.Sum(nil))
- }
- //
- // @Summary 微信公众号接入回调
- // @Description 微信公众号接入回调
- // @Tags callback
- // @Accept json
- // @Produce json
- // @Router /api/v1/wx/public [get]
- func (c *Controller) WxPublicIn(ctx *gin.Context) {
- signature := ctx.Query("signature")
- timestamp := ctx.Query("timestamp")
- nonce := ctx.Query("nonce")
- echostr := ctx.Query("echostr")
- fmt.Printf("%v,%v,%v,%v\n", signature, timestamp, nonce, echostr)
- token := parser.Conf.ThirdParty.Wx.PublicToken
- ok := CheckSignature(signature, timestamp, nonce, token)
- if !ok {
- log.Println("[微信接入] - 微信公众号接入校验失败!")
- return
- }
- log.Println("[微信接入] - 微信公众号接入校验成功!")
- _, _ = ctx.Writer.WriteString(echostr)
- }
- // WXTextMsg 微信文本消息结构体
- type WXTextMsg struct {
- ToUserName string
- FromUserName string
- CreateTime int64
- MsgType string
- Content string
- Event string
- MsgId int64
- Encrypt string
- }
- type TextRequestBody struct {
- XMLName xml.Name `xml:"xml"`
- ToUserName string
- FromUserName string
- CreateTime time.Duration
- MsgType string
- Content string
- MsgId int
- Event string
- }
- // WXRepTextMsg 微信回复文本消息结构体
- type WXRepTextMsg struct {
- ToUserName string
- FromUserName string
- CreateTime int64
- MsgType string
- Content string
- // 若不标记XMLName, 则解析后的xml名为该结构体的名称
- XMLName xml.Name `xml:"xml"`
- }
- func handlePublicOpenId(openId string) {
- if openId == "" {
- return
- }
- mreq := v1.UserWxPublicAddRequest{OpenId: openId}
- pb.Household.UserWxPublicAdd(context.Background(), &mreq)
- }
- type EncryptRequestBody struct {
- XMLName xml.Name `xml:"xml"`
- ToUserName string
- Encrypt string
- }
- func makeMsgSignature(token string, timestamp, nonce, msg_encrypt string) string {
- sl := []string{token, timestamp, nonce, msg_encrypt}
- sort.Strings(sl)
- s := sha1.New()
- io.WriteString(s, strings.Join(sl, ""))
- return fmt.Sprintf("%x", s.Sum(nil))
- }
- func validateMsg(timestamp, nonce, msgEncrypt, msgSignatureIn string) bool {
- msgSignatureGen := makeMsgSignature(parser.Conf.ThirdParty.Wx.PublicToken, timestamp, nonce, msgEncrypt)
- if msgSignatureGen != msgSignatureIn {
- return false
- }
- return true
- }
- var encodingAESKey = "9ppghgxzoJJEokyofNlM5P9yhJMBH43CQqNjX8O4txx"
- func encodingAESKey2AESKey(encodingKey string) []byte {
- data, _ := base64.StdEncoding.DecodeString(encodingKey + "=")
- return data
- }
- func aesDecrypt(cipherData []byte, aesKey []byte) ([]byte, error) {
- k := len(aesKey) //PKCS#7
- if len(cipherData)%k != 0 {
- return nil, errors.New("crypto/cipher: ciphertext size is not multiple of aes key length")
- }
- block, err := aes.NewCipher(aesKey)
- if err != nil {
- return nil, err
- }
- iv := make([]byte, aes.BlockSize)
- if _, err := io.ReadFull(rand.Reader, iv); err != nil {
- return nil, err
- }
- blockMode := cipher.NewCBCDecrypter(block, iv)
- plainData := make([]byte, len(cipherData))
- blockMode.CryptBlocks(plainData, cipherData)
- return plainData, nil
- }
- func parseEncryptTextRequestBody(plainText []byte) (*TextRequestBody, error) {
- fmt.Println(string(plainText))
- // Read length
- buf := bytes.NewBuffer(plainText[16:20])
- var length int32
- binary.Read(buf, binary.BigEndian, &length)
- fmt.Println(string(plainText[20 : 20+length]))
- // appID validation
- appIDstart := 20 + length
- id := plainText[appIDstart : int(appIDstart)+len(parser.Conf.ThirdParty.Wx.PublicAppId)]
- if string(id) != parser.Conf.ThirdParty.Wx.PublicAppId {
- log.Println("Wechat Service: appid is invalid!")
- return nil, errors.New("Appid is invalid")
- }
- log.Println("Wechat Service: appid validation is ok!")
- // xml Decoding
- textRequestBody := &TextRequestBody{}
- fmt.Printf("request decrypt:%s\n", plainText[20:20+length])
- xml.Unmarshal(plainText[20:20+length], textRequestBody)
- return textRequestBody, nil
- }
- //
- // @Summary 微信公众号接收事件通知
- // @Description 微信公众号接收事件通知
- // @Tags callback
- // @Accept json
- // @Produce json
- // @Router /api/v1/wx/public [post]
- func (c *Controller) WxPublicCallback(ctx *gin.Context) {
- timestamp := ctx.Query("timestamp")
- nonce := ctx.Query("nonce")
- var textMsg WXTextMsg
- err := ctx.ShouldBindXML(&textMsg)
- if err != nil {
- log.Printf("[消息接收] - XML数据包解析失败: %v\n", err)
- return
- }
- bytes, _ := xml.Marshal(textMsg)
- fmt.Printf("text:%s,%v\n", bytes, textMsg.FromUserName)
- if textMsg.Encrypt != "" {
- cipherData, err := base64.StdEncoding.DecodeString(textMsg.Encrypt)
- if err != nil {
- log.Println("Wechat Service: Decode base64 error:", err)
- return
- }
- aesKey := encodingAESKey2AESKey(encodingAESKey)
- // AES Decrypt
- plainData, err := aesDecrypt(cipherData, aesKey)
- if err != nil {
- fmt.Println(err)
- return
- }
- //Xml decoding
- textRequestBody, err := parseEncryptTextRequestBody(plainData)
- if err != nil {
- fmt.Println(err)
- return
- }
- if textRequestBody.Event == "subscribe" {
- openId := textRequestBody.FromUserName
- handlePublicOpenId(openId)
- }
- WXEncryptMsgReply(textRequestBody.ToUserName,
- textRequestBody.FromUserName,
- //fmt.Sprintf("[消息回复] - %s", time.Now().Format("2006-01-02 15:04:05")),
- `<a data-miniprogram-appid="`+parser.Conf.ThirdParty.Wx.AppletAppId+`" data-miniprogram-path="`+parser.Conf.ThirdParty.Wx.AppletPagepath+`">点击进入小程序</a>`,
- nonce,
- timestamp, aesKey, ctx)
- return
- }
- if textMsg.Event == "subscribe" {
- openId := textMsg.FromUserName
- handlePublicOpenId(openId)
- }
- //log.Printf("[消息接收] - 收到消息, 消息类型为: %s, 消息内容为: %s\n", textMsg.MsgType, textMsg.Content)
- WXMsgReply(ctx, textMsg.ToUserName, textMsg.FromUserName)
- }
- // WXMsgReply 微信消息回复
- func WXMsgReply(c *gin.Context, fromUser, toUser string) {
- repTextMsg := WXRepTextMsg{
- ToUserName: toUser,
- FromUserName: fromUser,
- CreateTime: time.Now().Unix(),
- MsgType: "text",
- Content: `<a data-miniprogram-appid="` + parser.Conf.ThirdParty.Wx.AppletAppId + `" data-miniprogram-path="` + parser.Conf.ThirdParty.Wx.AppletPagepath + `">点击进入小程序</a>`,
- }
- msg, _ := xml.Marshal(&repTextMsg)
- c.Writer.Write(msg)
- }
- type EncryptResponseBody struct {
- XMLName xml.Name `xml:"xml"`
- Encrypt CDATAText
- MsgSignature CDATAText
- TimeStamp string
- Nonce CDATAText
- }
- type TextResponseBody struct {
- XMLName xml.Name `xml:"xml"`
- ToUserName CDATAText
- FromUserName CDATAText
- CreateTime string
- MsgType CDATAText
- Content CDATAText
- }
- type CDATAText struct {
- Text string `xml:",innerxml"`
- }
- func PadLength(slice_length, blocksize int) (padlen int) {
- padlen = blocksize - slice_length%blocksize
- if padlen == 0 {
- padlen = blocksize
- }
- return padlen
- }
- func PKCS7Pad(message []byte, blocksize int) (padded []byte, err error) {
- // block size must be bigger or equal 2
- if blocksize < 1<<1 {
- return nil, errors.New("block size is too small (minimum is 2 bytes)")
- }
- // block size up to 255 requires 1 byte padding
- if blocksize < 1<<8 {
- // calculate padding length
- padlen := PadLength(len(message), blocksize)
- // define PKCS7 padding block
- padding := bytes.Repeat([]byte{byte(padlen)}, padlen)
- // apply padding
- padded = append(message, padding...)
- return padded, nil
- }
- // block size bigger or equal 256 is not currently supported
- return nil, errors.New("unsupported block size")
- }
- func WXEncryptMsgReply(fromUserName, toUserName, content, nonce, timestamp string, aesKey []byte, c *gin.Context) {
- encryptBody := &EncryptResponseBody{}
- encryptXmlData, _ := makeEncryptXmlData(fromUserName, toUserName, timestamp, content, aesKey)
- encryptBody.Encrypt = value2CDATA(encryptXmlData)
- encryptBody.MsgSignature = value2CDATA(makeMsgSignature(parser.Conf.ThirdParty.Wx.PublicToken, timestamp, nonce, encryptXmlData))
- encryptBody.TimeStamp = timestamp
- encryptBody.Nonce = value2CDATA(nonce)
- msg, _ := xml.MarshalIndent(encryptBody, " ", " ")
- c.Writer.Write(msg)
- }
- func value2CDATA(v string) CDATAText {
- //return CDATAText{[]byte("<![CDATA[" + v + "]]>")}
- return CDATAText{"<![CDATA[" + v + "]]>"}
- }
- func makeEncryptXmlData(fromUserName, toUserName, timestamp, content string, aesKey []byte) (string, error) {
- // Encrypt part3: Xml Encoding
- textResponseBody := &TextResponseBody{}
- textResponseBody.FromUserName = value2CDATA(fromUserName)
- textResponseBody.ToUserName = value2CDATA(toUserName)
- textResponseBody.MsgType = value2CDATA("text")
- textResponseBody.Content = value2CDATA(content)
- textResponseBody.CreateTime = timestamp
- body, err := xml.MarshalIndent(textResponseBody, " ", " ")
- if err != nil {
- return "", errors.New("xml marshal error")
- }
- // Encrypt part2: Length bytes
- buf := new(bytes.Buffer)
- err = binary.Write(buf, binary.BigEndian, int32(len(body)))
- if err != nil {
- fmt.Println("Binary write err:", err)
- }
- bodyLength := buf.Bytes()
- // Encrypt part1: Random bytes
- randomBytes := []byte("abcdefghijklmnop")
- // Encrypt Part, with part4 - appID
- plainData := bytes.Join([][]byte{randomBytes, bodyLength, body, []byte(parser.Conf.ThirdParty.Wx.PublicAppId)}, nil)
- cipherData, err := aesEncrypt(plainData, aesKey)
- if err != nil {
- return "", errors.New("aesEncrypt error")
- }
- return base64.StdEncoding.EncodeToString(cipherData), nil
- }
- func aesEncrypt(plainData []byte, aesKey []byte) ([]byte, error) {
- k := len(aesKey)
- var err error
- if len(plainData)%k != 0 {
- plainData, err = PKCS7Pad(plainData, k)
- if err != nil {
- return nil, err
- }
- }
- block, err := aes.NewCipher(aesKey)
- if err != nil {
- return nil, err
- }
- iv := make([]byte, aes.BlockSize)
- if _, err := io.ReadFull(rand.Reader, iv); err != nil {
- return nil, err
- }
- cipherData := make([]byte, len(plainData))
- blockMode := cipher.NewCBCEncrypter(block, iv)
- blockMode.CryptBlocks(cipherData, plainData)
- return cipherData, nil
- }
|