dengbiao 4 meses atrás
pai
commit
0f1b3792f7
11 arquivos alterados com 854 adições e 10 exclusões
  1. +134
    -8
      app/hdl/hdl_wx_open.go
  2. +68
    -0
      app/lib/wechat/helpers.go
  3. +11
    -0
      app/lib/wechat/md/wechat_api_md.go
  4. +254
    -0
      app/lib/wechat/message.go
  5. +61
    -0
      app/lib/wechat/prpcrypt.go
  6. +109
    -0
      app/lib/wechat/wechat_api.go
  7. +11
    -0
      app/lib/wechat/wechat_err_code.go
  8. +200
    -0
      app/lib/wechat/wechat_message.go
  9. +3
    -0
      app/md/app_redis_key.go
  10. +2
    -1
      app/router/router.go
  11. +1
    -1
      go.mod

+ 134
- 8
app/hdl/hdl_wx_open.go Ver arquivo

@@ -1,19 +1,24 @@
package hdl

import (
"applet/app/e"
"applet/app/lib/wechat"
"applet/app/md"
"applet/app/utils"
"applet/app/utils/cache"
db "code.fnuoos.com/zhimeng/model.git/src"
"code.fnuoos.com/zhimeng/model.git/src/super/implement"
"encoding/xml"
"fmt"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"net/url"
)

type ReqWxMessage struct {
AppID string `xml:"AppId"`
CreateTime int64 `xml:"CreateTime"`
InfoType string `xml:"InfoType"`
ComponentVerifyTicket string `xml:"ComponentVerifyTicket"`
type OriginalWxMessage struct {
AppID string `xml:"AppId"`
Encrypt string `xml:"Encrypt"`
}

func SetTicket(c *gin.Context) {
@@ -26,7 +31,7 @@ func SetTicket(c *gin.Context) {

utils.FilePutContents("SetTicket_Get", utils.SerializeStr(params))

var wxMsg ReqWxMessage
var originalWxMessage OriginalWxMessage
// 读取请求体
body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
@@ -35,18 +40,139 @@ func SetTicket(c *gin.Context) {
}

utils.FilePutContents("SetTicket_Post", string(body))
err = xml.Unmarshal(body, &wxMsg)
err = xml.Unmarshal(body, &originalWxMessage)
if err != nil {
fmt.Println("setTicket>>>>>>>>", err.Error())
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

utils.FilePutContents("SetTicket_XML", utils.SerializeStr(wxMsg))
//1、查找对应 wx_open_third_party_app_list 记录
wxOpenThirdPartyAppListDb := implement.NewWxOpenThirdPartyAppListDb(db.Db)
wxOpenThirdPartyAppList, err := wxOpenThirdPartyAppListDb.GetWxOpenThirdPartyAppListByAppId(originalWxMessage.AppID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if wxOpenThirdPartyAppList == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "未查询到对应App记录"})
return
}

//2、对消息体进行解密
instance := wechat.NewWechatMsgCrypt(wxOpenThirdPartyAppList.Token, wxOpenThirdPartyAppList.AesKey, wxOpenThirdPartyAppList.Appid)
eventRequest := wechat.EventEncryptRequest{
XMLName: xml.Name{},
Encrypt: originalWxMessage.Encrypt,
Appid: originalWxMessage.AppID,
}
reqWxMessage := instance.WechatEventDecrypt(eventRequest, params["msg_signature"], params["timestamp"], params["nonce"])
fmt.Println("解密结果:", reqWxMessage)
utils.FilePutContents("SetTicket_XML", utils.SerializeStr(reqWxMessage))
if reqWxMessage.InfoType == "component_verify_ticket" { //TODO::微信公众平台 验证票据
cacheKey := fmt.Sprintf(md.MasterComponentVerifyTicket, utils.AnyToString(wxOpenThirdPartyAppList.Uuid))
cacheComponentVerifyTicket, _ := cache.GetString(cacheKey)
if cacheComponentVerifyTicket == "" || cacheComponentVerifyTicket != reqWxMessage.ComponentVerifyTicket {
cache.SetEx(cacheKey, reqWxMessage.ComponentVerifyTicket, 43140)
wxOpenThirdPartyAppList.ComponentVerifyTicket = reqWxMessage.ComponentVerifyTicket
_, err = wxOpenThirdPartyAppListDb.UpdateWxOpenThirdPartyAppList(wxOpenThirdPartyAppList, "component_verify_ticket")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
}
}
if reqWxMessage.InfoType == "unauthorized" { //TODO::微信公众平台 取消授权
appid := reqWxMessage.AuthorizerAppid
userWxAppletListDb := implement.NewUserWxAppletListDb(db.Db)
userWxAppletList, err := userWxAppletListDb.GetUserWxAppletListByAppId(appid)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if userWxAppletList == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "未查询到对应小程序授权记录"})
return
}
userWxAppletList.IsAuth = 0
_, err = userWxAppletListDb.UpdateUserWxAppletList(userWxAppletList, "is_auth")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
}
if reqWxMessage.InfoType == "unauthorized" || reqWxMessage.InfoType == "authorized" { //TODO::微信公众平台 授权 || 更新授权
appid := reqWxMessage.AuthorizerAppid
userWxAppletListDb := implement.NewUserWxAppletListDb(db.Db)
userWxAppletList, err := userWxAppletListDb.GetUserWxAppletListByAppId(appid)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if userWxAppletList == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "未查询到对应小程序授权记录"})
return
}

