func execLevelExamples() { // 默认日志级别 debug log.Printf("default log level: %s", log.GetLevel()) log.Tracef("IsTraceEnabled? %t", log.IsTraceEnabled()) log.Debugf("IsDebugEnabled? %t", log.IsDebugEnabled()) log.Infof("IsInfoEnabled? %t", log.IsInfoEnabled()) // trace 级别 log.SetLevel(log.Ltrace) log.Tracef(msgFmt, 1) // info 级别 log.SetLevel(log.Linfo) log.Debugf(msgFmt, 2) log.Infof(msgFmt, 2) // warn 级别 log.SetLevel(log.Lwarn) log.Infof(msgFmt, 3) log.Warnf(msgFmt, 3) // error 级别 log.SetLevel(log.Lerror) log.Warnf(msgFmt, 4) log.Errorf(msgFmt, 4) // 恢复默认级别,防止影响其他测试 // debug 级别 log.SetLevel(log.Ldebug) log.Tracef(msgFmt, 5) log.Debugf(msgFmt, 5) }
// RefreshAccessToken 定时刷新 access_token func RefreshAccessToken(appId, appSecret string) { // 内部变量,外部不可以调用 var _token = &accessToken{} AccessToken = func() string { _token.mutex.RLock() defer _token.mutex.RUnlock() return _token.AccessToken } go func() { url := fmt.Sprintf(tokenURL, appId, appSecret) tick := time.Tick(refreshTimeout) for { new := refresh(url) log.Debugf("old access token %+v", _token) log.Debugf("new access token %+v", new) _token.mutex.Lock() _token.AccessToken = new.AccessToken _token.ExpiresIn = new.ExpiresIn _token.mutex.Unlock() <-tick // 等待下一个时钟周期到来 } }() }
func echoMsgVoice(m *weixin.RecvVoice) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyVoice{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, MediaId: m.MediaId, } log.Debugf("%+v", ret) return ret }
// EventDefaultHandler 注册默认处理器 func EventDefaultHandler(m *weixin.Message) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: fmt.Sprintf("Event=%s", m.Event), } log.Debugf("replay message: %+v", ret) return ret }
func echoMsgText(m *weixin.RecvText) weixin.ReplyMsg { log.Debugf("receive message: %+v", m) // echo message ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: m.FromUserName + ", " + m.Content, } log.Debugf("replay message: %+v", ret) return ret }
func echoMsgLocation(m *weixin.RecvLocation) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: weixin.AccessToken(), } log.Debugf("replay message: %+v", ret) return ret }
// EventViewHandler 注册点击菜单跳转链接时的事件处理器 func EventViewHandler(m *weixin.EventView) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message 貌似用户收不到回复的消息??? ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: fmt.Sprintf("Event=%s, EventKey=%s", m.Event, m.EventKey), } log.Debugf("replay message: %+v", ret) return ret }
// EventSubscribeHandler 注册关注事件处理器 func EventSubscribeHandler(m *weixin.EventSubscribe) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: fmt.Sprintf("Event=%s, EventKey=%s, Ticket=%s", m.Event, m.EventKey, m.Ticket), } log.Debugf("replay message: %+v", ret) return ret }
// EventTemplateSendJobFinishHandler 模版消息发送结果通知事件 func EventTemplateSendJobFinishHandler(m *weixin.EventTemplateSendJobFinish) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: fmt.Sprintf("Event=%s, MsgID=%d, Status=%s", m.Event, m.MsgID, m.Status), } log.Debugf("replay message: %+v", ret) return ret }
func echoMsgImage(m *weixin.RecvImage) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyImage{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, PicUrl: m.PicUrl, MediaId: m.MediaId, } log.Debugf("%+v", ret) return ret }
// EventLocationHandler 注册上报地理位置事件处理器 func EventLocationHandler(m *weixin.EventLocation) weixin.ReplyMsg { log.Debugf("%+v", m) // echo message ret := &weixin.ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: fmt.Sprintf("Latitude=%.6f, Longitude=%.6f, Precision=%.6f", m.Latitude, m.Longitude, m.Precision), } log.Debugf("replay message: %+v", ret) return ret }
func echoMsgLink(m *weixin.RecvLink) weixin.ReplyMsg { log.Debugf("%+v", m) // 回复图文消息 return nil }
func main() { addr := ":3080" weixin.Initialize(originId, appId, appSecret, token, encodingAESKey) weixin.RecvTextHandler = echoMsgText // 注册文本消息处理器 weixin.RecvImageHandler = echoMsgImage // 注册图片消息处理器 weixin.RecvVoiceHandler = echoMsgVoice // 注册语音消息处理器 weixin.RecvVideoHandler = echoMsgVideo // 注册视频消息处理器 weixin.RecvShortVideoHandler = echoMsgShortVideo // 注册小视频消息处理器 weixin.RecvLocationHandler = echoMsgLocation // 注册位置消息处理器 weixin.RecvLinkHandler = echoMsgLink // 注册链接消息处理器 weixin.RecvDefaultHandler = defaultHandler // 注册默认处理器 weixin.EventSubscribeHandler = EventSubscribeHandler // 注册关注事件处理器 weixin.EventUnsubscribeHandler = EventUnsubscribeHandler // 注册取消关注事件处理器 weixin.EventLocationHandler = EventLocationHandler // 注册上报地理位置事件处理器 weixin.EventClickHandler = EventClickHandler // 注册点击自定义菜单事件处理器 weixin.EventViewHandler = EventViewHandler // 注册点击菜单跳转链接时的事件处理器 // 模版消息发送结果通知事件 weixin.EventTemplateSendJobFinishHandler = EventTemplateSendJobFinishHandler weixin.EventDefaultHandler = EventDefaultHandler // 注册默认处理器 http.HandleFunc("/weixin", weixin.HandleAccess) http.Handle("/", http.FileServer(http.Dir("examples/static"))) // http.Handle("/admin/", http.StripPrefix("/admin/", http.FileServer(http.Dir("admin")))) log.Debugf("server is running at %s", addr) http.ListenAndServe(addr, nil) }
func echoMsgShortVideo(m *weixin.RecvVideo) weixin.ReplyMsg { log.Debugf("%+v", m) // MediaId ??? ret := &weixin.ReplyVideo{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, MediaId: m.ThumbMediaId, Title: "shortvideo", Description: "thist is a test desc...", } log.Debugf("%+v", ret) return ret }
func parseBody(encryptType, timestamp, nonce, msgSignature string, body []byte) (msg *Message, err error) { msg = &Message{} // 如果报文被加密了,先要验签解密 if encryptType == "aes" { encMsg := &EncMessage{} // 解析加密的 xml err = xml.Unmarshal(body, encMsg) if err != nil { return nil, err } msg.ToUserName = encMsg.ToUserName msg.Encrypt = encMsg.Encrypt if !CheckSignature(Token, timestamp, nonce, encMsg.Encrypt, msgSignature) { return nil, errors.New("check signature error") } body, err = DecryptMsg(encMsg.Encrypt, EncodingAESKey, AppId) if err != nil { return nil, err } log.Debugf("receive: %s", body) } // 解析 xml err = xml.Unmarshal(body, msg) if err != nil { return nil, err } return msg, nil }
func defaultHandler(msg *weixin.Message) weixin.ReplyMsg { log.Debugf("%+v", msg) event := weixin.NewRecvEvent(msg) js, _ := json.Marshal(event) // echo message ret := &weixin.ReplyText{ ToUserName: msg.FromUserName, FromUserName: msg.ToUserName, CreateTime: msg.CreateTime, Content: string(js), } log.Debugf("replay message: %+v", ret) return ret }
// 处理所有来自微信的消息,已经验证过 URL 和 Method 了 func processMessage(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() timestamp := q.Get("timestamp") nonce := q.Get("nonce") encryptType := q.Get("encrypt_type") msgSignature := q.Get("msg_signature") // 读取报文 body, err := ioutil.ReadAll(r.Body) if err != nil { log.Error(err) http.Error(w, "read body error", http.StatusNotAcceptable) return } log.Debugf("from weixin: %s", body) msg, err := parseBody(encryptType, timestamp, nonce, msgSignature, body) if err != nil { log.Error(err) http.Error(w, err.Error(), http.StatusBadRequest) return } // 处理消息 reply := HandleMessage(msg) // 如果返回为 nil,表示不需要回复,结束 if reply == nil { return } // 如果返回不为 nil,表示需要回复 ret, err := packReply(reply, encryptType, timestamp, nonce) if err != nil { log.Error(err) http.Error(w, err.Error(), http.StatusBadRequest) return } log.Debugf("to weixin: %s", ret) w.Header().Set("Content-Type", "text/xml; charset=utf-8") w.Write(ret) }
func TestEventTemplateSendJobFinishHandler(t *testing.T) { // EventTemplateSendJobFinishHandler 模版消息发送结果通知事件 EventTemplateSendJobFinishHandler = func(m *EventTemplateSendJobFinish) ReplyMsg { log.Debugf("%+v", m) // echo message ret := &ReplyText{ ToUserName: m.FromUserName, FromUserName: m.ToUserName, CreateTime: m.CreateTime, Content: fmt.Sprintf("Event=%s, MsgID=%d, Status=%s", m.Event, m.MsgID, m.Status), } log.Debugf("replay message: %+v", ret) return ret } body := `<xml> <ToUserName><![CDATA[gh_7f083739789a]]></ToUserName> <FromUserName><![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8]]></FromUserName> <CreateTime>1395658984</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event> <MsgID>200163840</MsgID> <Status><![CDATA[failed: system failed]]></Status> </xml>` req, _ := http.NewRequest("POST", testURL, bytes.NewBufferString(body)) w := httptest.NewRecorder() HandleAccess(w, req) if w.Code != http.StatusOK { t.Errorf("http request error with status %d: %s", w.Code, w.Body) t.FailNow() } t.Logf("%s", w.Body) }
// PostUnmarshal 工具类, POST json 并解析返回的报文,返回 error func PostUnmarshal(url string, js []byte, ret interface{}) (err error) { log.Debugf("url=%s, body=%s", url, js) resp, err := http.Post(url, "application/json", bytes.NewBuffer(js)) if err != nil { return err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(ret) if err != nil { return err } return nil }
func main() { log.Debugf("this is a test message, %d", 1111) format := fmt.Sprintf("%s %s %s %s:%d %s", "2006-01-02 15:04:05.000000", log.TagToken, log.LevelToken, log.ProjectToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Tinfof("6ba7b814-9dad-11d1-80b4-00c04fd430c8", "this is a test message, %d", 1111) format = fmt.Sprintf(`{"date": "%s", "time": "%s", "level": "%s", "file": "%s", "line": %d, "log": "%s"}`, "2006-01-02", "15:04:05.999", log.LevelToken, log.ProjectToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Infof("this is a test message, %d", 1111) format = fmt.Sprintf(`<log><date>%s</date><time>%s</time><level>%s</level><file>%s</file><line>%d</line><msg>%s</msg><log>`, "2006-01-02", "15:04:05.000", log.LevelToken, log.ProjectToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Tinfof("6ba7b814-9dad-11d1-80b4-00c04fd430c8", "this is a test message, %d", 1111) log.Error("level = debug") log.Infof("this is a test message, %d", 1111) log.Errorf("this is another test message, %d", 22222) // Fatalf("%d %s", log.FatalLevel, log.FatalLevel) format = fmt.Sprintf("%s %s %s %s:%d %s", "2006-1-2", "3:4:05.9", log.LevelToken, log.PathToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Infof("this is a test message, %d", 1111) format = fmt.Sprintf("%s %s %s %s:%d %s", "2006-01-02", "15:04:05.999999", log.LevelToken, log.PackageToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Infof("this is a test message, %d", 1111) format = fmt.Sprintf("%s %s %s:%d %s", "2006-01-02 15:04:05.000000", log.LevelToken, log.ProjectToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Infof("this is a test message, %d", 1111) format = fmt.Sprintf(`{"date": "%s", "time": "%s", "level": "%s", "file": "%s", "line": %d, "log": "%s"}`, "2006-01-02", "15:04:05.999", log.LevelToken, log.ProjectToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Infof("this is a test message, %d", 1111) format = fmt.Sprintf(`<log><date>%s</date><time>%s</time><level>%s</level><file>%s</file><line>%d</line><msg>%s</msg><log>`, "2006-01-02", "15:04:05.000", log.LevelToken, log.ProjectToken, log.LineToken, log.MessageToken) log.ChangeFormat(format) log.Infof("this is a test message, %d", 1111) }
func packReply(reply ReplyMsg, encryptType, timestamp, nonce string) (ret []byte, err error) { switch reply.(type) { case *ReplyText: reply.SetMsgType(MsgTypeText) case *ReplyImage: reply.SetMsgType(MsgTypeImage) case *ReplyVoice: reply.SetMsgType(MsgTypeVoice) case *ReplyVideo: reply.SetMsgType(MsgTypeVideo) case *ReplyMusic: reply.SetMsgType(MsgTypeMusic) case *ReplyNews: reply.SetMsgType(MsgTypeNews) default: panic("unexpected custom message type") } ret, err = xml.MarshalIndent(reply, "", " ") if err != nil { return nil, err } log.Debugf("replay: %s", ret) // 如果接收的消息加密了,那么回复的消息也需要签名加密 if encryptType == "aes" { b64Enc, err := EncryptMsg(ret, EncodingAESKey, AppId) if err != nil { return nil, err } encMsg := EncMessage{ Encrypt: b64Enc, MsgSignature: Signature(Token, timestamp, nonce, b64Enc), TimeStamp: timestamp, Nonce: nonce, // 随机数 } ret, err = xml.MarshalIndent(encMsg, "", " ") if err != nil { return nil, err } } return ret, nil }
// HandleMessage 处理各类消息 func HandleMessage(msg *Message) (ret ReplyMsg) { log.Debugf("process `%s` message", msg.MsgType) switch msg.MsgType { case MsgTypeText: if RecvTextHandler != nil { return RecvTextHandler(NewRecvText(msg)) } case MsgTypeImage: if RecvImageHandler != nil { return RecvImageHandler(NewRecvImage(msg)) } case MsgTypeVoice: if RecvVoiceHandler != nil { return RecvVoiceHandler(NewRecvVoice(msg)) } case MsgTypeVideo: if RecvVideoHandler != nil { return RecvVideoHandler(NewRecvVideo(msg)) } case MsgTypeShortVideo: if RecvShortVideoHandler != nil { return RecvShortVideoHandler(NewRecvVideo(msg)) } case MsgTypeLocation: if RecvLocationHandler != nil { return RecvLocationHandler(NewRecvLocation(msg)) } case MsgTypeLink: if RecvLinkHandler != nil { return RecvLinkHandler(NewRecvLink(msg)) } case MsgTypeEvent: return HandleEvent(msg) default: log.Errorf("unexpected receive MsgType: %s", msg.MsgType) return nil } return RecvDefaultHandler(msg) }
// Upload 工具类, 上传文件 func Upload(url, fieldName string, file *os.File, ret interface{}, desc ...string) (err error) { buf := &bytes.Buffer{} w := multipart.NewWriter(buf) //关键的一步操作 // fw, err := w.CreateFormField(file.Name()) fw, err := w.CreateFormFile(fieldName, file.Name()) if err != nil { return err } _, err = io.Copy(fw, file) if err != nil { return err } contentType := w.FormDataContentType() if len(desc) > 0 { w.WriteField("description", desc[0]) } w.Close() log.Debugf("url=%s, fieldName=%s, fileName=%s", url, fieldName, file.Name()) resp, err := http.Post(url, contentType, buf) if err != nil { return err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(ret) if err != nil { return err } if wxerrer, ok := ret.(WeixinErrorer); ok { wxerr := wxerrer.GetWeixinError() if wxerr.ErrCode != WeixinErrCodeSuccess { return wxerr } } return nil }
// GetUnmarshal 工具类, Get 并解析返回的报文,返回 error func GetUnmarshal(url string, ret interface{}) (err error) { log.Debugf("url=%s", url) resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(ret) if err != nil { return err } if wxerrer, ok := ret.(WeixinErrorer); ok { wxerr := wxerrer.GetWeixinError() if wxerr.ErrCode != WeixinErrCodeSuccess { return wxerr } } return nil }
// HandleAccess 接入微信公众平台开发,并接口来自微信服务器的消息 func HandleAccess(w http.ResponseWriter, r *http.Request) { log.Debugf("%s", r.URL) q := r.URL.Query() signature := q.Get("signature") timestamp := q.Get("timestamp") nonce := q.Get("nonce") // 每次都验证 URL,以判断来源是否合法 if !ValidateURL(Token, timestamp, nonce, signature) { http.Error(w, "validate url error, request not from weixin?", http.StatusUnauthorized) return } switch r.Method { // 如果是 GET 请求,表示这是接入验证请求 case "GET": w.Write([]byte(q.Get("echostr"))) case "POST": processMessage(w, r) default: http.Error(w, "only GET or POST method allowed", http.StatusUnauthorized) } }
EventPicSysphotoHandler func(*EventPicSysphoto) ReplyMsg EventPicPhotoOrAlbumHandler func(*EventPicPhotoOrAlbum) ReplyMsg EventPicWeixinHandler func(*EventPicWeixin) ReplyMsg EventLocationSelectHandler func(*EventLocationSelect) ReplyMsg EventQualificationVerifySuccessHandler func(*EventQualificationVerifySuccess) ReplyMsg // 资质认证成功 EventQualificationVerifyFailHandler func(*EventQualificationVerifyFail) ReplyMsg // 资质认证失败 EventNamingVerifySuccessHandler func(*EventNamingVerifySuccess) ReplyMsg // 名称认证成功(即命名成功) EventNamingVerifyFailHandler func(*EventNamingVerifyFail) ReplyMsg // 名称认证失败 EventAnnualRenewHandler func(*EventAnnualRenew) ReplyMsg // 年审通知 EventVerifyExpiredHandler func(*EventVerifyExpired) ReplyMsg // 认证过期失效通知 ) // RecvDefaultHandler 如果没有注册某类消息处理器,那么收到这类消息时,使用这个默认处理器 var RecvDefaultHandler = func(msg *Message) (reply ReplyMsg) { log.Debugf("unregistered receive message handler %s, use RecvDefaultHandler", msg.MsgType) return nil } // EventDefaultHandler 如果没有注册某类事件处理器,那么收到这类事件时,使用这个默认处理器 var EventDefaultHandler = func(msg *Message) (reply ReplyMsg) { log.Debugf("unregistered receive event handler %s, use EventDefaultHandler", msg.Event) return nil } // HandleMessage 处理各类消息 func HandleMessage(msg *Message) (ret ReplyMsg) { log.Debugf("process `%s` message", msg.MsgType) switch msg.MsgType { case MsgTypeText:
// EventUnsubscribeHandler 注册取消关注事件处理器 func EventUnsubscribeHandler(m *weixin.EventSubscribe) weixin.ReplyMsg { log.Debugf("someone gone") return nil }
// HandleEvent 处理各类事件 func HandleEvent(msg *Message) (reply ReplyMsg) { log.Debugf("process `%s` event", msg.MsgType) switch msg.Event { case EventTypeSubscribe: if EventSubscribeHandler != nil { return EventSubscribeHandler(NewEventSubscribe(msg)) } case EventTypeUnsubscribe: if EventUnsubscribeHandler != nil { return EventUnsubscribeHandler(NewEventSubscribe(msg)) } case EventTypeLocation: if EventLocationHandler != nil { return EventLocationHandler(NewEventLocation(msg)) } case EventTypeClick: if EventClickHandler != nil { return EventClickHandler(NewEventClick(msg)) } case EventTypeView: if EventViewHandler != nil { return EventViewHandler(NewEventView(msg)) } case EventTypeTemplateSendJobFinish: if EventTemplateSendJobFinishHandler != nil { return EventTemplateSendJobFinishHandler(NewEventTemplateSendJobFinish(msg)) } case EventTypeScancodePush: if EventScancodePushHandler != nil { return EventScancodePushHandler(NewEventScancodePush(msg)) } case EventTypeScancodeWaitmsg: if EventScancodeWaitmsgHandler != nil { return EventScancodeWaitmsgHandler(NewEventScancodeWaitmsg(msg)) } case EventTypePicSysphoto: if EventPicSysphotoHandler != nil { return EventPicSysphotoHandler(NewEventPicSysphoto(msg)) } case EventTypePicPhotoOrAlbum: if EventPicPhotoOrAlbumHandler != nil { return EventPicPhotoOrAlbumHandler(NewEventPicPhotoOrAlbum(msg)) } case EventTypePicWeixin: if EventPicWeixinHandler != nil { return EventPicWeixinHandler(NewEventPicWeixin(msg)) } case EventTypeLocationSelect: if EventLocationSelectHandler != nil { return EventLocationSelectHandler(NewEventLocationSelect(msg)) } case EventTypeQualificationVerifySuccess: if EventQualificationVerifySuccessHandler != nil { return EventQualificationVerifySuccessHandler(NewEventQualificationVerifySuccess(msg)) } case EventTypeQualificationVerifyFail: if EventQualificationVerifyFailHandler != nil { return EventQualificationVerifyFailHandler(NewEventQualificationVerifyFail(msg)) } case EventTypeNamingVerifySuccess: if EventNamingVerifySuccessHandler != nil { return EventNamingVerifySuccessHandler(NewEventNamingVerifySuccess(msg)) } case EventTypeNamingVerifyFail: if EventNamingVerifyFailHandler != nil { return EventNamingVerifyFailHandler(NewEventNamingVerifyFail(msg)) } case EventTypeAnnualRenew: if EventAnnualRenewHandler != nil { return EventAnnualRenewHandler(NewEventAnnualRenew(msg)) } case EventTypeVerifyExpired: if EventVerifyExpiredHandler != nil { return EventVerifyExpiredHandler(NewEventVerifyExpired(msg)) } default: log.Errorf("unexpected receive EventType: %s", msg.Event) return nil } return EventDefaultHandler(msg) }