scrm-go

open_wechat_service.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package wechat_service
  2. import (
  3. "bytes"
  4. "crypto/aes"
  5. "crypto/cipher"
  6. "crypto/rand"
  7. "crypto/sha1"
  8. "encoding/base64"
  9. "encoding/binary"
  10. "encoding/json"
  11. "encoding/xml"
  12. "errors"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. math_rand "math/rand"
  17. "net/http"
  18. "sort"
  19. "strings"
  20. "time"
  21. "SCRM/service"
  22. "SCRM/utils"
  23. "github.com/astaxie/beego"
  24. "github.com/astaxie/beego/context"
  25. "github.com/silenceper/wechat/util"
  26. )
  27. func init() {
  28. AesKey = EncodingAESKey2AESKey(encodingAESKey)
  29. }
  30. var (
  31. //以下均为公众号管理后台设置项
  32. token = beego.AppConfig.String("openwechattoken")
  33. appID = beego.AppConfig.String("openwechatappid")
  34. encodingAESKey = beego.AppConfig.String("openwechatencodingaeskey")
  35. )
  36. func MakeMsgSignature(timestamp, nonce, msg_encrypt string) string {
  37. sl := []string{token, timestamp, nonce, msg_encrypt}
  38. sort.Strings(sl)
  39. s := sha1.New()
  40. io.WriteString(s, strings.Join(sl, ""))
  41. return fmt.Sprintf("%x", s.Sum(nil))
  42. }
  43. func ValidateMsg(timestamp, nonce, msgEncrypt, msgSignatureIn string) bool {
  44. msgSignatureGen := MakeMsgSignature(timestamp, nonce, msgEncrypt)
  45. if msgSignatureGen != msgSignatureIn {
  46. return false
  47. }
  48. return true
  49. }
  50. func AesDecrypt(cipherData []byte, aesKey []byte) ([]byte, error) {
  51. k := len(aesKey) //PKCS#7
  52. if len(cipherData)%k != 0 {
  53. return nil, errors.New("crypto/cipher: ciphertext size is not multiple of aes key length")
  54. }
  55. block, err := aes.NewCipher(aesKey)
  56. if err != nil {
  57. return nil, err
  58. }
  59. iv := make([]byte, aes.BlockSize)
  60. if _, err := io.ReadFull(rand.Reader, iv); err != nil {
  61. return nil, err
  62. }
  63. blockMode := cipher.NewCBCDecrypter(block, iv)
  64. plainData := make([]byte, len(cipherData))
  65. blockMode.CryptBlocks(plainData, cipherData)
  66. return plainData, nil
  67. }
  68. var AesKey []byte
  69. func EncodingAESKey2AESKey(encodingKey string) []byte {
  70. data, _ := base64.StdEncoding.DecodeString(encodingKey + "=")
  71. return data
  72. }
  73. func ValidateAppId(id []byte) bool {
  74. if string(id) == appID {
  75. return true
  76. }
  77. return false
  78. }
  79. func ParseEncryptTextRequestBody(plainText []byte) (*TextRequestBody, error) {
  80. // Read length
  81. buf := bytes.NewBuffer(plainText[16:20])
  82. var length int32
  83. binary.Read(buf, binary.BigEndian, &length)
  84. // appID validation
  85. appIDstart := 20 + length
  86. id := plainText[appIDstart : int(appIDstart)+len(appID)]
  87. if !ValidateAppId(id) {
  88. return nil, errors.New("Appid is invalid")
  89. }
  90. textRequestBody := &TextRequestBody{}
  91. xml.Unmarshal(plainText[20:20+length], textRequestBody)
  92. return textRequestBody, nil
  93. }
  94. func Value2CDATA(v string) CDATAText {
  95. //return CDATAText{[]byte("<![CDATA[" + v + "]]>")}
  96. return CDATAText{"<![CDATA[" + v + "]]>"}
  97. }
  98. func MakeEncryptXmlData(fromUserName, toUserName, timestamp, content string) (string, error) {
  99. textResponseBody := &TextResponseBody{}
  100. textResponseBody.FromUserName = Value2CDATA(fromUserName)
  101. textResponseBody.ToUserName = Value2CDATA(toUserName)
  102. textResponseBody.MsgType = Value2CDATA("text")
  103. textResponseBody.Content = Value2CDATA(content)
  104. textResponseBody.CreateTime = timestamp
  105. body, err := xml.MarshalIndent(textResponseBody, " ", " ")
  106. if err != nil {
  107. return "", errors.New("xml marshal error")
  108. }
  109. buf := new(bytes.Buffer)
  110. err = binary.Write(buf, binary.BigEndian, int32(len(body)))
  111. if err != nil {
  112. return "", errors.New("Binary write err:" + err.Error())
  113. }
  114. bodyLength := buf.Bytes()
  115. randomBytes := []byte("abcdefghijklmnop")
  116. plainData := bytes.Join([][]byte{randomBytes, bodyLength, body, []byte(appID)}, nil)
  117. cipherData, err := AesEncrypt(plainData, AesKey)
  118. if err != nil {
  119. return "", errors.New("AesEncrypt error")
  120. }
  121. return base64.StdEncoding.EncodeToString(cipherData), nil
  122. }
  123. // PadLength calculates padding length, from github.com/vgorin/cryptogo
  124. func PadLength(slice_length, blocksize int) (padlen int) {
  125. padlen = blocksize - slice_length%blocksize
  126. if padlen == 0 {
  127. padlen = blocksize
  128. }
  129. return padlen
  130. }
  131. //from github.com/vgorin/cryptogo
  132. func PKCS7Pad(message []byte, blocksize int) (padded []byte) {
  133. // block size must be bigger or equal 2
  134. if blocksize < 1<<1 {
  135. panic("block size is too small (minimum is 2 bytes)")
  136. }
  137. // block size up to 255 requires 1 byte padding
  138. if blocksize < 1<<8 {
  139. // calculate padding length
  140. padlen := PadLength(len(message), blocksize)
  141. // define PKCS7 padding block
  142. padding := bytes.Repeat([]byte{byte(padlen)}, padlen)
  143. // apply padding
  144. padded = append(message, padding...)
  145. return padded
  146. }
  147. // block size bigger or equal 256 is not currently supported
  148. panic("unsupported block size")
  149. }
  150. func AesEncrypt(plainData []byte, aesKey []byte) ([]byte, error) {
  151. k := len(aesKey)
  152. if len(plainData)%k != 0 {
  153. plainData = PKCS7Pad(plainData, k)
  154. }
  155. block, err := aes.NewCipher(aesKey)
  156. if err != nil {
  157. return nil, err
  158. }
  159. iv := make([]byte, aes.BlockSize)
  160. if _, err := io.ReadFull(rand.Reader, iv); err != nil {
  161. return nil, err
  162. }
  163. cipherData := make([]byte, len(plainData))
  164. blockMode := cipher.NewCBCEncrypter(block, iv)
  165. blockMode.CryptBlocks(cipherData, plainData)
  166. return cipherData, nil
  167. }
  168. func MakeEncryptResponseBody(fromUserName, toUserName, content, nonce, timestamp string) ([]byte, error) {
  169. encryptBody := &EncryptResponseBody{}
  170. encryptXmlData, _ := MakeEncryptXmlData(fromUserName, toUserName, timestamp, content)
  171. encryptBody.Encrypt = Value2CDATA(encryptXmlData)
  172. encryptBody.MsgSignature = Value2CDATA(MakeMsgSignature(timestamp, nonce, encryptXmlData))
  173. encryptBody.TimeStamp = timestamp
  174. encryptBody.Nonce = Value2CDATA(nonce)
  175. return xml.MarshalIndent(encryptBody, " ", " ")
  176. }
  177. func SendMsgTypeTextMessage(appid string, ToUserName string, FromUserName string, text string, nonce string, timestamp string, Ctx *context.Context, orgID int64) {
  178. arthorizer, err := GetAuthorizationByAppID(orgID, appid)
  179. if err != nil {
  180. utils.ErrorLog("SendMsgTypeTextMessage error:%s", err)
  181. Ctx.WriteString("success")
  182. return
  183. }
  184. messages, err := GetTextReplyMessagesByKey(arthorizer.UserOrgId, text)
  185. if err != nil {
  186. utils.ErrorLog("SendMsgTypeTextMessage error:%s", err)
  187. Ctx.WriteString("success")
  188. return
  189. }
  190. if len(messages) == 0 {
  191. utils.ErrorLog("SendMsgTypeTextMessage error: messages is nil")
  192. Ctx.WriteString("success")
  193. return
  194. }
  195. r := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
  196. n := r.Intn(len(messages))
  197. for key, item := range messages {
  198. if key == n {
  199. //安全模式下向用户回复消息也需要加密
  200. respBody, e := MakeEncryptResponseBody(FromUserName, ToUserName, item.MessageContent, nonce, timestamp)
  201. if e != nil {
  202. Ctx.WriteString("success")
  203. return
  204. }
  205. Ctx.WriteString(string(respBody))
  206. fmt.Println(string(respBody))
  207. return
  208. }
  209. }
  210. Ctx.WriteString("success")
  211. return
  212. }
  213. //SendSubscribeTextMessage 当用户关注微信公众号,回复ta
  214. func SendSubscribeTextMessage(appid string, ToUserName string, FromUserName string, nonce string, timestamp string, Ctx *context.Context, orgID int64) {
  215. arthorizer, err := GetAuthorizationByAppID(orgID, appid)
  216. if err != nil {
  217. utils.ErrorLog("SendSubscribeTextMessage error:%s", err)
  218. Ctx.WriteString("success")
  219. return
  220. }
  221. message, err := GetSubscribeReplyMessagesByOrgID(arthorizer.UserOrgId)
  222. if err != nil {
  223. utils.ErrorLog("SendSubscribeTextMessage error:%s", err)
  224. Ctx.WriteString("success")
  225. return
  226. }
  227. if message == nil {
  228. utils.ErrorLog("SendSubscribeTextMessage error: message is nil")
  229. Ctx.WriteString("success")
  230. return
  231. }
  232. respBody, e := MakeEncryptResponseBody(FromUserName, ToUserName, message.MessageContent, nonce, timestamp)
  233. if e != nil {
  234. Ctx.WriteString("success")
  235. return
  236. }
  237. Ctx.WriteString(string(respBody))
  238. return
  239. }
  240. func SendClickButtonMessage(appid string, ToUserName string, FromUserName string, keyName string, nonce string, timestamp string, Ctx *context.Context, orgID int64) {
  241. arthorizer, err := GetAuthorizationByAppID(orgID, appid)
  242. if err != nil {
  243. utils.ErrorLog("SendClickButtonMessage error:%s", err)
  244. Ctx.WriteString("success")
  245. return
  246. }
  247. message, err := GetClickButtonReplyMessagesByOrgID(arthorizer.UserOrgId, keyName)
  248. if err != nil {
  249. utils.ErrorLog("SendClickButtonMessage error:%s", err)
  250. Ctx.WriteString("success")
  251. return
  252. }
  253. if message == nil {
  254. utils.ErrorLog("SendClickButtonMessage error: message is nil")
  255. Ctx.WriteString("success")
  256. return
  257. }
  258. respBody, e := MakeEncryptResponseBody(FromUserName, ToUserName, message.MessageContent, nonce, timestamp)
  259. if e != nil {
  260. Ctx.WriteString("success")
  261. return
  262. }
  263. Ctx.WriteString(string(respBody))
  264. return
  265. }
  266. func GetReqPreAuthCode() (code string, err error) {
  267. redisClient := service.RedisClient()
  268. defer redisClient.Close()
  269. componentAccessToken, err := redisClient.Get("sgj_patient:component_access_token").Result()
  270. if err != nil {
  271. utils.ErrorLog("component_access_token不存在")
  272. return
  273. }
  274. appID := beego.AppConfig.String("openwechatappid")
  275. type reqPreAuthCodeStruct struct {
  276. ComponentAppid string `json:"component_appid"`
  277. }
  278. // 通过 ComponentAccessToken 取 pre_auth_code
  279. // 3、获取预授权码pre_auth_code
  280. var ReqPreAuthCode reqPreAuthCodeStruct
  281. ReqPreAuthCode.ComponentAppid = appID
  282. //创建请求
  283. uri := fmt.Sprintf("%s?component_access_token=%s", "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode", componentAccessToken)
  284. var responseBytes []byte
  285. responseBytes, err = util.PostJSON(uri, ReqPreAuthCode)
  286. if err != nil {
  287. utils.ErrorLog("%s", err)
  288. return
  289. }
  290. var res MPResult
  291. err = json.Unmarshal(responseBytes, &res)
  292. if err != nil {
  293. utils.ErrorLog("%s", err)
  294. return
  295. }
  296. if res.ErrCode > 0 {
  297. utils.ErrorLog("%s", res.ErrMsg)
  298. return
  299. }
  300. var pre_auth_code_struct PreAuthCode
  301. err = json.Unmarshal(responseBytes, &pre_auth_code_struct)
  302. if err != nil {
  303. utils.ErrorLog("pre_auth_code_struct Unmarshal json error:%s", err)
  304. return
  305. }
  306. code = pre_auth_code_struct.PreAuthCode
  307. return
  308. }
  309. //ComponentAPIQueryAuth 使用授权码换取公众号的接口调用凭据和授权信息
  310. func ComponentAPIQueryAuth(AuthorizationCode string, ComponentAccessToken string) ([]byte, error) {
  311. //post的body内容,当前为json格式
  312. reqbody := "{\"component_appid\":\"" + beego.AppConfig.String("openwechatappid") + "\",\"authorization_code\": \"" + AuthorizationCode + "\"}"
  313. //创建请求
  314. postReq, err := http.NewRequest("POST",
  315. "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token="+ComponentAccessToken,
  316. strings.NewReader(reqbody)) //post内容
  317. if err != nil {
  318. utils.ErrorLog("POST请求:omponent/api_query_auth请求创建失败:%s", err)
  319. return nil, err
  320. }
  321. //增加header
  322. postReq.Header.Set("Content-Type", "application/json; encoding=utf-8")
  323. //执行请求
  324. client := &http.Client{}
  325. resp, err := client.Do(postReq)
  326. if err != nil {
  327. utils.ErrorLog("POST请求:创建请求失败:%s", err)
  328. return nil, err
  329. }
  330. //读取响应
  331. body, err := ioutil.ReadAll(resp.Body) //此处可增加输入过滤
  332. if err != nil {
  333. utils.ErrorLog("POST请求:读取body失败:%s", err)
  334. return nil, err
  335. }
  336. resp.Body.Close()
  337. return body, nil
  338. }
  339. //ComponentAPIGetAuthorizerInfo 利用AuthorizerAppid(公众号授权后,获取的appid)拉取公众号信息
  340. func ComponentAPIGetAuthorizerInfo(AuthorizerAppid string, ComponentAccessToken string) ([]byte, error) {
  341. //post的body内容,当前为json格式
  342. reqbody := "{\"component_appid\":\"" + beego.AppConfig.String("openwechatappid") + "\",\"authorizer_appid\": \"" + AuthorizerAppid + "\"}"
  343. //创建请求
  344. postReq, err := http.NewRequest("POST",
  345. "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token="+ComponentAccessToken, //post链接
  346. strings.NewReader(reqbody)) //post内容
  347. if err != nil {
  348. fmt.Println("POST请求:omponent/api_get_authorizer_info请求创建失败")
  349. return nil, err
  350. }
  351. //增加header
  352. postReq.Header.Set("Content-Type", "application/json; encoding=utf-8")
  353. //执行请求
  354. client := &http.Client{}
  355. resp, err := client.Do(postReq)
  356. if err != nil {
  357. fmt.Println("POST请求:创建请求失败")
  358. return nil, err
  359. }
  360. //读取响应
  361. body, err := ioutil.ReadAll(resp.Body) //此处可增加输入过滤
  362. if err != nil {
  363. fmt.Println("POST请求:读取body失败")
  364. return nil, err
  365. }
  366. resp.Body.Close()
  367. return body, nil
  368. }