userWxAppletList.IsAuth = 1
_, err = userWxAppletListDb.UpdateUserWxAppletList(userWxAppletList, "is_auth")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
}

c.String(http.StatusOK, "success")
return
}

func GetPreAuthCode(c *gin.Context) {
masterId := c.DefaultQuery("master_id", "")
wxOpenThirdPartyAppListDb := implement.NewWxOpenThirdPartyAppListDb(db.Db)
wxOpenThirdPartyAppList, err := wxOpenThirdPartyAppListDb.GetWxOpenThirdPartyAppList(utils.StrToInt(masterId))
if err != nil {
return
}
if wxOpenThirdPartyAppList == nil {
e.OutErr(c, e.ERR_NOT_FAN, "未查询到对应三方应用记录")
return
}
wxApiService, err := wechat.NewWxApiService(masterId, wxOpenThirdPartyAppList.Appid, wxOpenThirdPartyAppList.AppSecret)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}
preAuthCode, err := wxApiService.GetPreAuthCode()
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}

var w http.ResponseWriter
w.Header().Set("Access-Control-Allow-Origin", "*")
redirectURI := "http://super.admin.izhyin.com/Wx/getAuthUrlCallBack"
// 对redirectURI进行URL编码
encodedRedirectURI := url.QueryEscape(redirectURI)

// 构造微信登录页面的URL
baseURL := "https://mp.weixin.qq.com/cgi-bin/componentloginpage"
query := url.Values{}
query.Add("component_appid", wxOpenThirdPartyAppList.Appid)
query.Add("pre_auth_code", preAuthCode)
query.Add("redirect_uri", encodedRedirectURI)
query.Add("auth_type", "1")

// 将查询参数附加到基础URL
urlStr := baseURL + "?" + query.Encode()
// 设置JavaScript重定向
fmt.Fprintf(w, `
<script>
window.onload = function () {
window.location.href = '%s';
};
</script>`, urlStr)
}

