2024-01-22 22:34:59 +08:00
|
|
|
package qyweixin
|
|
|
|
|
|
|
|
import (
|
2024-01-23 12:05:34 +08:00
|
|
|
"bytes"
|
2024-01-24 18:36:07 +08:00
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/hex"
|
2024-01-22 22:34:59 +08:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
log "github.com/sirupsen/logrus"
|
2024-01-24 18:36:07 +08:00
|
|
|
"github.com/smbrave/goutil"
|
2024-01-22 22:34:59 +08:00
|
|
|
"github.com/spf13/cast"
|
|
|
|
"gitlab.batiao8.com/open/gosdk/util"
|
|
|
|
"gitlab.batiao8.com/open/gosdk/wechat"
|
|
|
|
"gitlab.batiao8.com/open/gosdk/wechat/cache"
|
|
|
|
"gitlab.batiao8.com/open/gosdk/wechat/message"
|
|
|
|
wutil "gitlab.batiao8.com/open/gosdk/wechat/util"
|
2024-01-23 12:05:34 +08:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"mime/multipart"
|
2024-01-22 22:34:59 +08:00
|
|
|
"net/http"
|
2024-01-23 12:05:34 +08:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-01-22 22:34:59 +08:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
wechatCache cache.Cache = cache.NewMemory()
|
|
|
|
)
|
2024-01-23 12:05:34 +08:00
|
|
|
var (
|
|
|
|
urlQiyeSend = "https://qyapi.weixin.qq.com/cgi-bin/message/send"
|
|
|
|
)
|
|
|
|
|
|
|
|
type MessageRequest struct {
|
|
|
|
ToUser string `json:"touser"`
|
|
|
|
ToParty string `json:"toparty"`
|
|
|
|
ToTag string `json:"totag"`
|
|
|
|
MstType string `json:"msgtype"`
|
|
|
|
AgentId string `json:"agentid"`
|
|
|
|
|
|
|
|
Text struct {
|
|
|
|
Content string `json:"content"`
|
|
|
|
} `json:"text"`
|
|
|
|
|
|
|
|
Image struct {
|
|
|
|
MediaID string `json:"media_id"`
|
|
|
|
} `json:"image"`
|
|
|
|
|
|
|
|
Video struct {
|
|
|
|
MediaID string `json:"media_id"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
Desc string `json:"description"`
|
|
|
|
} `json:"video"`
|
|
|
|
}
|
|
|
|
|
2024-01-24 18:36:07 +08:00
|
|
|
type JsapiConfig struct {
|
|
|
|
Corpid string `json:"corpid"`
|
|
|
|
Agent string `json:"agent"`
|
|
|
|
Signature string `json:"signature"`
|
|
|
|
Noncestr string `json:"noncestr"`
|
|
|
|
Timestamp string `json:"timestamp"`
|
|
|
|
}
|
|
|
|
|
2024-01-23 12:05:34 +08:00
|
|
|
type BaseResponse struct {
|
|
|
|
ErrCode int `json:"errcode"`
|
|
|
|
ErrMsg string `json:"errmsg"`
|
|
|
|
}
|
2024-01-22 22:34:59 +08:00
|
|
|
|
|
|
|
type AppConfig struct {
|
|
|
|
Corpid string
|
|
|
|
Secret string
|
|
|
|
Agent string
|
|
|
|
Token string
|
|
|
|
AesKey string
|
|
|
|
Replay func(message.MixMessage) *message.Reply
|
|
|
|
}
|
|
|
|
|
|
|
|
type App struct {
|
|
|
|
tokenExpire int64
|
|
|
|
token string
|
|
|
|
config *AppConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
type UserInfo struct {
|
|
|
|
UserId string
|
|
|
|
RealName string
|
|
|
|
}
|
|
|
|
|
2024-01-22 22:47:35 +08:00
|
|
|
func NewApp(cfg *AppConfig) *App {
|
2024-01-22 22:34:59 +08:00
|
|
|
return &App{
|
2024-01-22 22:47:35 +08:00
|
|
|
config: cfg,
|
2024-01-22 22:34:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (q *App) GetToken() string {
|
|
|
|
if time.Now().Unix() <= q.tokenExpire-600 {
|
|
|
|
return q.token
|
|
|
|
}
|
|
|
|
q.refreshToken()
|
|
|
|
return q.token
|
|
|
|
}
|
|
|
|
|
|
|
|
func (q *App) GetResult(rspBody []byte) (map[string]interface{}, error) {
|
|
|
|
result := make(map[string]interface{})
|
|
|
|
if err := json.Unmarshal(rspBody, &result); err != nil {
|
|
|
|
log.Errorf("result[%s] error :%s", string(rspBody), err.Error())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if cast.ToInt(result["errcode"]) != 0 {
|
|
|
|
log.Errorf("result[%s] error ", string(rspBody))
|
|
|
|
return nil, fmt.Errorf("%d:%s", cast.ToInt(result["errcode"]), cast.ToString(result["errmsg"]))
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2024-01-24 16:34:00 +08:00
|
|
|
func (q *App) GetOpenid(userid string) (string, error) {
|
|
|
|
if err := q.refreshToken(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
reqUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid?access_token=%s", q.GetToken())
|
|
|
|
rspBody, err := util.HttpPostJson(reqUrl, nil, []byte(fmt.Sprintf(`{"userid" : "%s"}`, userid)))
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("httpPost url[%s] error :%s", reqUrl, err.Error())
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
result, err := q.GetResult(rspBody)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return cast.ToString(result["openid"]), nil
|
|
|
|
}
|
|
|
|
|
2024-01-22 22:34:59 +08:00
|
|
|
func (q *App) GetUserInfo(userid string) (*UserInfo, error) {
|
|
|
|
if err := q.refreshToken(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
reqUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", q.GetToken(), userid)
|
|
|
|
rspBody, err := util.HttpGet(reqUrl, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("httpPost url[%s] error :%s", reqUrl, err.Error())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result, err := q.GetResult(rspBody)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
userInfo := new(UserInfo)
|
|
|
|
userInfo.UserId = userid
|
|
|
|
userInfo.RealName = cast.ToString(result["name"])
|
|
|
|
|
|
|
|
return userInfo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (q *App) GetDepartmentUserid(departmentId int) ([]string, error) {
|
|
|
|
if err := q.refreshToken(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
reqUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=%s&department_id=%d", q.GetToken(), departmentId)
|
|
|
|
rspBody, err := util.HttpGet(reqUrl, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("httpPost url[%s] error :%s", reqUrl, err.Error())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result, err := q.GetResult(rspBody)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
userids := make([]string, 0)
|
|
|
|
userlist := cast.ToSlice(result["userlist"])
|
|
|
|
for _, u := range userlist {
|
|
|
|
user := cast.ToStringMap(u)
|
|
|
|
userids = append(userids, cast.ToString(user["userid"]))
|
|
|
|
}
|
|
|
|
return userids, nil
|
|
|
|
}
|
|
|
|
|
2024-01-23 12:05:34 +08:00
|
|
|
func (a *App) SendText(receiver []string, content string) error {
|
|
|
|
reqUrl := fmt.Sprintf("%s?access_token=%s", urlQiyeSend, a.GetToken())
|
|
|
|
req := new(MessageRequest)
|
|
|
|
req.MstType = "text"
|
|
|
|
req.AgentId = a.config.Agent
|
|
|
|
req.Text.Content = content
|
|
|
|
req.ToUser = strings.Join(receiver, "|")
|
|
|
|
|
|
|
|
data, _ := json.Marshal(req)
|
|
|
|
result, err := util.HttpPostJson(reqUrl, nil, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var rsp BaseResponse
|
|
|
|
err = json.Unmarshal([]byte(result), &rsp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if rsp.ErrCode != 0 {
|
|
|
|
return fmt.Errorf("%d:%s", rsp.ErrCode, rsp.ErrMsg)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) SendImage(receiver []string, meidiaId string) error {
|
|
|
|
|
|
|
|
url := fmt.Sprintf("%s?access_token=%s", urlQiyeSend, a.GetToken())
|
|
|
|
req := new(MessageRequest)
|
|
|
|
req.MstType = "image"
|
|
|
|
req.AgentId = a.config.Agent
|
|
|
|
req.ToUser = strings.Join(receiver, "|")
|
|
|
|
req.Image.MediaID = meidiaId
|
|
|
|
|
|
|
|
data, _ := json.Marshal(req)
|
|
|
|
result, err := util.HttpPostJson(url, nil, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var rsp BaseResponse
|
|
|
|
err = json.Unmarshal([]byte(result), &rsp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if rsp.ErrCode != 0 {
|
|
|
|
return fmt.Errorf("%d:%s", rsp.ErrCode, rsp.ErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) SendVideo(receiver []string, meidiaId, title, desc string) error {
|
|
|
|
|
|
|
|
url := fmt.Sprintf("%s?access_token=%s", urlQiyeSend, a.GetToken())
|
|
|
|
req := new(MessageRequest)
|
|
|
|
req.MstType = "video"
|
|
|
|
req.AgentId = a.config.Agent
|
|
|
|
req.ToUser = strings.Join(receiver, "|")
|
|
|
|
req.Video.MediaID = meidiaId
|
|
|
|
req.Video.Title = title
|
|
|
|
req.Video.Desc = desc
|
|
|
|
data, _ := json.Marshal(req)
|
|
|
|
result, err := util.HttpPostJson(url, nil, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var rsp BaseResponse
|
|
|
|
err = json.Unmarshal([]byte(result), &rsp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if rsp.ErrCode != 0 {
|
|
|
|
return fmt.Errorf("%d:%s", rsp.ErrCode, rsp.ErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *App) Upload(path, kind string) (string, error) {
|
|
|
|
|
|
|
|
fh, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer fh.Close()
|
|
|
|
bodyBuf := &bytes.Buffer{}
|
|
|
|
bodyWriter := multipart.NewWriter(bodyBuf)
|
|
|
|
|
|
|
|
fileWriter, err := bodyWriter.CreateFormFile(kind, filepath.Base(path))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(fileWriter, fh)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
bodyWriter.Close()
|
|
|
|
|
|
|
|
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", a.GetToken(), kind)
|
|
|
|
req, err := http.NewRequest("POST", url, bodyBuf)
|
|
|
|
req.Header.Add("Content-Type", bodyWriter.FormDataContentType())
|
|
|
|
urlQuery := req.URL.Query()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.URL.RawQuery = urlQuery.Encode()
|
|
|
|
client := http.Client{}
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
jsonbody, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
result := make(map[string]interface{})
|
|
|
|
if err := json.Unmarshal(jsonbody, &result); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if cast.ToInt(result["errcode"]) != 0 {
|
|
|
|
return "", fmt.Errorf("%d:%s", cast.ToInt(result["errcode"]), cast.ToString(result["errmsg"]))
|
|
|
|
}
|
|
|
|
return cast.ToString(result["media_id"]), nil
|
|
|
|
}
|
|
|
|
|
2024-01-24 18:36:07 +08:00
|
|
|
func (q *App) GetJsapiConfig(url string) (*JsapiConfig, error) {
|
|
|
|
ticket, err := q.getJsapiTicket()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cfg := new(JsapiConfig)
|
|
|
|
noncestr := goutil.RandomStr(32)
|
|
|
|
timstamp := cast.ToString(time.Now().Unix())
|
|
|
|
str := "jsapi_ticket=" + cast.ToString(ticket) + "&noncestr=" + noncestr + "×tamp=" + timstamp + "&url=" + url
|
|
|
|
h := sha1.New()
|
|
|
|
h.Write([]byte(str))
|
|
|
|
signature := hex.EncodeToString(h.Sum(nil))
|
|
|
|
cfg.Signature = signature
|
|
|
|
cfg.Timestamp = timstamp
|
|
|
|
cfg.Noncestr = noncestr
|
|
|
|
cfg.Agent = q.config.Agent
|
|
|
|
cfg.Corpid = q.config.Corpid
|
|
|
|
return cfg, nil
|
|
|
|
}
|
|
|
|
|
2024-01-22 22:34:59 +08:00
|
|
|
func (q *App) Callback(ctx *gin.Context) {
|
|
|
|
|
|
|
|
//配置微信参数
|
|
|
|
wechatConfig := &wechat.Config{
|
|
|
|
AppID: q.config.Corpid,
|
|
|
|
AppSecret: q.config.Secret,
|
|
|
|
Token: q.config.Token,
|
|
|
|
EncodingAESKey: q.config.AesKey,
|
|
|
|
Cache: wechatCache,
|
|
|
|
}
|
|
|
|
|
|
|
|
// 首次配置
|
|
|
|
if strings.ToUpper(ctx.Request.Method) == http.MethodGet {
|
|
|
|
sign := wutil.Signature(ctx.Query("timestamp"), ctx.Query("echostr"),
|
|
|
|
ctx.Query("nonce"), wechatConfig.Token)
|
|
|
|
if sign != ctx.Query("msg_signature") {
|
|
|
|
log.Errorf("sign error forcheck config")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, resp, err := wutil.DecryptMsg(wechatConfig.AppID, ctx.Query("echostr"), wechatConfig.EncodingAESKey)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("DecryptMsg failed! error:%s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Data(http.StatusOK, "Content-type: text/plain", resp)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2.响应消息
|
|
|
|
wc := wechat.NewWechat(wechatConfig)
|
|
|
|
ctx.Request.URL.RawQuery += "&encrypt_type=aes"
|
|
|
|
server := wc.GetServer(ctx.Request, ctx.Writer)
|
|
|
|
|
|
|
|
server.SetMessageHandler(q.config.Replay)
|
|
|
|
|
|
|
|
server.SetDebug(true)
|
|
|
|
err := server.Serve()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("qiye weixin Service err:%s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = server.Send()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("qiye weixin Send err:%s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 18:36:07 +08:00
|
|
|
func (q *App) getJsapiTicket() (string, error) {
|
|
|
|
|
|
|
|
resp, err := http.Get("https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=" + q.GetToken() + "&type=agent_config")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
jsonData := make(map[string]interface{})
|
|
|
|
json.Unmarshal(body, &jsonData)
|
|
|
|
return cast.ToString(jsonData["ticket"]), nil
|
|
|
|
}
|
|
|
|
|
2024-01-22 22:34:59 +08:00
|
|
|
func (q *App) refreshToken() error {
|
|
|
|
if time.Now().Unix() <= q.tokenExpire-600 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
reqUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", q.config.Corpid, q.config.Secret)
|
|
|
|
rspBody, err := util.HttpGet(reqUrl, nil)
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
result, err := q.GetResult(rspBody)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
q.token = cast.ToString(result["access_token"])
|
|
|
|
q.tokenExpire = time.Now().Unix() + cast.ToInt64(result["expires_in"])
|
|
|
|
return nil
|
|
|
|
}
|