package wechat_service import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha1" "encoding/base64" "encoding/binary" "encoding/json" "encoding/xml" "errors" "fmt" "io" "io/ioutil" math_rand "math/rand" "net/http" "sort" "strings" "time" "SCRM/service" "SCRM/utils" "github.com/astaxie/beego" "github.com/astaxie/beego/context" "github.com/astaxie/beego/httplib" "github.com/silenceper/wechat/util" ) func init() { AesKey = EncodingAESKey2AESKey(encodingAESKey) } var ( //以下均为公众号管理后台设置项 token = beego.AppConfig.String("openwechattoken") appID = beego.AppConfig.String("openwechatappid") encodingAESKey = beego.AppConfig.String("openwechatencodingaeskey") ) func MakeMsgSignature(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(timestamp, nonce, msgEncrypt) if msgSignatureGen != msgSignatureIn { return false } return true } 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 } var AesKey []byte func EncodingAESKey2AESKey(encodingKey string) []byte { data, _ := base64.StdEncoding.DecodeString(encodingKey + "=") return data } func ValidateAppId(id []byte) bool { if string(id) == appID { return true } return false } func ParseEncryptTextRequestBody(plainText []byte) (*TextRequestBody, error) { // Read length buf := bytes.NewBuffer(plainText[16:20]) var length int32 binary.Read(buf, binary.BigEndian, &length) // appID validation appIDstart := 20 + length id := plainText[appIDstart : int(appIDstart)+len(appID)] if !ValidateAppId(id) { return nil, errors.New("Appid is invalid") } textRequestBody := &TextRequestBody{} xml.Unmarshal(plainText[20:20+length], textRequestBody) return textRequestBody, nil } func Value2CDATA(v string) CDATAText { //return CDATAText{[]byte("")} return CDATAText{""} } func MakeEncryptXmlData(fromUserName, toUserName, timestamp, content string) (string, error) { 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") } buf := new(bytes.Buffer) err = binary.Write(buf, binary.BigEndian, int32(len(body))) if err != nil { return "", errors.New("Binary write err:" + err.Error()) } bodyLength := buf.Bytes() randomBytes := []byte("abcdefghijklmnop") plainData := bytes.Join([][]byte{randomBytes, bodyLength, body, []byte(appID)}, nil) cipherData, err := AesEncrypt(plainData, AesKey) if err != nil { return "", errors.New("AesEncrypt error") } return base64.StdEncoding.EncodeToString(cipherData), nil } // PadLength calculates padding length, from github.com/vgorin/cryptogo func PadLength(slice_length, blocksize int) (padlen int) { padlen = blocksize - slice_length%blocksize if padlen == 0 { padlen = blocksize } return padlen } //from github.com/vgorin/cryptogo func PKCS7Pad(message []byte, blocksize int) (padded []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(message), blocksize) // define PKCS7 padding block padding := bytes.Repeat([]byte{byte(padlen)}, padlen) // apply padding padded = append(message, padding...) return padded } // block size bigger or equal 256 is not currently supported panic("unsupported block size") } func AesEncrypt(plainData []byte, aesKey []byte) ([]byte, error) { k := len(aesKey) if len(plainData)%k != 0 { plainData = PKCS7Pad(plainData, k) } 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 } func MakeEncryptResponseBody(fromUserName, toUserName, content, nonce, timestamp string) ([]byte, error) { encryptBody := &EncryptResponseBody{} encryptXmlData, _ := MakeEncryptXmlData(fromUserName, toUserName, timestamp, content) encryptBody.Encrypt = Value2CDATA(encryptXmlData) encryptBody.MsgSignature = Value2CDATA(MakeMsgSignature(timestamp, nonce, encryptXmlData)) encryptBody.TimeStamp = timestamp encryptBody.Nonce = Value2CDATA(nonce) return xml.MarshalIndent(encryptBody, " ", " ") } func SendMsgTypeTextMessage(appid string, ToUserName string, FromUserName string, text string, nonce string, timestamp string, Ctx *context.Context, orgID int64) { arthorizer, err := GetAuthorizationByAppID(orgID, appid) if err != nil { utils.ErrorLog("SendMsgTypeTextMessage error:%s", err) Ctx.WriteString("success") return } messages, err := GetTextReplyMessagesByKey(arthorizer.UserOrgId, text) if err != nil { utils.ErrorLog("SendMsgTypeTextMessage error:%s", err) Ctx.WriteString("success") return } if len(messages) == 0 { utils.ErrorLog("SendMsgTypeTextMessage error: messages is nil") Ctx.WriteString("success") return } r := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) n := r.Intn(len(messages)) for key, item := range messages { if key == n { //安全模式下向用户回复消息也需要加密 respBody, e := MakeEncryptResponseBody(FromUserName, ToUserName, item.MessageContent, nonce, timestamp) if e != nil { Ctx.WriteString("success") return } Ctx.WriteString(string(respBody)) fmt.Println(string(respBody)) return } } Ctx.WriteString("success") return } //SendSubscribeTextMessage 当用户关注微信公众号,回复ta func SendSubscribeTextMessage(appid string, ToUserName string, FromUserName string, nonce string, timestamp string, Ctx *context.Context, orgID int64) { arthorizer, err := GetAuthorizationByAppID(orgID, appid) if err != nil { utils.ErrorLog("SendSubscribeTextMessage error:%s", err) Ctx.WriteString("success") return } message, err := GetOrgSubscribeReplyMessages(arthorizer.UserOrgId) if err != nil { utils.ErrorLog("SendSubscribeTextMessage error:%s", err) Ctx.WriteString("success") return } if message == nil { utils.ErrorLog("SendSubscribeTextMessage error: message is nil") Ctx.WriteString("success") return } respBody, e := MakeEncryptResponseBody(FromUserName, ToUserName, message.MessageContent, nonce, timestamp) if e != nil { Ctx.WriteString("success") return } Ctx.WriteString(string(respBody)) return } func SendClickButtonMessage(appid string, ToUserName string, FromUserName string, keyName string, nonce string, timestamp string, Ctx *context.Context, orgID int64) { arthorizer, err := GetAuthorizationByAppID(orgID, appid) if err != nil { utils.ErrorLog("SendClickButtonMessage error:%s", err) Ctx.WriteString("success") return } message, err := GetClickButtonReplyMessagesByOrgID(arthorizer.UserOrgId, keyName) if err != nil { utils.ErrorLog("SendClickButtonMessage error:%s", err) Ctx.WriteString("success") return } if message == nil { utils.ErrorLog("SendClickButtonMessage error: message is nil") Ctx.WriteString("success") return } respBody, e := MakeEncryptResponseBody(FromUserName, ToUserName, message.MessageContent, nonce, timestamp) if e != nil { Ctx.WriteString("success") return } Ctx.WriteString(string(respBody)) return } func GetReqPreAuthCode() (code string, err error) { redisClient := service.RedisClient() fmt.Println("redisClietent是设么", redisClient) defer redisClient.Close() componentAccessToken, err := redisClient.Get("sgj_patient:component_access_token").Result() fmt.Println("componentAccessToken是设么", componentAccessToken) if err != nil { utils.ErrorLog("component_access_token不存在") return } appID := beego.AppConfig.String("openwechatappid") type reqPreAuthCodeStruct struct { ComponentAppid string `json:"component_appid"` } // 通过 ComponentAccessToken 取 pre_auth_code // 3、获取预授权码pre_auth_code var ReqPreAuthCode reqPreAuthCodeStruct ReqPreAuthCode.ComponentAppid = appID //创建请求 uri := fmt.Sprintf("%s?component_access_token=%s", "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode", componentAccessToken) var responseBytes []byte responseBytes, err = util.PostJSON(uri, ReqPreAuthCode) if err != nil { utils.ErrorLog("%s", err) return } var res MPResult err = json.Unmarshal(responseBytes, &res) if err != nil { utils.ErrorLog("%s", err) return } if res.ErrCode > 0 { utils.ErrorLog("%s", res.ErrMsg) return } var pre_auth_code_struct PreAuthCode err = json.Unmarshal(responseBytes, &pre_auth_code_struct) if err != nil { utils.ErrorLog("pre_auth_code_struct Unmarshal json error:%s", err) return } code = pre_auth_code_struct.PreAuthCode return } //ComponentAPIQueryAuth 使用授权码换取公众号的接口调用凭据和授权信息 func ComponentAPIQueryAuth(AuthorizationCode string, ComponentAccessToken string) ([]byte, error) { //post的body内容,当前为json格式 reqbody := "{\"component_appid\":\"" + beego.AppConfig.String("openwechatappid") + "\",\"authorization_code\": \"" + AuthorizationCode + "\"}" //创建请求 postReq, err := http.NewRequest("POST", "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token="+ComponentAccessToken, strings.NewReader(reqbody)) //post内容 if err != nil { utils.ErrorLog("POST请求:omponent/api_query_auth请求创建失败:%s", err) return nil, err } //增加header postReq.Header.Set("Content-Type", "application/json; encoding=utf-8") //执行请求 client := &http.Client{} resp, err := client.Do(postReq) if err != nil { utils.ErrorLog("POST请求:创建请求失败:%s", err) return nil, err } //读取响应 body, err := ioutil.ReadAll(resp.Body) //此处可增加输入过滤 if err != nil { utils.ErrorLog("POST请求:读取body失败:%s", err) return nil, err } resp.Body.Close() return body, nil } //ComponentAPIGetAuthorizerInfo 利用AuthorizerAppid(公众号授权后,获取的appid)拉取公众号信息 func ComponentAPIGetAuthorizerInfo(AuthorizerAppid string, ComponentAccessToken string) ([]byte, error) { //post的body内容,当前为json格式 reqbody := "{\"component_appid\":\"" + beego.AppConfig.String("openwechatappid") + "\",\"authorizer_appid\": \"" + AuthorizerAppid + "\"}" //创建请求 postReq, err := http.NewRequest("POST", "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token="+ComponentAccessToken, //post链接 strings.NewReader(reqbody)) //post内容 if err != nil { fmt.Println("POST请求:omponent/api_get_authorizer_info请求创建失败") return nil, err } //增加header postReq.Header.Set("Content-Type", "application/json; encoding=utf-8") //执行请求 client := &http.Client{} resp, err := client.Do(postReq) if err != nil { fmt.Println("POST请求:创建请求失败") return nil, err } //读取响应 body, err := ioutil.ReadAll(resp.Body) //此处可增加输入过滤 if err != nil { fmt.Println("POST请求:读取body失败") return nil, err } resp.Body.Close() return body, nil } func PostJSON(uri string, jsonData []byte) ([]byte, error) { // jsonData, err := json.Marshal(obj) // if err != nil { // return nil, err // } jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1) jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1) jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1) body := bytes.NewBuffer(jsonData) response, err := http.Post(uri, "application/json;charset=utf-8", body) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode) } return ioutil.ReadAll(response.Body) } func SendMpWechatMenus(AuthorizerAccessToken string, jsonData []byte) (err error) { //读取响应 body, err := PostJSON("https://api.weixin.qq.com/cgi-bin/menu/create?access_token="+AuthorizerAccessToken, jsonData) fmt.Println("公众徐欧文是树森", err) fmt.Println("body", body) if err != nil { return } var result MPResult err = json.Unmarshal([]byte(body), &result) if err != nil { return } if result.ErrCode > 0 { utils.ErrorLog("MPResult ERROR:%v", result) err = errors.New(result.ErrMsg) return } return } func DeleteMpWechatMenus(AuthorizerAccessToken string) (err error) { uri := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s", AuthorizerAccessToken) b := httplib.Get(uri) var result MPResult err = b.ToJSON(&result) if err != nil { return } if result.ErrCode > 0 { err = errors.New(result.ErrMsg) return } return }