func WechatMsgRecieve(c *gin.Context) {

return


+ 68
- 0
app/lib/wechat/helpers.go Ver arquivo

@@ -0,0 +1,68 @@
package wechat

import (
"bytes"
"crypto/sha1"
"fmt"
"io"
"math/rand"
"sort"
"strings"
)

// GetSignature 获取签名
func GetSignature(timestamp, nonce string, encrypted string, token string) string {
data := []string{
encrypted,
token,
timestamp,
nonce,
}
sort.Strings(data)
s := sha1.New()
_, err := io.WriteString(s, strings.Join(data, ""))
if err != nil {
panic("签名错误:sign error")
}
return fmt.Sprintf("%x", s.Sum(nil))
}

func makeRandomString(length int) string {
randStr := ""
strSource := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyl"
maxLength := len(strSource) - 1
for i := 0; i < length; i++ {
randomNum := rand.Intn(maxLength)
randStr += strSource[randomNum : randomNum+1]
}
return randStr
}

func pKCS7Pad(plainText []byte, blockSize int) []byte {
// block size must be bigger or equal 2
if blockSize < 1<<1 {
panic("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(plainText), blockSize)

// define PKCS7 padding block
padding := bytes.Repeat([]byte{byte(padLen)}, padLen)

// apply padding
padded := append(plainText, padding...)
return padded
}
// block size bigger or equal 256 is not currently supported
panic("unsupported block size")
}

func padLength(sliceLength, blockSize int) int {
padLen := blockSize - sliceLength%blockSize
if padLen == 0 {
padLen = blockSize
}
return padLen
}

+ 11
- 0
app/lib/wechat/md/wechat_api_md.go Ver arquivo

@@ -0,0 +1,11 @@
package md

type GetComponentAccessToken struct {
ComponentAccessToken string `json:"component_access_token" example:"第三方平台 access_token"`
ExpiresIn int `json:"expires_in" example:"有效期,单位:秒"`
}

type GetPreAuthCode struct {
PreAuthCode string `json:"pre_auth_code" example:"预授权码"`
ExpiresIn int `json:"expires_in" example:"有效期,单位:秒"`
}

+ 254
- 0
app/lib/wechat/message.go Ver arquivo

@@ -0,0 +1,254 @@
package wechat

import (
"bytes"
"encoding/binary"
"encoding/xml"
"errors"
"strconv"
"time"
)

var EventTicket = "component_verify_ticket" //ticket推送
var EventUnauthorized = "unauthorized" //取消授权
var EventUpdateAuthorized = "updateauthorized" //更新授权
var EventAuthorized = "authorized" //授权成功

var MsgTypeText = "text" //文本消息
var MsgTypeImage = "image" //文本消息
var MsgTypeVoice = "voice" //语音消息
var MsgTypeVideo = "Video" //视频消息
var MsgTypeMusic = "music" //音乐消息
var MsgTypeNews = "news" //图文消息

// EventMessageBody 事件推送
type EventMessageBody struct {
XMLName xml.Name `xml:"xml"`
AppId string `xml:"AppId" json:"app_id"`
CreateTime int `xml:"CreateTime" json:"create_time"`
InfoType string `xml:"InfoType" json:"info_type"`
ComponentVerifyTicket string `xml:"ComponentVerifyTicket" json:"component_verify_ticket"`
AuthorizerAppid string `xml:"AuthorizerAppid" json:"authorizer_appid"`
AuthorizationCode string `xml:"AuthorizationCode" json:"authorization_code"`
AuthorizationCodeExpiredTime string `xml:"AuthorizationCodeExpiredTime" json:"authorization_code_expired_time"`
PreAuthCode string `xml:"PreAuthCode" json:"pre_auth_code"`
}

// MessageBodyDecrypt 消息体
type MessageBodyDecrypt struct {
XMLName xml.Name `xml:"xml"`
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Url string `xml:"Url"`
PicUrl string `xml:"PicUrl"`
MediaId string `xml:"MediaId"`
ThumbMediaId string `xml:"ThumbMediaId"`
Content string `xml:"Content"`
MsgId int `xml:"MsgId"`
Location_X string `xml:"Location_x"`
Location_Y string `xml:"Location_y"`
Label string `xml:"Label"`
}

type MessageEncryptBody struct {
XMLName xml.Name `xml:"xml"`
Encrypt CDATA `xml:"Encrypt"`
MsgSignature CDATA `xml:"MsgSignature"`
TimeStamp string `xml:"TimeStamp"`
Nonce CDATA `xml:"Nonce"`
}

type MessageText struct {
XMLName xml.Name `xml:"xml"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType CDATA `xml:"MsgType"`
Content CDATA `xml:"Content"`
}

type MessageImage struct {
XMLName xml.Name `xml:"xml"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType CDATA `xml:"MsgType"`
Image Media `xml:"Image"`
}

type MessageVoice struct {
XMLName xml.Name `xml:"xml"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType CDATA `xml:"MsgType"`
Voice Media `xml:"Voice"`
}

type MessageVideo struct {
XMLName xml.Name `xml:"xml"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType CDATA `xml:"MsgType"`
Video Video `xml:"Video"`
}

type MessageMusic struct {
XMLName xml.Name `xml:"xml"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType CDATA `xml:"MsgType"`
Music Music `xml:"Music"`
}

type MessageArticle struct {
XMLName xml.Name `xml:"xml"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime string `xml:"CreateTime"`
MsgType CDATA `xml:"MsgType"`
ArticleCount string `xml:"ArticleCount"`
Articles []ArticleItem `xml:"Articles"`
}

type CDATA struct {
Text string `xml:",innerxml"`
}

type Media struct {
MediaId CDATA `xml:"MediaId"`
}

type Video struct {
MediaId CDATA `xml:"MediaId"`
Title CDATA `xml:"Title"`
Description CDATA `xml:"Description"`
}

type Music struct {
Title CDATA `xml:"Title"`
Description CDATA `xml:"Description"`
MusicUrl CDATA `xml:"MusicUrl"`
HQMusicUrl CDATA `xml:"HQMusicUrl"`
ThumbMediaId CDATA `xml:"ThumbMediaId"`
}

type ArticleItem struct {
Title CDATA `xml:"Title"`
Description CDATA `xml:"Description"`
PicUrl CDATA `xml:"PicUrl"`
Url CDATA `xml:"Url"`
}

func FormatMessage(plainText []byte, data interface{}) (*interface{}, error) {
length := GetMessageLength(plainText)
err := xml.Unmarshal(plainText[20:20+length], data)
if err != nil {
return nil, errors.New("格式化消息失败:format message error")
}
return &data, nil
}

func GetMessageLength(plainText []byte) int32 {
// Read length
buf := bytes.NewBuffer(plainText[16:20])
var length int32
err := binary.Read(buf, binary.BigEndian, &length)
if err != nil {
panic("获取消息长度失败:read message length error")
}
return length
}

func ValueToCDATA(content string) CDATA {
return CDATA{"<![CDATA[" + content + "]]>"}
}

// FormatTextMessage 格式化文本消息
func FormatTextMessage(fromUserName string, toUserName string, content string) []byte {
timestamp := strconv.Itoa(int(time.Now().Unix()))
textMessage := MessageText{FromUserName: ValueToCDATA(fromUserName), ToUserName: ValueToCDATA(toUserName), Content: ValueToCDATA(content), CreateTime: timestamp, MsgType: ValueToCDATA(MsgTypeText)}
messageBytes, err := xml.MarshalIndent(textMessage, " ", " ")
if err != nil {
panic("格式化文本消息失败:xml marsha1 error;" + err.Error())
}
return messageBytes
}

// FormatImageMessage 格式化图片消息
func FormatImageMessage(fromUserName string, toUserName string, mediaId string) []byte {
timestamp := strconv.Itoa(int(time.Now().Unix()))
imageMessage := MessageImage{FromUserName: ValueToCDATA(fromUserName), ToUserName: ValueToCDATA(toUserName), CreateTime: timestamp, MsgType: ValueToCDATA(MsgTypeImage), Image: Media{ValueToCDATA(mediaId)}}
messageBytes, err := xml.MarshalIndent(imageMessage, " ", " ")
if err != nil {
panic("格式化图片消息失败:xml marsha1 error;" + err.Error())
}
return messageBytes
}

// FormatVoiceMessage 格式语音消息
func FormatVoiceMessage(fromUserName string, toUserName string, mediaId string) []byte {
timestamp := strconv.Itoa(int(time.Now().Unix()))
voiceMessage := MessageVoice{FromUserName: ValueToCDATA(fromUserName), ToUserName: ValueToCDATA(toUserName), CreateTime: timestamp, MsgType: ValueToCDATA(MsgTypeVoice), Voice: Media{ValueToCDATA(mediaId)}}
messageBytes, err := xml.MarshalIndent(voiceMessage, " ", " ")
if err != nil {
panic("格式化语音消息失败:xml marsha1 error;" + err.Error())
}
return messageBytes
}

// FormatVideoMessage 格式化视频消息
func FormatVideoMessage(fromUserName string, toUserName string, mediaId string, title string, description string) []byte {
timestamp := strconv.Itoa(int(time.Now().Unix()))
videoMessage := MessageVideo{FromUserName: ValueToCDATA(fromUserName), ToUserName: ValueToCDATA(toUserName), CreateTime: timestamp, MsgType: ValueToCDATA(MsgTypeVideo), Video: Video{
MediaId: ValueToCDATA(mediaId),
Title: ValueToCDATA(title),
Description: ValueToCDATA(description),
}}
messageBytes, err := xml.MarshalIndent(videoMessage, " ", " ")
if err != nil {
panic("格式化语音消息失败:xml marsha1 error;" + err.Error())
}
return messageBytes
}

// FormatMusicMessage 格式化音乐消息
func FormatMusicMessage(fromUserName string, toUserName string, thumbMediaId string, title string, description string, musicUrl string, hQMusicUrl string) []byte {
timestamp := strconv.Itoa(int(time.Now().Unix()))
musicMessage := MessageMusic{FromUserName: ValueToCDATA(fromUserName), ToUserName: ValueToCDATA(toUserName), CreateTime: timestamp, MsgType: ValueToCDATA(MsgTypeMusic), Music: Music{
Title: ValueToCDATA(title),
Description: ValueToCDATA(description),
MusicUrl: ValueToCDATA(musicUrl),
HQMusicUrl: ValueToCDATA(hQMusicUrl),
ThumbMediaId: ValueToCDATA(thumbMediaId),
}}
messageBytes, err := xml.MarshalIndent(musicMessage, " ", " ")
if err != nil {
panic("格式化音乐消息失败:xml marsha1 error;" + err.Error())
}
return messageBytes
}

// FormatArticlesMessage 格式化音乐消息
func FormatArticlesMessage(fromUserName string, toUserName string, items []ArticleItem) []byte {
timestamp := strconv.Itoa(int(time.Now().Unix()))
articleNum := strconv.Itoa(len(items))
musicMessage := MessageArticle{FromUserName: ValueToCDATA(fromUserName), ToUserName: ValueToCDATA(toUserName), CreateTime: timestamp, MsgType: ValueToCDATA(MsgTypeNews), Articles: items, ArticleCount: articleNum}
messageBytes, err := xml.MarshalIndent(musicMessage, " ", " ")
if err != nil {
panic("格式化图文消息失败:xml marsha1 error;" + err.Error())
}
return messageBytes
}

// FormatEncryptData 格式化微信加密数据
func FormatEncryptData(encrypted string, token string) MessageEncryptBody {
nonce := makeRandomString(16)
timestamp := strconv.Itoa(int(time.Now().Unix()))
data := MessageEncryptBody{Encrypt: ValueToCDATA(encrypted), Nonce: ValueToCDATA(nonce), MsgSignature: ValueToCDATA(GetSignature(timestamp, nonce, encrypted, token)), TimeStamp: timestamp}
return data
}

+ 61
- 0
app/lib/wechat/prpcrypt.go Ver arquivo

@@ -0,0 +1,61 @@
package wechat

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"io"
)

type prpCrypt struct {
Key []byte
Iv []byte
}

func NewPrpCrypt(aesKey string) *prpCrypt {
instance := new(prpCrypt)
//网络字节序
instance.Key, _ = base64.StdEncoding.DecodeString(aesKey + "=")
instance.Iv = randomIv()
return instance
}

func randomIv() []byte {
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("random iv error")
}
return iv
}

