commit 3b2b97f860cfe43a751055b4972cbefe45f4efa8 Author: dustoair <107600816+dustoair@users.noreply.github.com> Date: Sun Jul 24 11:39:52 2022 +0800 wechat gateway diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b08030 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.idea +go.sum +main.exe +server.exe +log.txt +logs +*.log +uploads +gin.log +cert +challenge +.env +application.yaml +nogit_test.go \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b3f83cb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +################################################################################ +## MAIN STAGE ## +################################################################################ +# Copy the manager into the distroless image. +#FROM scratch +#FROM hub.sre.ink/base/distroless-static:nonroot-20210710 +#FROM mirror.ccs.tencentyun.com/library/alpine:3.13 +FROM alpine:3.15.0 +#FROM hub.sre.ink/base/alpine:3.15.0 +#FROM centos:7.9.2009 +LABEL Description="sre wechat reciver gateway" +MAINTAINER sre +#RUN echo 'https://mirrors.cloud.tencent.com/alpine/v3.13/main' > /etc/apk/repositories \ +# && echo 'https://mirrors.cloud.tencent.com/alpine/v3.13/community' >>/etc/apk/repositories \ +# && apk update && apk add tzdata && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ +# && echo "Asia/Shanghai" > /etc/timezone +RUN apk update && apk add tzdata && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && echo "Asia/Shanghai" > /etc/timezone + +WORKDIR /app +COPY wechat.bin /app/wechat.bin +COPY application.yaml /app/application.yaml +ADD kubectl /usr/bin/kubectl +RUN mkdir -p /root/.kube +ADD config /root/.kube/config +RUN chmod -R 777 /app + +#USER nonroot:nonroot +EXPOSE 8080 +ENTRYPOINT ["/app/wechat.bin"] \ No newline at end of file diff --git a/application-dev.yaml b/application-dev.yaml new file mode 100644 index 0000000..d2b5e5d --- /dev/null +++ b/application-dev.yaml @@ -0,0 +1,8 @@ +server: + port: 8080 +wechat: + CorpId: ww24112334343434 + AppToken: xPh343434343434343434 + EncodingAesKey: SJmT3434343434343434343434 + SendSecret: Jk343434444444444444444343434 + SendAid: 1000002 \ No newline at end of file diff --git a/global/cmds.go b/global/cmds.go new file mode 100644 index 0000000..ea6541e --- /dev/null +++ b/global/cmds.go @@ -0,0 +1,9 @@ +package global + +var CMDList = []string{ + "kubectl ", + "df -h", + "free -m", + "history", + "last", +} diff --git a/global/emoj.go b/global/emoj.go new file mode 100644 index 0000000..771c1f2 --- /dev/null +++ b/global/emoj.go @@ -0,0 +1,224 @@ +package global + +var EmojMap = map[string]string{ + "/::-|": "汗", + "/:pig": "猪头", + "/:cake": "蛋糕", + "/::!": "恐慌", + "[大兵]": "大兵", + "/:showlove": "吻", + "/::Z": "睡", + "/::Q": "抓狂", + "/:--b": "流汗", + "/:B-)": "坏笑", + "/:X-)": "奸笑", + "/:fade": "花谢了", + "[爆竹]": "爆竹", + "/:sun": "太阳", + "/::P": "调皮", + "[Sigh]": "叹气", + "/::@": "发怒", + "[Awesome]": "666", + "/::T": "呕吐", + "/:P-(": "委屈", + "/:8*": "可怜", + "[Hey]": "打招呼", + "[Concerned]": "关心", + "[Flushed]": "圆眼睛", + "[Facepalm]": "绷不住", + "[Smart]": "机灵", + "/:heart": "爱心", + "/:>-|": "鄙视", + "/:cake/:gift": "生日礼物", + "/::-S": "嚷嚷", + "/::d": "目瞪口呆", + "/:,@o": "咦", + "/:rose": "玫瑰", + "/::(": "难过", + "[Onlooker]": "吃瓜", + "/:hug": "抱抱", + "/:dig": "抠鼻", + "/:,@!": "衰", + "/::O": "惊讶", + "[Hurt]": "受伤", + "/:?": "疑惑", + "/:!!!": "骷髅", + "[Sick]": "口罩", + "[Blessing]": "福气", + "/:coffee": "咖啡", + "/::|": "呆住", + "/:handclap": "故障", + "/::'|": "快哭了", + "[LetMeSee]": "我看看", + "[微笑]": "微笑", + "[Yeah!]": "茄子", + "/:@>": "右哼哼", + "/:@@": "拳头", + "[Party]": "祝贺", + "/:share": "握手", + "/:wipe": "擦汗", + "/:,@P": "抿嘴笑", + "/:|-)": "熊猫眼", + "[Doge]": "狗头", + "/::<": "泪奔", + "[Broken]": "裂开", + "[Fireworks]": "烟花", + "[NoProb]": "妥妥的", + "/:strong/:beer": "牛皮", + "[Duh]": "没精神", + "[Emm]": "嗯啊", + "/:jj": "勾引", + "/:moon": "月亮", + "[Lol]": "喜极而泣", + "/:jump": "跳一跳", + "/::~": "撇嘴", + "/:weak": "弱鸡", + "/:,@@": "困", + "/::>": "憨笑", + "[Happy]": "眯眼笑", + "/:shit": "便便", + "[Packet]": "红包", + "[MyBad]": "掌掴", + "[Boring]": "无聊", + "/::D": "裂嘴笑", + "[Respect]": "佩服", + "/:pd": "菜刀", + "/:xx": "敲打", + "/:@)": "承让", + "/::B": "色", + "/:8-)": "酷", + "/::$": "害羞", + "[Wow]": "哇偶", + "/:break": "心碎", + "/:v": "胜利", + "/::'(": "大哭", + "/:circle": "转圈圈", + "[Smirk]": "斜眼笑", + "/:shake": "瑟瑟发抖", + "[OMG]": "老天爷", + "/:ok": "ok", + "[Rich]": "发财", + "/:strong": "赞", + "[Sweats]": "汗水", + "/:,@x": "嘘", + "/:bye": "再见", + "[GoForIt]": "加油啊", + "/:gift": "礼物", + "/:,@-D": "害羞笑", + "[Worship]": "祈祷", + "/::X": "住口", + "/::*": "亲亲", + "[LetDown]": "无精打采", +} +var EmojMapReverse = map[string]string{ + "抠鼻": "/:dig", + "斜眼笑": "[Smirk]", + "衰": "/:,@!", + "鄙视": "/:>-|", + "狗头": "[Doge]", + "猪头": "/:pig", + "便便": "/:shit", + "裂嘴笑": "/::D", + "裂开": "[Broken]", + "我看看": "[LetMeSee]", + "胜利": "/:v", + "红包": "[Packet]", + "太阳": "/:sun", + "月亮": "/:moon", + "扇耳光": "[MyBad]", + "握手": "/:share", + "赞": "/:strong", + "牛皮": "/:strong/:beer", + "喜极而泣": "[Lol]", + "困": "/:,@@", + "大哭": "/::'(", + "睡": "/::Z", + "呕吐": "/::T", + "茄子": "[Yeah!]", + "承让": "/:@)", + "蛋糕": "/:cake", + "咖啡": "/:coffee", + "菜刀": "/:pd", + "礼物": "/:gift", + "生日礼物": "/:cake", + "微笑": "[微笑]", + "撇嘴": "/::~", + "色": "/::B", + "呆住": "/::|", + "酷": "/:8-)", + "泪奔": "/::<", + "害羞": "/::$", + "住口": "/::X", + "汗": "/::-|", + "发怒": "/::@", + "调皮": "/::P", + "惊讶": "/::O", + "难过": "/::(", + "流汗": "/:--b", + "抓狂": "/::Q", + "抿嘴笑": "/:,@P", + "害羞笑": "/:,@-D", + "目瞪口呆": "/::d", + "咦": "/:,@o", + "熊猫眼": "/:|-)", + "恐慌": "/::!", + "憨笑": "/::>", + "大兵": "[大兵]", + "嚷嚷": "/::-S", + "疑惑": "/:?", + "嘘": "/:,@x", + "目眩": "/:,@@", + "骷髅": "/:!!!", + "敲打": "/:xx", + "再见": "/:bye", + "擦汗": "/:wipe", + "故障": "/:handclap", + "坏笑": "/:B-)", + "右哼哼": "/:@>", + "委屈": "/:P-(", + "快哭了": "/::'|", + "奸笑": "/:X-)", + "亲亲": "/::*", + "可怜": "/:8*", + "眯眼笑": "[Happy]", + "口罩": "[Sick]", + "圆眼睛": "[Flushed]", + "无精打采": "[LetDown]", + "没精神": "[Duh]", + "打招呼": "[Hey]", + "绷不住": "[Facepalm]", + "机灵": "[Smart]", + "关心": "[Concerned]", + "吃瓜": "[Onlooker]", + "加油啊": "[GoForIt]", + "汗水": "[Sweats]", + "老天爷": "[OMG]", + "嗯啊": "[Emm]", + "佩服": "[Respect]", + "妥妥的": "[NoProb]", + "掌掴": "[MyBad]", + "哇偶": "[Wow]", + "无聊": "[Boring]", + "666": "[Awesome]", + "叹气": "[Sigh]", + "受伤": "[Hurt]", + "吻": "/:showlove", + "爱心": "/:heart", + "心碎": "/:break", + "抱抱": "/:hug", + "弱鸡": "/:weak", + "勾引": "/:jj", + "拳头": "/:@@", + "ok": "/:ok", + "祈祷": "[Worship]", + "玫瑰": "/:rose", + "花谢了": "/:fade", + "祝贺": "[Party]", + "发财": "[Rich]", + "福气": "[Blessing]", + "烟花": "[Fireworks]", + "爆竹": "[爆竹]", + "跳一跳": "/:jump", + "瑟瑟发抖": "/:shake", + "转圈圈": "/:circle", +} diff --git a/global/emoji_test.go b/global/emoji_test.go new file mode 100644 index 0000000..9829046 --- /dev/null +++ b/global/emoji_test.go @@ -0,0 +1,12 @@ +package global + +import ( + "fmt" + "testing" +) + +func TestEmoj(tt *testing.T) { + for k, v := range EmojMap { + fmt.Println(fmt.Sprintf("%s:%s", v, k)) + } +} diff --git a/global/var.go b/global/var.go new file mode 100644 index 0000000..c8ea82b --- /dev/null +++ b/global/var.go @@ -0,0 +1,15 @@ +package global + +import ( + "context" + "github.com/go-redis/redis/v8" +) + +var ( + CTX = context.Background() + RedisDb *redis.Client + HistoryCmds []string + EnableReply = true + EnableAiReply = true + EnableCmdExec = true +) diff --git a/global/wechat.go b/global/wechat.go new file mode 100644 index 0000000..2dc7e25 --- /dev/null +++ b/global/wechat.go @@ -0,0 +1,11 @@ +package global + +var ( + WxCrypt *WXBizMsgCrypt + WechatCorpId string + WechatToken string //微信回调的token + WechatEncodingAesKey string + WechatSendSecret string + WechatSendAid string + WechatAccessToken string //主动发消息的token +) diff --git a/global/wxbizmsgcrypt.go b/global/wxbizmsgcrypt.go new file mode 100644 index 0000000..6f77885 --- /dev/null +++ b/global/wxbizmsgcrypt.go @@ -0,0 +1,315 @@ +package global + +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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..74e360f --- /dev/null +++ b/go.mod @@ -0,0 +1,26 @@ +module WechatGateWay + +go 1.19 + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.12.0 // indirect + github.com/subosito/gotenv v1.3.0 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect +) diff --git a/handle/handleEntry.go b/handle/handleEntry.go new file mode 100644 index 0000000..06f6e23 --- /dev/null +++ b/handle/handleEntry.go @@ -0,0 +1,26 @@ +package handle + +import ( + "fmt" + "log" + "net/http" +) + +func HandleTencent(w http.ResponseWriter, r *http.Request) { + if r.URL.RequestURI() == "/favicon.ico" { + return + } + switch r.Method { + case "GET": + // 微信企业号验证 + log.Println("接收到验证请求") + handleVerify(w, r) + case "POST": + // 微信企业号接收到消息 + log.Println("接收到消息") + handleMessage(w, r) + default: + log.Println("接收到非法请求") + fmt.Fprintln(w, "接收到非法请求") + } +} diff --git a/handle/handleMessage.go b/handle/handleMessage.go new file mode 100644 index 0000000..f68554c --- /dev/null +++ b/handle/handleMessage.go @@ -0,0 +1,63 @@ +package handle + +import ( + "WechatGateWay/global" + "encoding/xml" + "io" + "log" + "net/http" +) + +func handleMessage(w http.ResponseWriter, r *http.Request) { + msgSignature := r.URL.Query().Get("msg_signature") //消息签名 + timestamp := r.URL.Query().Get("timestamp") + nonce := r.URL.Query().Get("nonce") //随机数,由企业自行生成 + body, err := io.ReadAll(r.Body) + if err != nil { + log.Println("读取Body错误", err.Error()) + return + } else { + msg, err := global.WxCrypt.DecryptMsg(msgSignature, timestamp, nonce, body) + if err != nil { + log.Println("解密消息Body错误", err.ErrMsg) + return + } else { + var msgContent MsgContent + err := xml.Unmarshal(msg, &msgContent) + if err != nil { + log.Println("反序列化错误") + return + } else { + log.Println("消息验证成功:", msgContent) + if global.EnableReply { + Reply(msgContent, timestamp, nonce, w) + } else { + return + } + + } + } + } +} + +func Reply(msgContent MsgContent, timestamp, nonce string, w http.ResponseWriter) { + switch msgContent.MsgType { + case "text": + log.Println("接收到文本消息") + replyText(msgContent, timestamp, nonce, w) + case "image": + log.Println("收到图片消息") + replyImage(msgContent, timestamp, nonce, w) + return + case "voice": + log.Println("收到语音消息") + return + case "event": + log.Println("收到事件消息") + return + case "location": + log.Println("收到地理位置消息") + return + } + +} diff --git a/handle/handleMessage_test.go b/handle/handleMessage_test.go new file mode 100644 index 0000000..944bf07 --- /dev/null +++ b/handle/handleMessage_test.go @@ -0,0 +1 @@ +package handle diff --git a/handle/handleVerify.go b/handle/handleVerify.go new file mode 100644 index 0000000..a775ab8 --- /dev/null +++ b/handle/handleVerify.go @@ -0,0 +1,26 @@ +package handle + +import ( + "WechatGateWay/global" + "log" + "net/http" +) + +func handleVerify(w http.ResponseWriter, r *http.Request) { + msgSignature := r.URL.Query().Get("msg_signature") + timestamp := r.URL.Query().Get("timestamp") + nonce := r.URL.Query().Get("nonce") + echoStr := r.URL.Query().Get("echostr") + // 合法性验证 + echoStrBytes, err := global.WxCrypt.VerifyURL(msgSignature, timestamp, nonce, echoStr) + if err != nil { + log.Println("验证失败", err.ErrMsg) + } else { + log.Println("验证成功", string(echoStrBytes)) + // 需要返回才能通过验证 + _, err := w.Write(echoStrBytes) + if err != nil { + log.Println("返回验证结果失败", err.Error()) + } + } +} diff --git a/handle/replyImage.go b/handle/replyImage.go new file mode 100644 index 0000000..1de29d8 --- /dev/null +++ b/handle/replyImage.go @@ -0,0 +1,19 @@ +package handle + +import ( + "WechatGateWay/third_part" + "fmt" + "net/http" +) + +func replyImage(msgContent MsgContent, timestamp, nonce string, w http.ResponseWriter) { + fmt.Println(msgContent.ToUsername) + fmt.Println(msgContent.FromUsername) + fmt.Println(msgContent.CreateTime) + fmt.Println(msgContent.MsgType) + fmt.Println(msgContent.Content) + fmt.Println(msgContent.Msgid) + fmt.Println(msgContent.Agentid) + + third_part.SendPicFile(msgContent.FromUsername, "/app/data/ygg.jpg") +} diff --git a/handle/replyText.go b/handle/replyText.go new file mode 100644 index 0000000..c5f24af --- /dev/null +++ b/handle/replyText.go @@ -0,0 +1,73 @@ +package handle + +import ( + "WechatGateWay/global" + "WechatGateWay/third_part" + "WechatGateWay/utils" + "encoding/xml" + "log" + "net/http" +) + +func replyText(msgContent MsgContent, timestamp, nonce string, w http.ResponseWriter) { + var replyContent string + if global.EnableCmdExec && utils.SliceContain(global.CMDList, msgContent.Content) { + //开启了命令执行功能,并且消息内容包含了命令 + log.Println("收到命令消息") + var cmdString string + if msgContent.Content == "last" { + cmdString = global.HistoryCmds[len(global.HistoryCmds)-1] + } else if msgContent.Content == "history" { + cmdString = utils.SlicePrint(global.HistoryCmds) + } else { + cmdString = msgContent.Content + } + global.HistoryCmds = append(global.HistoryCmds, msgContent.Content) + replyContent, err := utils.CMDShellTrick(cmdString) + if err != nil { + replyContent = "/:,@!执行失败:\n" + msgContent.Content + } else { + replyContent = "/::D执行成功:\n" + replyContent + //aiAnswer = "执行命令:" + msgContent.Content + } + } else { + var replyContentAi string + if _, ok := global.EmojMap[msgContent.Content]; ok { + //系统表情消息 取字典意思丢ai回复 + replyContentAi = third_part.AiChat(global.EmojMap[msgContent.Content]) + } else { + //普通文本消息直接丢ai + replyContentAi = third_part.AiChat(msgContent.Content) + } + //对ai结果 有表情包的上表情包 + if emoj, ok := utils.MapKey(global.EmojMap, replyContentAi); ok { + replyContent = emoj + replyContentAi + } else { + replyContent = replyContentAi + } + } + + //构造回复消息 + replyMsg, _ := xml.Marshal(ReplyTextMsg{ + ToUsername: msgContent.FromUsername, + FromUsername: msgContent.ToUsername, + CreateTime: msgContent.CreateTime, + MsgType: "text", + Content: replyContent, + }) + encryptMsg, cryptErr := global.WxCrypt.EncryptMsg(string(replyMsg), timestamp, nonce) + if cryptErr != nil { + log.Println("回复加密出错", cryptErr) + return + } else { + log.Println(string(encryptMsg)) + l, err := w.Write(encryptMsg) + if err != nil { + log.Println("返回消息失败") + return + } else { + log.Println("成功写入", l) + } + } + +} diff --git a/handle/struct.go b/handle/struct.go new file mode 100644 index 0000000..68f6f6d --- /dev/null +++ b/handle/struct.go @@ -0,0 +1,32 @@ +package handle + +// ToUserName 成员UserID +// FromUserName 企业微信CorpID +// CreateTime 消息创建时间(整型) +// MsgType 消息类型,此时固定为:text +// Content 文本消息内容,最长不超过2048个字节,超过将截断 +type ReplyTextMsg struct { + ToUsername string `xml:"ToUserName"` + FromUsername string `xml:"FromUserName"` + CreateTime uint32 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Content string `xml:"Content"` +} + +type ReplyImageMsg struct { + ToUsername string `xml:"ToUserName"` + FromUsername string `xml:"FromUserName"` + CreateTime uint32 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Content string `xml:"Content"` +} + +type MsgContent struct { + ToUsername string `xml:"ToUserName"` + FromUsername string `xml:"FromUserName"` + CreateTime uint32 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Content string `xml:"Content"` + Msgid string `xml:"MsgId"` + Agentid uint32 `xml:"AgentId"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..7e0bb9a --- /dev/null +++ b/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "WechatGateWay/global" + "WechatGateWay/handle" + "fmt" + "github.com/spf13/viper" + "log" + "net/http" + "os" +) + +func init() { + // 读取配置文件 + workDir, _ := os.Getwd() + viper.SetConfigName("application") + viper.SetConfigType("yml") + viper.AddConfigPath(workDir) + err := viper.ReadInConfig() + if err != nil { + fmt.Println("config file not found") + os.Exit(1) + } + global.WechatCorpId = viper.GetString("wechat.CorpId") + global.WechatToken = viper.GetString("wechat.AppToken") + global.WechatEncodingAesKey = viper.GetString("wechat.EncodingAesKey") + global.WechatSendSecret = viper.GetString("wechat.SendSecret") + global.WechatSendAid = viper.GetString("wechat.SendAid") + // receive_id 企业应用的回调,表示corpid + global.WxCrypt = global.NewWXBizMsgCrypt(global.WechatToken, global.WechatEncodingAesKey, global.WechatCorpId, global.XmlType) + //go third_part.GetRemoteToken(global.WechatCorpId, global.WechatSendSecret) + log.Println("server init success") +} +func main() { + // 开启一个http服务器,接收来自企业微信的消息 + http.HandleFunc("/", handle.HandleTencent) + port := viper.GetString("server.port") + if port == "" { + port = "8080" + } + log.Println("server start at port:", port) + log.Fatalln(http.ListenAndServe(":"+port, nil)) +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e61b779 --- /dev/null +++ b/readme.md @@ -0,0 +1,53 @@ + +# 接收消息与事件 +https://developer.work.weixin.qq.com/document/10514 + +微信消息加解密库 +https://github.com/go-laoji/wxbizmsgcrypt + +https://github.com/easychen/wecomchan/blob/main/go-wecomchan/wecomchan.go + +## build arm64 on oracle k8s +```bash +cd /root +rm -rf WechatGateWay +git clone https://git.sre.ink/go/WechatGateWay.git +cd WechatGateWay +/bin/cp -arf /root/wx.yaml application.yaml +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go mod tidy +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -v -a -o wechat.bin main.go +/bin/cp -arf /usr/bin/kubectl . +/bin/cp -arf /root/.kube/config . +docker build -t sre/wechatgateway:arm64 . +kubectl rollout restart deployment -n sre wehcat-gateway +``` + +## ingress +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-ip2region + namespace: sre + annotations: + kubernetes.io/ingress.class: "nginx" # 自动签发开关 + cert-manager.io/cluster-issuer: "letsencrypt-prod-http01" # 自动签发开关 +spec: + tls: + - hosts: + - test.com + secretName: ingress-tls-test-com # 需要修改 + rules: + - host: test.com + http: + paths: + - path: / + backend: + service: + name: ipregion + port: + number: 8080 + pathType: ImplementationSpecific +``` + + diff --git a/third_part/WeChat.go b/third_part/WeChat.go new file mode 100644 index 0000000..ee98f35 --- /dev/null +++ b/third_part/WeChat.go @@ -0,0 +1,374 @@ +package third_part + +import ( + "WechatGateWay/global" + "WechatGateWay/utils" + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math" + "mime/multipart" + "net/http" + "os" + "reflect" +) + +var ctx = global.CTX + +var GetTokenApi = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s" +var SendMessageApi = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s" +var UploadMediaApi = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" + +type Msg struct { + Content string `json:"content"` +} +type Pic struct { + MediaId string `json:"media_id"` +} +type JsonData struct { + ToUser string `json:"touser"` + AgentId string `json:"agentid"` + MsgType string `json:"msgtype"` + DuplicateCheckInterval int `json:"duplicate_check_interval"` + Text Msg `json:"text"` + Image Pic `json:"image"` +} + +// GetRemoteToken 从企业微信服务端API获取access_token,存在redis服务则缓存 +func GetRemoteToken(corpId, appSecret string) string { + getTokenUrl := fmt.Sprintf(GetTokenApi, corpId, appSecret) + //log.Println("getTokenUrl==>", getTokenUrl) + resp, err := http.Get(getTokenUrl) + if err != nil { + log.Println(err) + } + respData, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + } + resp.Body.Close() + tokenResponse := utils.ParseJson(string(respData)) + //log.Println("企业微信获取access_token接口返回==>", tokenResponse) + accessToken := tokenResponse["access_token"].(string) + + //存到gloabl + global.WechatAccessToken = accessToken + log.Println("WechatAccessToken已更新") + return accessToken +} + +// PostMsg 推送消息 +func PostMsg(postData JsonData, postUrl string) string { + postJson, _ := json.Marshal(postData) + log.Println("postJson ", string(postJson)) + log.Println("postUrl ", postUrl) + msgReq, err := http.NewRequest("POST", postUrl, bytes.NewBuffer(postJson)) + if err != nil { + log.Println(err) + } + msgReq.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(msgReq) + if err != nil { + log.Fatalln("企业微信发送应用消息接口报错==>", err) + } + defer msgReq.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + mediaResp := utils.ParseJson(string(body)) + log.Println("企业微信发送应用消息接口返回==>", mediaResp) + return string(body) +} + +// UploadMediaFile 上传临时素材并返回mediaId +func UploadMediaFile(msgType string, filename string, accessToken string) (string, float64) { + + // 企业微信图片上传不能大于2M + //if pic.Size > 2*1024*1024 { + // log.Fatalln("图片超过2M 无法发送") + // // 自定义code无效的图片文件 + // return "", 400 + // + //} + + //图片类型 + //fileExt := strings.ToLower(path.Ext(pic.Filename)) + //if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" { + // log.Fatalln("上传失败!只允许png,jpg,gif,jpeg文件") + // return "", 400 + //} + + // 读取上传的文件流。 + fd, err := os.Open(filename) + if err != nil { + fmt.Println("Error:", err) + return "", 400 + } + defer fd.Close() + fileInfo, err := os.Stat(filename) + if err != nil { + fmt.Println("Error:", err) + return "", 400 + } + + buf := new(bytes.Buffer) + writer := multipart.NewWriter(buf) + if createFormFile, err := writer.CreateFormFile("media", fileInfo.Name()); err == nil { + readAll, _ := ioutil.ReadAll(fd) + createFormFile.Write(readAll) + } + writer.Close() + + uploadMediaUrl := fmt.Sprintf(UploadMediaApi, accessToken, msgType) + log.Println("uploadMediaUrl==>", uploadMediaUrl) + newRequest, _ := http.NewRequest("POST", uploadMediaUrl, buf) + newRequest.Header.Set("Content-Type", writer.FormDataContentType()) + log.Println("Content-Type ", writer.FormDataContentType()) + client := &http.Client{} + resp, err := client.Do(newRequest) + respData, _ := ioutil.ReadAll(resp.Body) + mediaResp := utils.ParseJson(string(respData)) + log.Println("企业微信上传临时素材接口返回==>", mediaResp) + if err != nil { + log.Fatalln("上传临时素材出错==>", err) + return "", mediaResp["errcode"].(float64) + } else { + return mediaResp["media_id"].(string), float64(0) + } +} + +// UploadMedia 上传临时素材并返回mediaId +func UploadMedia(msgType string, pic *multipart.FileHeader, accessToken string) (string, float64) { + + // 企业微信图片上传不能大于2M + //if pic.Size > 2*1024*1024 { + // log.Fatalln("图片超过2M 无法发送") + // // 自定义code无效的图片文件 + // return "", 400 + // + //} + + //图片类型 + //fileExt := strings.ToLower(path.Ext(pic.Filename)) + //if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" { + // log.Fatalln("上传失败!只允许png,jpg,gif,jpeg文件") + // return "", 400 + //} + + // 读取上传的文件流。 + fd, err := pic.Open() + if err != nil { + fmt.Println("Error:", err) + return "", 400 + } + defer fd.Close() + + buf := new(bytes.Buffer) + writer := multipart.NewWriter(buf) + if createFormFile, err := writer.CreateFormFile("media", pic.Filename); err == nil { + readAll, _ := ioutil.ReadAll(fd) + createFormFile.Write(readAll) + } + writer.Close() + + uploadMediaUrl := fmt.Sprintf(UploadMediaApi, accessToken, msgType) + log.Println("uploadMediaUrl==>", uploadMediaUrl) + newRequest, _ := http.NewRequest("POST", uploadMediaUrl, buf) + newRequest.Header.Set("Content-Type", writer.FormDataContentType()) + log.Println("Content-Type ", writer.FormDataContentType()) + client := &http.Client{} + resp, err := client.Do(newRequest) + respData, _ := ioutil.ReadAll(resp.Body) + mediaResp := utils.ParseJson(string(respData)) + log.Println("企业微信上传临时素材接口返回==>", mediaResp) + if err != nil { + log.Fatalln("上传临时素材出错==>", err) + return "", mediaResp["errcode"].(float64) + } else { + return mediaResp["media_id"].(string), float64(0) + } +} + +// ValidateToken 判断accessToken是否失效 +// true-未失效, false-失效需重新获取 +func ValidateToken(errcode any) bool { + + codeTyp := reflect.TypeOf(errcode) + log.Println("errcode的数据类型==>", codeTyp) + if !codeTyp.Comparable() { + log.Printf("type is not comparable: %v", codeTyp) + return true + } + + // 如果errcode为42001表明token已失效,则清空redis中的token缓存 + // 已知codeType为float64 + if math.Abs(errcode.(float64)-float64(42001)) < 1e-3 { + log.Println("现需重新获取token") + return false + } + return true +} + +// GetAccessToken 获取企业微信的access_token +func GetAccessToken() string { + var WecomCid = global.WechatCorpId + var WecomSecret = global.WechatSendSecret + + accessToken := global.WechatAccessToken + if accessToken == "" { + log.Println("get access_token from remote API") + accessToken = GetRemoteToken(WecomCid, WecomSecret) + } else { + log.Println("get access_token from redis") + } + return accessToken +} + +// InitJsonData 初始化Json公共部分数据 +func InitJsonData(msgType, toUser string) JsonData { + var WecomAid = global.WechatSendAid + + return JsonData{ + ToUser: toUser, + AgentId: WecomAid, + MsgType: msgType, + DuplicateCheckInterval: 600, + } +} + +// due func +func SendText(toUser, msg string) { + var res http.ResponseWriter + // 获取token + accessToken := GetAccessToken() + // 默认token有效 + tokenValid := true + + // 准备发送应用消息所需参数 + postData := InitJsonData("text", toUser) + postData.Text = Msg{ + Content: msg, + } + + postStatus := "" + for i := 0; i <= 3; i++ { + sendMessageUrl := fmt.Sprintf(SendMessageApi, accessToken) + postStatus = PostMsg(postData, sendMessageUrl) + postResponse := utils.ParseJson(postStatus) + errcode := postResponse["errcode"] + log.Println("发送应用消息接口返回errcode==>", errcode) + tokenValid = ValidateToken(errcode) + // token有效则跳出循环继续执行,否则重试3次 + if tokenValid { + break + } + // 刷新token + accessToken = GetAccessToken() + } + + res.Header().Set("Content-type", "application/json") + _, _ = res.Write([]byte(postStatus)) +} + +// SendPicFile send pic +func SendPicFile(toUser string, filename string) { + var res http.ResponseWriter + // 获取token + accessToken := GetAccessToken() + // 默认token有效 + tokenValid := true + + msgType := "image" + // 默认mediaId为空 + mediaId := "" + + // token有效则跳出循环继续执行,否则重试3次 + for i := 0; i <= 3; i++ { + var errcode float64 + mediaId, errcode = UploadMediaFile(msgType, filename, accessToken) + log.Printf("企业微信上传临时素材接口返回的media_id==>[%s], errcode==>[%f]\n", mediaId, errcode) + tokenValid = ValidateToken(errcode) + if tokenValid { + break + } + + accessToken = GetAccessToken() + } + + // 准备发送应用消息所需参数 + postData := InitJsonData("image", toUser) + postData.Image = Pic{ + MediaId: mediaId, + } + + postStatus := "" + for i := 0; i <= 3; i++ { + sendMessageUrl := fmt.Sprintf(SendMessageApi, accessToken) + postStatus = PostMsg(postData, sendMessageUrl) + postResponse := utils.ParseJson(postStatus) + errcode := postResponse["errcode"] + log.Println("发送应用消息接口返回errcode==>", errcode) + tokenValid = ValidateToken(errcode) + // token有效则跳出循环继续执行,否则重试3次 + if tokenValid { + break + } + // 刷新token + accessToken = GetAccessToken() + } + + res.Header().Set("Content-type", "application/json") + _, _ = res.Write([]byte(postStatus)) +} + +// wechat send pic +func SendPic(toUser string, pic *multipart.FileHeader) { + var res http.ResponseWriter + // 获取token + accessToken := GetAccessToken() + // 默认token有效 + tokenValid := true + + msgType := "image" + // 默认mediaId为空 + mediaId := "" + + // token有效则跳出循环继续执行,否则重试3次 + for i := 0; i <= 3; i++ { + var errcode float64 + mediaId, errcode = UploadMedia(msgType, pic, accessToken) + log.Printf("企业微信上传临时素材接口返回的media_id==>[%s], errcode==>[%f]\n", mediaId, errcode) + tokenValid = ValidateToken(errcode) + if tokenValid { + break + } + + accessToken = GetAccessToken() + } + + // 准备发送应用消息所需参数 + postData := InitJsonData("image", toUser) + postData.Image = Pic{ + MediaId: mediaId, + } + + postStatus := "" + for i := 0; i <= 3; i++ { + sendMessageUrl := fmt.Sprintf(SendMessageApi, accessToken) + postStatus = PostMsg(postData, sendMessageUrl) + postResponse := utils.ParseJson(postStatus) + errcode := postResponse["errcode"] + log.Println("发送应用消息接口返回errcode==>", errcode) + tokenValid = ValidateToken(errcode) + // token有效则跳出循环继续执行,否则重试3次 + if tokenValid { + break + } + // 刷新token + accessToken = GetAccessToken() + } + + res.Header().Set("Content-type", "application/json") + _, _ = res.Write([]byte(postStatus)) +} diff --git a/third_part/qingyunke.go b/third_part/qingyunke.go new file mode 100644 index 0000000..14b883f --- /dev/null +++ b/third_part/qingyunke.go @@ -0,0 +1,26 @@ +package third_part + +import ( + "WechatGateWay/utils" + "encoding/json" + "log" + "strings" +) + +type qingyunkeRes struct { + Result int `json:"result"` + Content string `json:"content"` +} + +func AiChat(msg string) string { + log.Println("分析关键词:", msg) + url := "http://api.qingyunke.com/api.php?key=free&appid=0&msg=" + msg + ansByte := utils.HTTPGet(url) + if ansByte == nil { + return "没听懂" + } + var ansRes qingyunkeRes + json.Unmarshal(ansByte, &ansRes) + //{br}替换成换行 + return strings.Replace(ansRes.Content, "{br}", "\n", -1) +} diff --git a/third_part/qingyunke_test.go b/third_part/qingyunke_test.go new file mode 100644 index 0000000..c2c7c41 --- /dev/null +++ b/third_part/qingyunke_test.go @@ -0,0 +1,9 @@ +package third_part + +import ( + "testing" +) + +func TestAiChat(t *testing.T) { + t.Log(AiChat("你好")) +} diff --git a/utils/cmd.go b/utils/cmd.go new file mode 100644 index 0000000..74a809a --- /dev/null +++ b/utils/cmd.go @@ -0,0 +1,31 @@ +package utils + +import ( + "fmt" + "log" + "os/exec" +) + +func CMDBool(name string, arg []string) bool { + cmdExec := exec.Command(name, arg...) + log.Println(cmdExec.String()) + out, err := cmdExec.CombinedOutput() + if err != nil { + log.Println(string(out)) + log.Printf("cmd.Run() failed with %s", err) + return false + } + fmt.Printf("combined out:%s", string(out)) + return true +} + +func CMDString(name string, arg []string) (string, error) { + cmdExec := exec.Command(name, arg...) + out, err := cmdExec.CombinedOutput() + return string(out), err +} + +func CMDShellTrick(cmd string) (string, error) { + out, err := exec.Command("sh", "-c", cmd).Output() + return string(out), err +} diff --git a/utils/cmd_test.go b/utils/cmd_test.go new file mode 100644 index 0000000..cf1915c --- /dev/null +++ b/utils/cmd_test.go @@ -0,0 +1,10 @@ +package utils + +import "testing" + +func TestCMDString(t *testing.T) { + t.Log(CMDString("ping", []string{"www.baidu.com"})) +} +func TestCMDTrick(t *testing.T) { + t.Log(CMDShellTrick("ping www.baidu.com")) +} diff --git a/utils/http.go b/utils/http.go new file mode 100644 index 0000000..6f582e7 --- /dev/null +++ b/utils/http.go @@ -0,0 +1,33 @@ +package utils + +import ( + "fmt" + "io" + "net/http" +) + +func HTTPGet(url string) []byte { + method := "GET" + + client := &http.Client{} + req, err := http.NewRequest(method, url, nil) + + if err != nil { + fmt.Println(err) + return nil + } + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return nil + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return nil + } + return body + +} diff --git a/utils/http_test.go b/utils/http_test.go new file mode 100644 index 0000000..66a1c2e --- /dev/null +++ b/utils/http_test.go @@ -0,0 +1,10 @@ +package utils + +import ( + "fmt" + "testing" +) + +func TestHTTPGet(t *testing.T) { + fmt.Println(HTTPGet("http://www.baidu.com")) +} diff --git a/utils/json.go b/utils/json.go new file mode 100644 index 0000000..fd8946d --- /dev/null +++ b/utils/json.go @@ -0,0 +1,18 @@ +package utils + +import ( + "encoding/json" + "log" +) + +// ParseJson 将json字符串解析为map +func ParseJson(jsonStr string) map[string]any { + var wecomResponse map[string]any + if string(jsonStr) != "" { + err := json.Unmarshal([]byte(string(jsonStr)), &wecomResponse) + if err != nil { + log.Println("生成json字符串错误") + } + } + return wecomResponse +} diff --git a/utils/mapRand.go b/utils/mapRand.go new file mode 100644 index 0000000..5c23bb4 --- /dev/null +++ b/utils/mapRand.go @@ -0,0 +1,21 @@ +package utils + +import "math/rand" + +func RandMapKey(m map[string]string) string { + mapKeys := make([]string, 0, len(m)) // pre-allocate exact size + for key := range m { + mapKeys = append(mapKeys, key) + } + return mapKeys[rand.Intn(len(mapKeys))] +} + +// MapKey 知道值 拿第一个key +func MapKey(m map[string]string, value string) (string, bool) { + for k, v := range m { + if v == value { + return k, true + } + } + return "", false +} diff --git a/utils/mapRand_test.go b/utils/mapRand_test.go new file mode 100644 index 0000000..ab5edaa --- /dev/null +++ b/utils/mapRand_test.go @@ -0,0 +1,12 @@ +package utils + +import ( + "WechatGateWay/global" + "fmt" + "testing" +) + +func TestRandMap(tt *testing.T) { + fmt.Println(RandMapKey(global.EmojMap)) + +} diff --git a/utils/sliceContain.go b/utils/sliceContain.go new file mode 100644 index 0000000..44eba5a --- /dev/null +++ b/utils/sliceContain.go @@ -0,0 +1,22 @@ +package utils + +import ( + "strings" +) + +func SliceContain(cmds []string, cmd string) bool { + for _, v := range cmds { + if strings.Contains(cmd, v) { + return true + } + } + return false +} + +func SlicePrint(cmds []string) string { + res := "" + for _, v := range cmds { + res = res + v + "\n" + } + return res +} diff --git a/utils/sliceContain_test.go b/utils/sliceContain_test.go new file mode 100644 index 0000000..234fd01 --- /dev/null +++ b/utils/sliceContain_test.go @@ -0,0 +1,18 @@ +package utils + +import ( + "WechatGateWay/global" + "fmt" + "testing" +) + +func TestSLiceContain(t *testing.T) { + fmt.Println(SliceContain(global.CMDList, "history")) + fmt.Println(SliceContain(global.CMDList, "history1")) + fmt.Println(SliceContain(global.CMDList, "kubectl ")) + fmt.Println(SliceContain(global.CMDList, "kubectlssss ")) + fmt.Println(SliceContain(global.CMDList, "kubectl get ns ")) +} +func TestSlicePrint(t *testing.T) { + fmt.Println(SlicePrint(global.CMDList)) +}