package qyweixin import ( "bytes" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/wechat" "git.u8t.cn/open/gosdk/wechat/cache" "git.u8t.cn/open/gosdk/wechat/message" wutil "git.u8t.cn/open/gosdk/wechat/util" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" "github.com/smbrave/goutil" "github.com/spf13/cast" "io" "io/ioutil" "mime/multipart" "net/http" "os" "path/filepath" "strings" "time" ) var ( wechatCache cache.Cache = cache.NewMemory() ) 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"` } type JsapiConfig struct { Corpid string `json:"corpid"` Agent string `json:"agent"` Signature string `json:"signature"` Noncestr string `json:"noncestr"` Timestamp string `json:"timestamp"` } type BaseResponse struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } 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 } func NewApp(cfg *AppConfig) *App { return &App{ config: cfg, } } 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.Warnf("result[%s] error ", string(rspBody)) return nil, fmt.Errorf("%d:%s", cast.ToInt(result["errcode"]), cast.ToString(result["errmsg"])) } return result, nil } 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 } 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 } 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 } 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 } 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! token[%s]", wechatConfig.Token) return } _, resp, err := wutil.DecryptMsg(wechatConfig.AppID, ctx.Query("echostr"), wechatConfig.EncodingAESKey) if err != nil { log.Errorf("DecryptMsg failed! appid[%s] aeskey[%s] error:%s ", wechatConfig.AppID, wechatConfig.EncodingAESKey, 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 [%s] err:%s", goutil.EncodeJSON(wechatConfig), err.Error()) return } err = server.Send() if err != nil { log.Errorf("qiye weixin Send err:%s", err.Error()) return } } 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 } 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 }