| @ -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 | |||
| } | |||
| @ -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) | |||
| } | |||
| @ -0,0 +1,5 @@ | |||
| module git.tetele.net/tgo/wechat | |||
| go 1.16 | |||
| require github.com/json-iterator/go v1.1.12 | |||
| @ -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= | |||
| @ -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 | |||
| } | |||
| @ -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 | |||
| } | |||
| @ -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 | |||
| } | |||
| @ -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 | |||
| } | |||
| @ -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] | |||
| } | |||
| @ -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" | |||