diff --git a/access_token.go b/access_token.go new file mode 100644 index 0000000..66b0a75 --- /dev/null +++ b/access_token.go @@ -0,0 +1,39 @@ +package wechat + +import ( + "fmt" + "log" +) + +/** + * 从微信api取access_token + */ +func GetAccessToken(appid, secret string) (wx_access_token_res, error) { + + url := fmt.Sprintf(ACCESS_TOKEN_API, appid, secret) + + data_byte, err := SendHttp("GET", url, nil) + + type wx_access_token_res struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + } + + var data wx_access_token_res + + if err == nil { + err = json.Unmarshal(data_byte, &data) + + if err != nil { + log.Println("get access token from WX api error:", err) + } + + if data.Errcode != 0 { + log.Println("get access token from WX api fail:", data.Errcode, data.Errmsg) + } + } + + return data, err +} diff --git a/access_token_test.go b/access_token_test.go new file mode 100644 index 0000000..9db040f --- /dev/null +++ b/access_token_test.go @@ -0,0 +1,13 @@ +package wechat + +import ( + "testing" +) + +func Test_GetAccessToken(t *testing.T) { + appid := "wxe2a6548a7ff2e558" + appsecret := "51aa4769025bd2c203b0e811b7063e1b" + ret, err := GetAccessToken(appid, appsecret) + t.Log(ret) + t.Log(err) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5b88a90 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.tetele.net/tgo/wechat + +go 1.16 + +require github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5ad85e0 --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/http.go b/http.go new file mode 100644 index 0000000..6366998 --- /dev/null +++ b/http.go @@ -0,0 +1,127 @@ +package wechat + +import ( + "bytes" + "encoding/xml" + "io/ioutil" + "net/http" + "strings" +) + +/** + * 自定义HTTP请求 + */ +func SendHttp(method, url string, param map[string]string, header ...map[string]string) ([]byte, error) { + httpClient := &http.Client{} + + paramStr := "" + if len(param) > 0 { + for key, value := range param { + paramStr += key + "=" + value + "&" + } + + paramStr = paramStr[0 : len(paramStr)-1] + } + + req, err := http.NewRequest(method, url, strings.NewReader(paramStr)) + if err != nil { + return []byte(""), err + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + if len(header) > 0 { + for _, item := range header { + for k, v := range item { + req.Header.Set(k, v) + } + } + } + + resp, err := httpClient.Do(req) + if err != nil { + return []byte(""), err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return []byte(""), err + } + + return body, nil +} + +/** + * send xml + */ +func SendXml(method, url string, requestxml interface{}, header ...map[string]string) ([]byte, error) { + + bytexml, err := xml.Marshal(&requestxml) + if err != nil { + return []byte(""), err + } + + httpClient := &http.Client{} + + req, err := http.NewRequest(method, url, bytes.NewBuffer(bytexml)) + if err != nil { + return []byte(""), err + } + + req.Header.Add("Content-Type", "application/xml; charset=utf-8") + + if len(header) > 0 { + for _, item := range header { + for k, v := range item { + req.Header.Add(k, v) + } + } + } + resp, err := httpClient.Do(req) + if err != nil { + return []byte(""), err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return []byte(""), err + } + + return body, nil +} + +/** + * post 请求 + */ +func PostJson(url string, param []byte, header ...map[string]string) ([]byte, error) { + httpClient := &http.Client{} + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(param)) + if err != nil { + return []byte(""), err + } + + req.Header.Set("Content-Type", "application/json") + + if len(header) > 0 { + for _, item := range header { + for k, v := range item { + req.Header[k] = []string{v} + } + } + } + resp, err := httpClient.Do(req) + if err != nil { + return []byte(""), err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return []byte(""), err + } + + return body, nil +} diff --git a/message.go b/message.go new file mode 100644 index 0000000..c41fd3d --- /dev/null +++ b/message.go @@ -0,0 +1,54 @@ +package wechat + +import ( + "fmt" + "log" +) + +//发送订阅消息返回结果 +type SendSubscribeMessageData struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +//发送统一消息返回结果 +type SendUniformMessageData struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +func SendSubscribeMessage(access_token string, message []byte) (SendSubscribeMessageData, error) { + + url := fmt.Sprintf(MINIAPP_SUBSCRIBE_MESSAGE_SEND_API, access_token) + + ret, err := PostJson(url, message) + + var data SendSubscribeMessageData + + if err != nil { + + return data, err + } + + err = json.Unmarshal(ret, &data) + + return data, err +} + +func SendUniformMessage(access_token string, message []byte) (SendUniformMessageData, error) { + + url := fmt.Sprintf(MINIAPP_UNIFORM_MESSAGE_API, access_token) + + ret, err := PostJson(url, message) + + var data SendUniformMessageData + + if err != nil { + + return data, err + } + + err = json.Unmarshal(ret, &data) + + return data, err +} diff --git a/openid.go b/openid.go new file mode 100644 index 0000000..46579a4 --- /dev/null +++ b/openid.go @@ -0,0 +1,63 @@ +package wechat + +import ( + "fmt" +) + +type AppOpenIdData struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Openid string `json:"openid"` + Scope string `json:"scope"` + Unionid interface{} `json:"unionid"` + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +type MiniAppOpenidData struct { + Openid string `json:"openid"` + SessionKey interface{} `json:"session_key"` + Unionid interface{} `json:"unionid"` + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +/** + * 从微信api取openid + */ +func GetAppOpenid(appid, secret, code string) (AppOpenIdData, error) { + + var data AppOpenIdData + + url := fmt.Sprintf(GET_APP_OPENID, appid, secret, code) + + data_byte, err := SendHttp("GET", url, nil) + if err != nil { + return data, err + } + + err = json.Unmarshal(data_byte, &data) + + return data, err +} + +/** + * 从微信api取openid + */ +func GetMiniAppOpenid(appid, secret, code string) (MiniAppOpenidData, error) { + + url := fmt.Sprintf(GET_MINIAPP_OPENID_API, appid, secret, code) + + data_byte, err := SendHttp("GET", url, nil) + + var data MiniAppOpenidData + + if err != nil { + return data, err + } + + err = json.Unmarshal(data_byte, &data) + + return data, err +} diff --git a/qrcode.go b/qrcode.go new file mode 100644 index 0000000..1cf1819 --- /dev/null +++ b/qrcode.go @@ -0,0 +1,77 @@ +package wechat + +import ( + "encoding/base64" + "errors" + "fmt" + "log" + + "git.tetele.net/tgo/helper" + "git.tetele.net/tgo/network" +) + +// 获取小程序码 +func GetMiniappQrcode(access_token string, qrcodeParamsMap map[string]interface{}) (string, error) { + + getCodeUrl := fmt.Sprintf(GET_MINIAPP_QRCODE, access_token) + + checkPath := true + envVersion := "release" + width := 430 + autoColor := false + isHyaline := false + + if _, exist := qrcodeParamsMap["check_path"]; exist { + checkPath = qrcodeParamsMap["check_path"].(bool) + } + + if _, exist := qrcodeParamsMap["env_version"]; exist { + envVersion = helper.ToStr(qrcodeParamsMap["env_version"]) + } + + if _, exist := qrcodeParamsMap["width"]; exist { + width = helper.ToInt(qrcodeParamsMap["width"]) + } + + if _, exist := qrcodeParamsMap["auto_color"]; exist { + autoColor = qrcodeParamsMap["auto_color"].(bool) + } + + if _, exist := qrcodeParamsMap["is_hyaline"]; exist { + isHyaline = qrcodeParamsMap["is_hyaline"].(bool) + } + + requestData := map[string]interface{}{ + "scene": qrcodeParamsMap["scene"], + "page": qrcodeParamsMap["page"], + "check_path": checkPath, + "env_version": envVersion, + "width": width, + "auto_color": autoColor, + "is_hyaline": isHyaline, + } + requestDataJson, err := json.Marshal(requestData) + + if err != nil { + return "", err + } + + response, err := PostJson(getCodeUrl, requestDataJson) + + if err != nil { + return "", err + } + + responseData := map[string]interface{}{} + err = json.Unmarshal(response, &responseData) + if err != nil { + // 有错,但能解析 + log.Println(err) + } + + if _, exist := responseData["errcode"]; exist && helper.ToInt(responseData["errcode"]) != 0 { + return "", errors.New(helper.ToStr(responseData["errmsg"])) + } + + return "data:image/png;base64," + base64.StdEncoding.EncodeToString(response), nil +} diff --git a/security_check.go b/security_check.go new file mode 100644 index 0000000..f0fffe1 --- /dev/null +++ b/security_check.go @@ -0,0 +1,159 @@ +package wechat + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + + "git.tetele.net/tgo/helper" +) + +//检测图片内容是否合规 +func ImgSecCheck(access_token, imgUrl string) error { + var err error + checkUrl := "https://api.weixin.qq.com/wxa/img_sec_check?access_token=%s" + + checkUrl = fmt.Sprintf(checkUrl, access_token) + + imgResponse, err := http.Get(imgUrl) //获取图片 + + if err != nil { + return err + } + + defer imgResponse.Body.Close() + + if imgResponse.StatusCode != 200 { + return errors.New("resp status:" + fmt.Sprint(imgResponse.StatusCode)) + } + + buf := new(bytes.Buffer) + w := multipart.NewWriter(buf) + + bin, err := ioutil.ReadAll(imgResponse.Body) + if err != nil { + fmt.Println(err) + return err + } + + fw, err := w.CreateFormFile("media", "fijgrpgpegjrepoikr") + if err != nil { + fmt.Println(err) + return err + } + + _, err = fw.Write(bin) + + if err != nil { + fmt.Println(err) + return err + } + + w.Close() + + req, err := http.NewRequest("POST", checkUrl, buf) + + if err != nil { + fmt.Println("req err: ", err) + return err + } + req.Header.Set("Content-Type", w.FormDataContentType()) + + checkResponse, err := http.DefaultClient.Do(req) + + if err != nil { + fmt.Println("resp err: ", err) + return err + } + defer checkResponse.Body.Close() + + if checkResponse.StatusCode != 200 { + return errors.New("resp status:" + fmt.Sprint(checkResponse.StatusCode)) + } + + response, err := ioutil.ReadAll(checkResponse.Body) + + if err != nil { + return err + } + + responseData := map[string]interface{}{} + err = json.Unmarshal(response, &responseData) + if err != nil { + // 有错,但能解析 + log.Println(err) + } + + if _, exist := responseData["errcode"]; exist && helper.ToInt(responseData["errcode"]) != 0 { + + return errors.New(helper.ToStr(responseData["errmsg"])) + } + + return nil +} + +//检测文本内容是否合规 +func MsgSecCheck(access_token string, data map[string]interface{}, retry ...int) error { + var err error + + data["version"] = 2 + + checkUrl := "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=%s" + + checkUrl = fmt.Sprintf(checkUrl, access_token) + + requestDataJson, err := json.Marshal(data) + + if err != nil { + return err + } + + response, err := PostJson(checkUrl, requestDataJson) + + if err != nil { + return err + } + + responseData := map[string]interface{}{} + err = json.Unmarshal(response, &responseData) + if err != nil { + // 有错,但能解析 + log.Println(err) + } + + if _, exist := responseData["errcode"]; exist && helper.ToInt(responseData["errcode"]) != 0 { + return errors.New(helper.ToStr(responseData["errmsg"])) + } + + resultData, err := helper.InterfaceToMapInterface(responseData["result"]) + + if err != nil { + return err + } + + if resultData["suggest"] != "pass" { + return errors.New("内容不能包含" + GetCheckContentByCode(helper.ToStr(resultData["label"]))) + } + + return nil +} + +func GetCheckContentByCode(code string) string { + codeList := map[string]string{ + "10001": "广告", + "20001": "时政", + "20002": "色情", + "20003": "辱骂", + "20006": "违法犯罪", + "20008": "欺诈", + "20012": "低俗", + "20013": "版权", + "21000": "其他", + } + + return codeList[code] +} diff --git a/url.go b/url.go new file mode 100644 index 0000000..fb8914f --- /dev/null +++ b/url.go @@ -0,0 +1,24 @@ +package wechat + +import ( + "github.com/json-iterator/go" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +//获取access token +const ACCESS_TOKEN_API string = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s" + +//小程序发送统一服务消息 +const MINIAPP_UNIFORM_MESSAGE_API string = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=%s" + +//小程序获取openid +const GET_MINIAPP_OPENID_API string = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code" + +//小程序发送订阅消息 +const MINIAPP_SUBSCRIBE_MESSAGE_SEND_API string = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s" + +//app获取openid +const GET_APP_OPENID string = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code" + +const GET_MINIAPP_QRCODE string = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s"