func (prp *prpCrypt) decrypt(encrypted string) ([]byte, error) {
encryptedBytes, _ := base64.StdEncoding.DecodeString(encrypted)
k := len(prp.Key) //PKCS#7
if len(encryptedBytes)%k != 0 {
panic("ciphertext size is not multiple of aes key length")
}
block, err := aes.NewCipher(prp.Key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, prp.Iv)
plainText := make([]byte, len(encryptedBytes))
blockMode.CryptBlocks(plainText, encryptedBytes)
return plainText, nil
}

func (prp *prpCrypt) encrypt(plainText []byte) ([]byte, error) {
k := len(prp.Key)
if len(plainText)%k != 0 {
plainText = pKCS7Pad(plainText, k)
}
block, err := aes.NewCipher(prp.Key)
if err != nil {
return nil, err
}
cipherData := make([]byte, len(plainText))
blockMode := cipher.NewCBCEncrypter(block, prp.Iv)
blockMode.CryptBlocks(cipherData, plainText)
return cipherData, nil
}

+ 109
- 0
app/lib/wechat/wechat_api.go Ver arquivo

@@ -0,0 +1,109 @@
package wechat

import (
"applet/app/cfg"
md2 "applet/app/lib/wechat/md"
"applet/app/md"
"applet/app/utils"
"applet/app/utils/cache"
db "code.fnuoos.com/zhimeng/model.git/src"
"code.fnuoos.com/zhimeng/model.git/src/super/implement"
"encoding/json"
"errors"
"fmt"
)

