diff --git a/weixin/app_sdk.go b/weixin/app_sdk.go new file mode 100644 index 0000000..bffc823 --- /dev/null +++ b/weixin/app_sdk.go @@ -0,0 +1,18 @@ +package weixin + +type AppSdk struct { + BaseSdk +} + +func NewAppSdk(appid, secret string) *AppSdk { + return &AppSdk{ + BaseSdk{ + appid: appid, + secret: secret, + }, + } +} + +func (o *AppSdk) GetUserInfoByCode(code string) (*UserInfo, error) { + return o.getUserInfoByCode(code) +} diff --git a/weixin/base.go b/weixin/base.go new file mode 100644 index 0000000..a57565b --- /dev/null +++ b/weixin/base.go @@ -0,0 +1,133 @@ +package weixin + +import ( + "fmt" + "github.com/tidwall/gjson" + "io" + "net/http" + "sync" + "time" +) + +const ( + code2AccessTokenUrl string = "https://api.weixin.qq.com/sns/oauth2/access_token" + accessToken2UserInfoUrl string = "https://api.weixin.qq.com/sns/userinfo" + jscode2SessionUrl string = "https://api.weixin.qq.com/sns/jscode2session" + accessTokenUrl string = "https://api.weixin.qq.com/cgi-bin/token" + userPhoneNumberUrl string = "https://api.weixin.qq.com/wxa/business/getuserphonenumber" + getWxACodeUnLimitUrl string = "https://api.weixin.qq.com/wxa/getwxacodeunlimit" + code2UserinfoUrl string = "https://api.weixin.qq.com/sns/userinfo" + oaQrCodeCreateUrl string = "https://api.weixin.qq.com/cgi-bin/qrcode/create" + userInfoByOpenid string = "https://api.weixin.qq.com/cgi-bin/user/info" +) + +type UserInfo struct { + Openid string + Unionid string + AccessToken string + Nickname string + HeadUrl string + Sex int //1:为男性 2位女性 + Country string + City string + Province string +} + +type BaseSdk struct { + appid string + secret string + accessToken string + expireIn int64 + lock sync.Mutex +} + +func (o *BaseSdk) getAccessToken() (string, error) { + o.lock.Lock() + defer o.lock.Unlock() + if time.Now().Unix() < o.expireIn { + return o.accessToken, nil + } + + // 获取一个新token + url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", accessTokenUrl, o.appid, o.secret) + res, err := http.Get(url) + if err != nil { + return "", err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + g := gjson.ParseBytes(body) + + accessToken := g.Get("access_token").String() + if accessToken == "" { + return "", fmt.Errorf("%d:%s", g.Get("errcode").Int(), g.Get("errmsg").String()) + } + o.accessToken = accessToken + o.expireIn = time.Now().Unix() + g.Get("expires_in").Int() - 10 + return o.accessToken, nil +} + +func (o *BaseSdk) getUserInfoByCode(code string) (*UserInfo, error) { + url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code", + code2AccessTokenUrl, o.appid, o.secret, code) + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + g := gjson.ParseBytes(body) + + errcode := g.Get("errcode").Int() + if errcode != 0 { + return nil, fmt.Errorf("%d:%s", errcode, g.Get("errmsg").String()) + } + + var user UserInfo + user.Unionid = g.Get("unionid").String() + user.AccessToken = g.Get("access_token").String() + user.Openid = g.Get("openid").String() + + err = o.getUserInfo(&user) + if err != nil { + return nil, err + } + + return &user, nil +} + +func (o *BaseSdk) getUserInfo(user *UserInfo) error { + url := fmt.Sprintf("%s?access_token=%s&openid=%s&lang=zh_CN", + code2UserinfoUrl, user.AccessToken, user.Openid) + res, err := http.Get(url) + if err != nil { + return err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + g := gjson.ParseBytes(body) + errcode := g.Get("errcode").Int() + if errcode != 0 { + return fmt.Errorf("%d:%s", errcode, g.Get("errmsg").String()) + } + + user.Unionid = g.Get("unionid").String() + user.Nickname = g.Get("nickname").String() + user.HeadUrl = g.Get("headimgurl").String() + user.Province = g.Get("province").String() + user.City = g.Get("city").String() + user.Country = g.Get("country").String() + user.Sex = int(g.Get("sex").Int()) + return nil +} diff --git a/weixin/mp_sdk.go b/weixin/mp_sdk.go new file mode 100644 index 0000000..962d07e --- /dev/null +++ b/weixin/mp_sdk.go @@ -0,0 +1,110 @@ +package weixin + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/spf13/cast" + "github.com/tidwall/gjson" + "io" + "net/http" +) + +type MpSdk struct { + BaseSdk +} + +func NewMpSdk(appid, secret string) *MpSdk { + return &MpSdk{ + BaseSdk{ + appid: appid, + secret: secret, + }, + } +} + +func (o *MpSdk) GetUserInfoByJsCode(code string) (*UserInfo, error) { + url := fmt.Sprintf("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", + jscode2SessionUrl, o.appid, o.secret, code) + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + userInfo := new(UserInfo) + + g := gjson.ParseBytes(body) + userInfo.Openid = g.Get("openid").String() + userInfo.Unionid = g.Get("unionid").String() + return userInfo, nil +} + +func (o *MpSdk) GetPhone(code string) (string, error) { + accessToken, err := o.getAccessToken() + if err != nil { + return "", err + } + reqUrl := fmt.Sprintf("%s?access_token=%s", userPhoneNumberUrl, accessToken) + res, err := http.Post(reqUrl, "application/json", bytes.NewBuffer([]byte(fmt.Sprintf(`{"code":"%s"}`, code)))) + if err != nil { + return "", err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + + g := gjson.ParseBytes(body) + errcode := g.Get("errcode").Int() + if errcode != 0 { + return "", fmt.Errorf("%d:%s", errcode, g.Get("errmsg")) + } + + return g.Get("phone_info.phoneNumber").String(), nil +} + +func (o *MpSdk) GetUnlimitedQRCode(params map[string]interface{}) ([]byte, error) { + if _, ok := params["scene"]; !ok { + return nil, errors.New("scene参数缺失") + } + + if _, ok := params["width"]; !ok { + params["width"] = 280 + } + if _, ok := params["env_version"]; !ok { + params["env_version"] = "release" + } + if _, ok := params["check_path"]; !ok { + params["check_path"] = false + } + if _, ok := params["page"]; !ok { + params["page"] = "pages/index/index" + } + + marshal, _ := json.Marshal(params) + accessToken, err := o.getAccessToken() + if err != nil { + return nil, err + } + url := fmt.Sprintf("%s?access_token=%s", getWxACodeUnLimitUrl, accessToken) + res, _ := http.Post(url, "application/json", bytes.NewBuffer(marshal)) + body, err := io.ReadAll(res.Body) + defer res.Body.Close() + + if err != nil { + return nil, err + } + mp := make(map[string]interface{}) + err = json.Unmarshal(body, &mp) + if err == nil { + return nil, fmt.Errorf("%d:%s", cast.ToInt64(mp["errcode"]), cast.ToString(mp["errmsg"])) + } + + return body, nil +} diff --git a/weixin/oa_sdk.go b/weixin/oa_sdk.go new file mode 100644 index 0000000..3017549 --- /dev/null +++ b/weixin/oa_sdk.go @@ -0,0 +1,92 @@ +package weixin + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/tidwall/gjson" + "io" + "net/http" +) + +type OaSdk struct { + BaseSdk +} + +func NewOaSdk(appid, secret string) *OaSdk { + return &OaSdk{ + BaseSdk{ + appid: appid, + secret: secret, + }, + } +} + +func (o *OaSdk) GetQrCode(sceneStr string) (string, error) { + params := make(map[string]interface{}) + params["expire_seconds"] = 1728000 //20天 + params["action_name"] = "QR_STR_SCENE" + params["action_info"] = map[string]interface{}{ + "scene": map[string]interface{}{ + "scene_str": sceneStr, // sceneStr不超过64个字符 + }, + } + + marshal, _ := json.Marshal(params) + accessToken, err := o.getAccessToken() + if err != nil { + return "", err + } + + url := fmt.Sprintf("?access_token=%s", oaQrCodeCreateUrl, accessToken) + res, _ := http.Post(url, "application/json", bytes.NewBuffer(marshal)) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + g := gjson.ParseBytes(body) + + qrcodeUrl := g.Get("url").String() + if qrcodeUrl == "" { + return "", fmt.Errorf("%d:%s", g.Get("errcode").Int(), g.Get("errmsg").String()) + } + return qrcodeUrl, nil +} + +func (o *BaseSdk) GetUserInfoByCode(code string) (*UserInfo, error) { + return o.getUserInfoByCode(code) +} + +func (o *OaSdk) GetUserInfoByOpenid(openid string) (*UserInfo, error) { + accessToken, err := o.getAccessToken() + if err != nil { + return nil, err + } + url := fmt.Sprintf("%s?access_token=%s&openid=%s&lang=zh_CN", + userInfoByOpenid, accessToken, openid) + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + g := gjson.ParseBytes(body) + errcode := g.Get("errcode").Int() + if errcode != 0 { + return nil, fmt.Errorf("%d:%s", errcode, g.Get("errmsg").String()) + } + user := new(UserInfo) + user.Unionid = g.Get("unionid").String() + user.Nickname = g.Get("nickname").String() + user.Province = g.Get("province").String() + user.City = g.Get("city").String() + user.Country = g.Get("country").String() + user.HeadUrl = g.Get("headimgurl").String() + user.Sex = int(g.Get("sex").Int()) + user.Openid = openid + return user, nil +}