diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91feec3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Created by .ignore support plugin (hsz.mobi) +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# IDE +.idea \ No newline at end of file diff --git a/entry.go b/entry.go new file mode 100644 index 0000000..e5c3d38 --- /dev/null +++ b/entry.go @@ -0,0 +1,133 @@ +package wxwork_message_sdk + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "log" + "net/http" + "strconv" + "strings" +) + +import ( + "github.com/julienschmidt/httprouter" + "github.com/sbzhu/weworkapi_golang/wxbizmsgcrypt" +) + +type ErrorInfo struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +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"` +} + +type Wx struct { + WxCrypt *wxbizmsgcrypt.WXBizMsgCrypt + Path string + Port string + RegistryHandle map[string]func(textContent string) (content string, err error) + Delimiters []string +} + +func verifyController(wxCrypt *wxbizmsgcrypt.WXBizMsgCrypt) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + params := r.URL.Query() + msgSignature := params.Get("msg_signature") + timestamp := params.Get("timestamp") + nonce := params.Get("nonce") + echostr := params.Get("echostr") + + // 进行解密 + sEchoStr, cryptErr := wxCrypt.VerifyURL(msgSignature, timestamp, nonce, echostr) + if nil != cryptErr { + resultBytes, _ := json.Marshal(ErrorInfo{ + Code: cryptErr.ErrCode, + Msg: cryptErr.ErrMsg, + }) + _, _ = fmt.Fprintf(w, string(resultBytes)) + return + } + + _, _ = fmt.Fprintf(w, string(sEchoStr)) + } +} + +func receiveController(wx *Wx) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + params := r.URL.Query() + msgSignature := params.Get("msg_signature") + timestamp := params.Get("timestamp") + nonce := params.Get("nonce") + xmlData, _ := ioutil.ReadAll(r.Body) + // 进行解密 + msg, cryptErr := wx.WxCrypt.DecryptMsg(msgSignature, timestamp, nonce, xmlData) + if nil != cryptErr { + return + } + + var msgContent MsgContent + err := xml.Unmarshal(msg, &msgContent) + if nil != err { + return + } + content := strings.ToLower(msgContent.Content) + result := "没有匹配到相应结果" + + prefix, content := formatContext(content, wx.Delimiters) + + fmt.Println(prefix) + fmt.Println(content) + + switch { + case nil != wx.RegistryHandle[prefix]: + result, err = wx.RegistryHandle[prefix](content) + if nil != err { + result = err.Error() + } + } + + responseData := "" + strconv.FormatUint(uint64(msgContent.CreateTime), 10) + "" + msgContent.Msgid + "" + strconv.FormatUint(uint64(msgContent.Agentid), 10) + "" + + sEncryptMsg, cryptErr := wx.WxCrypt.EncryptMsg(responseData, timestamp, nonce) + if nil != cryptErr { + return + } + + _, _ = fmt.Fprint(w, string(sEncryptMsg)) + } +} + +func Create(token string, corpId string, encodingAesKey string) func(path string, port string, delimiters []string) *Wx { + return func(path string, port string, delimiters []string) *Wx { + wxCrypt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, corpId, wxbizmsgcrypt.XmlType) + return &Wx{ + RegistryHandle: make(map[string]func(textContent string) (content string, err error)), + WxCrypt: wxCrypt, + Path: path, + Port: port, + Delimiters: delimiters, + } + } +} + +func (w *Wx) Run() { + router := httprouter.New() + router.GET(w.Path, verifyController(w.WxCrypt)) + router.POST(w.Path, receiveController(w)) + fmt.Println("Server Started!") + log.Fatal(http.ListenAndServe(w.Port, router)) +} + +func (w *Wx) Registry(handel func(textContent string) (content string, err error), levels ...string) { + w.RegistryHandle[strings.ToLower(strings.Join(levels, " "))] = handel +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..9a6711c --- /dev/null +++ b/utils.go @@ -0,0 +1,85 @@ +package wxwork_message_sdk + +import ( + "strings" +) + +// 正确获取中文下标,从0开始 +func unicodeIndex(str, substr string) int { + result := strings.Index(str, substr) + if result >= 0 { + prefix := []byte(str)[0:result] + rs := []rune(string(prefix)) + result = len(rs) + } + + return result +} + +// 获取当前句子的标记符下标(针对多个标记符的情况) +func getDelimiterIndexForMultiple(content string, delimiters []string) int { + index := len([]rune(content)) + + for _, delimiter := range delimiters { + currentDelimiterIndex := unicodeIndex(content, delimiter) + 1 + + // 开始和最后的标识符不理会 + // 如果没有标记符也不理会 + if currentDelimiterIndex > 1 && currentDelimiterIndex < index && index != 0 { + index = currentDelimiterIndex + } + } + + if index == len([]rune(content)) { + return 0 + } + + return index +} + +// 获取当前句子的标记符下标(针对单个标记符的情况) +func getDelimiterIndex(content string, delimiter string) int { + index := unicodeIndex(content, delimiter) + 1 + if index > 1 { + return index + } + return index +} + +// 格式化句子 +func formatContext(content string, delimiters []string) (string, string) { + index := 0 + + if len(delimiters) == 1 { + index = getDelimiterIndex(content, delimiters[0]) + } else { + index = getDelimiterIndexForMultiple(content, delimiters) + } + + if index == 0 { + return content, content + } + + // 当前句子的标识符 + delimiter := string([]rune(content)[index-1 : index]) + + // 后面是否还有 + behind := strings.Index(string([]rune(content)[:index]), delimiter) + + if behind == -1 { + return strings.ToLower(string([]rune(content)[:index-1])), content + } + + prefix := string([]rune(content)[:index - 1]) + content = string([]rune(content)[index:]) + + nextPrefix, nextContent := formatContext(content, []string{delimiter}) + + content = nextContent + if nextPrefix != nextContent { + prefix = prefix + " " + nextPrefix + content = nextContent + } + + return strings.Trim(prefix, " "), strings.Trim(content, " ") +}