type WxApiService struct {
ComponentAppid string `json:"component_appid"`
ComponentAppsecret string `json:"component_appsecret"`
ComponentVerifyTicket string `json:"component_verify_ticket"`
MasterId string `json:"master_id"`
Host string `json:"host"`
}

func NewWxApiService(masterId, componentAppid, componentAppsecret string) (wxApiService WxApiService, err error) { // set方法
wxApiService.MasterId = masterId
wxApiService.ComponentAppid = componentAppid
wxApiService.ComponentAppsecret = componentAppsecret
wxApiService.Host = "http://super.advertisement.dengbiao.top"
if cfg.Prd {
wxApiService.Host = "http://www.baidu.com"
}
cacheKey := fmt.Sprintf(md.MasterComponentVerifyTicket, wxApiService.MasterId)
cacheComponentVerifyTicket, _ := cache.GetString(cacheKey)
if cacheComponentVerifyTicket != "" {
return wxApiService, errors.New("微信验证票据ticket未获取到")
}
return
}

// GetComponentAccessToken 获取接口调用令牌token
func (wxApiService *WxApiService) GetComponentAccessToken() (cacheComponentAccessToken string, err error) { // set方法
cacheKey := fmt.Sprintf(md.MasterComponentAccessToken, wxApiService.MasterId)
cacheComponentAccessToken, _ = cache.GetString(cacheKey)
if cacheComponentAccessToken != "" {
url := "https://api.weixin.qq.com/cgi-bin/component/api_component_token"
params := map[string]string{
"component_appid": wxApiService.ComponentAppid,
"component_appsecret": wxApiService.ComponentAppsecret,
"component_verify_ticket": wxApiService.ComponentVerifyTicket,
}
postBody, err1 := utils.CurlPost(url, params, nil)
if err1 != nil {
return cacheComponentAccessToken, err1
}
var postData md2.GetComponentAccessToken
err = json.Unmarshal(postBody, &postData)
if err != nil {
return
}
if postData.ComponentAccessToken == "" {
err = errors.New("获取接口令牌token失败")
return
}
cacheComponentAccessToken = postData.ComponentAccessToken
cache.SetEx(cacheKey, cacheComponentAccessToken, postData.ExpiresIn-600)
wxOpenThirdPartyAppListDb := implement.NewWxOpenThirdPartyAppListDb(db.Db)
wxOpenThirdPartyAppList, err1 := wxOpenThirdPartyAppListDb.GetWxOpenThirdPartyAppListByAppId(wxApiService.ComponentAppid)
if err1 != nil {
return cacheComponentAccessToken, err1
}
if wxOpenThirdPartyAppList == nil {
err = errors.New("未查询到对应App记录")
return
}
wxOpenThirdPartyAppList.ComponentAccessToken = cacheComponentAccessToken
_, err = wxOpenThirdPartyAppListDb.UpdateWxOpenThirdPartyAppList(wxOpenThirdPartyAppList, "component_access_token")
if err != nil {
return
}
}
return
}

// GetPreAuthCode 获取预授权码
func (wxApiService *WxApiService) GetPreAuthCode() (preAuthCode string, err error) { // set方法
componentAccessToken, err := wxApiService.GetComponentAccessToken()
if err != nil {
return
}
url := "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + componentAccessToken
params := map[string]string{
"component_appid": wxApiService.ComponentAppid,
}
postBody, err := utils.CurlPost(url, params, nil)
if err != nil {
return
}
var postData md2.GetPreAuthCode
err = json.Unmarshal(postBody, &postData)
if err != nil {
return
}
if postData.PreAuthCode == "" {
err = errors.New("获取预授权码失败")
return
}
preAuthCode = postData.PreAuthCode
return
}

+ 11
- 0
app/lib/wechat/wechat_err_code.go Ver arquivo

@@ -0,0 +1,11 @@
package wechat

var SuccessCode = 0
var ValidateSignatureError = -40001
var ParseXmlError = -40002
var ComputeSignatureError = -40003
var IllegalAesKey = -40004
var ValidateAppidError = -40005
var EncryptAESError = -40006
var DecryptAESError = -40007
var IllegalBuffer = -40008

+ 200
- 0
app/lib/wechat/wechat_message.go Ver arquivo

@@ -0,0 +1,200 @@
package wechat

import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/xml"
"errors"
"fmt"
)

type msgCrypt struct {
token string
aesKey string
appid string
}

// EventEncryptRequest 微信事件推送结构体
type EventEncryptRequest struct {
XMLName xml.Name `xml:"xml"`
Encrypt string `xml:"Encrypt"`
Appid string `xml:"Appid"`
}

// MessageEncryptRequest 微信消息加密结构体
type MessageEncryptRequest struct {
XMLName xml.Name `xml:"xml"`
Encrypt string `xml:"Encrypt"`
MsgSignature string `xml:"MsgSignature"`
TimeStamp string `xml:"TimeStamp"`
Nonce string `xml:"Nonce"`
}

// NewWechatMsgCrypt 实例化微信加解密
func NewWechatMsgCrypt(token string, aesKey string, appid string) *msgCrypt {
instance := new(msgCrypt)
instance.token = token
instance.aesKey = aesKey
instance.appid = appid
return instance
}

// WechatEventDecrypt 微信事件推送解密
func (w *msgCrypt) WechatEventDecrypt(eventRequest EventEncryptRequest, msgSignature string, timestamp, nonce string) EventMessageBody {
errCode, data := w.decryptMsg(msgSignature, timestamp, nonce, eventRequest.Encrypt)
if errCode != SuccessCode {
panic(fmt.Sprintf("消息解密失败,code:%d", errCode))
}
message := EventMessageBody{}
_, err := FormatMessage(data, &message)
if err != nil {
panic(fmt.Sprintf("消息格式化失败,%s", err.Error()))
}
return message
}

// WechatMessageDecrypt 微信消息解密
func (w *msgCrypt) WechatMessageDecrypt(messageEncryptRequest MessageEncryptRequest) interface{} {
errCode, data := w.decryptMsg(messageEncryptRequest.MsgSignature, messageEncryptRequest.TimeStamp, messageEncryptRequest.Nonce, messageEncryptRequest.Encrypt)
if errCode != SuccessCode {
panic(fmt.Sprintf("消息解密失败,code:%d", errCode))
}
message := MessageBodyDecrypt{}
_, err := FormatMessage(data, &message)
if err != nil {
panic(fmt.Sprintf("消息格式化失败,%s", err.Error()))
}
return message
}

// WechatTextMessage 微信文本消息加密
func (w *msgCrypt) WechatTextMessage(fromUserName string, toUserName string, content string) MessageEncryptBody {
message := FormatTextMessage(fromUserName, toUserName, content)
encrypted, err := w.encryptMsg(message)
if err != nil {
panic("消息加密失败:" + err.Error())
}
data := FormatEncryptData(encrypted, w.token)
return data
}

// WechatImageMessage 微信图片消息加密
func (w *msgCrypt) WechatImageMessage(fromUserName string, toUserName string, mediaId string) MessageEncryptBody {
message := FormatImageMessage(fromUserName, toUserName, mediaId)
encrypted, err := w.encryptMsg(message)
if err != nil {
panic("消息加密失败:" + err.Error())
}
data := FormatEncryptData(encrypted, w.token)
return data
}

// WechatVoiceMessage 微信语音消息加密
func (w *msgCrypt) WechatVoiceMessage(fromUserName string, toUserName string, mediaId string) MessageEncryptBody {
message := FormatVoiceMessage(fromUserName, toUserName, mediaId)
encrypted, err := w.encryptMsg(message)
if err != nil {
panic("消息加密失败:" + err.Error())
}
data := FormatEncryptData(encrypted, w.token)
return data
}

// WechatVideoMessage 微信视频消息加密
func (w *msgCrypt) WechatVideoMessage(fromUserName string, toUserName string, mediaId string, title string, description string) MessageEncryptBody {
message := FormatVideoMessage(fromUserName, toUserName, mediaId, title, description)
encrypted, err := w.encryptMsg(message)
if err != nil {
panic("消息加密失败:" + err.Error())
}
data := FormatEncryptData(encrypted, w.token)
return data
}

// WechatMusicMessage 微信音乐消息加密
func (w *msgCrypt) WechatMusicMessage(fromUserName string, toUserName string, thumbMediaId string, title string, description string, musicUrl string, hQMusicUrl string) MessageEncryptBody {
message := FormatMusicMessage(fromUserName, toUserName, thumbMediaId, title, description, musicUrl, hQMusicUrl)
encrypted, err := w.encryptMsg(message)
if err != nil {
panic("消息加密失败:" + err.Error())
}
data := FormatEncryptData(encrypted, w.token)
return data
}

// WechatArticlesMessage 微信图文消息加密
func (w *msgCrypt) WechatArticlesMessage(fromUserName string, toUserName string, items []ArticleItem) MessageEncryptBody {
message := FormatArticlesMessage(fromUserName, toUserName, items)
encrypted, err := w.encryptMsg(message)
if err != nil {
panic("消息加密失败:" + err.Error())
}
data := FormatEncryptData(encrypted, w.token)
return data
}

// decryptMsg aes消息解密
func (w *msgCrypt) decryptMsg(msgSignature string, timestamp, nonce string, encrypted string) (int, []byte) {
//验证aes
if len(w.aesKey) != 43 {
return IllegalAesKey, nil
}
//判断签名是否一致
if err := w.validSignature(msgSignature, timestamp, nonce, encrypted); err != nil {
return ValidateSignatureError, nil
}
//解密
prp := NewPrpCrypt(w.aesKey)
plainText, err := prp.decrypt(encrypted)
if err != nil {
return DecryptAESError, nil
}
//验证appid是否一致(消息来源是否一致)
if err := w.validMessageSource(plainText); err != nil {
return ValidateAppidError, nil
}
return SuccessCode, plainText
}

// encryptMsg aes消息加密
func (w *msgCrypt) encryptMsg(message []byte) (string, error) {
//计算消息长度
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.BigEndian, int32(len(message)))
if err != nil {
return "", err
}
messageLength := buf.Bytes()
//生成随机字符串
randBytes := []byte(makeRandomString(16))
plainData := bytes.Join([][]byte{randBytes, messageLength, message, []byte(w.appid)}, nil)
prp := NewPrpCrypt(w.aesKey)
//消息加密
encrypted, err := prp.encrypt(plainData)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}

// validSignature 验证签名是否一致
func (w *msgCrypt) validSignature(msgSignature string, timestamp, nonce string, encrypted string) error {
validSignature := GetSignature(timestamp, nonce, encrypted, w.token)
if validSignature != msgSignature {
return errors.New("签名不一致:valid sign error")
}
return nil
}

// validMessageSource 验证消息来源
func (w *msgCrypt) validMessageSource(plainText []byte) error {
messageLength := GetMessageLength(plainText)
//获取appid位置
appIdStartPos := 20 + messageLength
id := plainText[appIdStartPos : int(appIdStartPos)+len(w.appid)]
if string(id) != w.appid {
return errors.New("消息来源不一致:Appid is invalid")
}
return nil
}

+ 3
- 0
app/md/app_redis_key.go Ver arquivo

@@ -15,4 +15,7 @@ const (
KEY_SYS_CFG_CACHE = "sys_cfg_cache"

CfgCacheTime = 86400

MasterComponentVerifyTicket = "master_component_verify_ticket:%s"
MasterComponentAccessToken = "master_component_access_token:%s"
)

+ 2
- 1
app/router/router.go Ver arquivo

@@ -51,10 +51,11 @@ func Init() *gin.Engine {

func route(r *gin.RouterGroup) {
r.GET("/test", hdl.Demo)
wxOpenNotify := r.Group("/wxOpenNotify")
wxOpenNotify := r.Group("/wxOpen")
{
wxOpenNotify.Any("/:APPID/wechatMsgRecieve", hdl.WechatMsgRecieve)
wxOpenNotify.Any("/setTicket", hdl.SetTicket)
wxOpenNotify.GET("/getPreAuthCode", hdl.GetPreAuthCode)
}

r.Use(mw.DB) // 以下接口需要用到数据库


+ 1
- 1
go.mod Ver arquivo

@@ -5,7 +5,7 @@ go 1.18
//replace code.fnuoos.com/zhimeng/model.git => E:/company/ad/models

require (
code.fnuoos.com/zhimeng/model.git v0.0.3-0.20240819041103-0f30b4455fec
code.fnuoos.com/zhimeng/model.git v0.0.3-0.20240820125928-6ce4c06dda24
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
github.com/boombuler/barcode v1.0.1
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5


Carregando…
Cancelar
Salvar