package weapp import ( "encoding/base64" "encoding/binary" "encoding/json" "encoding/xml" "errors" "fmt" "io" "io/ioutil" "net/http" "reflect" "strconv" "strings" "time" ) // MsgType 消息类型 type MsgType = string // 所有消息类型 const ( MsgText MsgType = "text" // 文本消息类型 MsgImg = "image" // 图片消息类型 MsgCard = "miniprogrampage" // 小程序卡片消息类型 MsgEvent = "event" // 事件类型 MsgTrans = "transfer_customer_service" // 转发客服消息 ) // EventType 事件类型 type EventType string // 所有事件类型 const ( EventQuotaGet EventType = "get_quota" // 查询商户余额 EventCheckBusiness = "check_biz" // 取消订单事件 EventMediaCheckAsync = "wxa_media_check" // 异步校验图片/音频 EventAddExpressOrder = "add_waybill" // 请求下单事件 EventExpressPathUpdate = "add_express_path" // 运单轨迹更新事件 EventExpressOrderCancel = "cancel_waybill" // 审核商户事件 EventUserTempsessionEnter = "user_enter_tempsession" // 用户进入临时会话状态 EventNearbyPoiAuditInfoAdd = "add_nearby_poi_audit_info" // 附近小程序添加地点审核状态通知 EventDeliveryOrderStatusUpdate = "update_waybill_status" // 配送单配送状态更新通知 EventAgentPosQuery = "transport_get_agent_pos" // 查询骑手当前位置信息 EventAuthInfoGet = "get_auth_info" // 使用授权码拉取授权信息 EventAuthAccountCancel = "cancel_auth_account" // 取消授权帐号 EventDeliveryOrderAdd = "transport_add_order" // 真实发起下单任务 EventDeliveryOrderTipsAdd = "transport_add_tips" // 对待接单状态的订单增加小费 EventDeliveryOrderCancel = "transport_cancel_order" // 取消订单操作 EventDeliveryOrderReturnConfirm = "transport_confirm_return_to_biz" // 异常妥投商户收货确认 EventDeliveryOrderPreAdd = "transport_precreate_order" // 预下单 EventDeliveryOrderPreCancel = "transport_precancel_order" // 预取消订单 EventDeliveryOrderQuery = "transport_query_order_status" // 查询订单状态 EventDeliveryOrderReadd = "transport_readd_order" // 下单 EventPreAuthCodeGet = "get_pre_auth_code" // 获取预授权码 EventRiderScoreSet = "transport_set_rider_score" // 给骑手评分 ) // Server 微信通知服务处理器 type Server struct { appID string // 小程序 ID mchID string // 商户号 apiKey string // 商户签名密钥 token string // 微信服务器验证令牌 aesKey []byte // base64 解码后的消息加密密钥 validate bool // 是否验证请求来自微信服务器 textMessageHandler func(*TextMessageResult) *TransferCustomerMessage imageMessageHandler func(*ImageMessageResult) *TransferCustomerMessage cardMessageHandler func(*CardMessageResult) *TransferCustomerMessage userTempsessionEnterHandler func(*UserTempsessionEnterResult) mediaCheckAsyncHandler func(*MediaCheckAsyncResult) expressPathUpdateHandler func(*ExpressPathUpdateResult) addNearbyPoiAuditHandler func(*AddNearbyPoiResult) addExpressOrderHandler func(*AddExpressOrderResult) *AddExpressOrderReturn expressOrderCancelHandler func(*CancelExpressOrderResult) *CancelExpressOrderReturn checkExpressBusinessHandler func(*CheckExpressBusinessResult) *CheckExpressBusinessReturn quotaGetHandler func(*GetExpressQuotaResult) *GetExpressQuotaReturn deliveryOrderStatusUpdateHandler func(*DeliveryOrderStatusUpdateResult) *DeliveryOrderStatusUpdateReturn agentPosQueryHandler func(*AgentPosQueryResult) *AgentPosQueryReturn authInfoGetHandler func(*AuthInfoGetResult) *AuthInfoGetReturn authAccountCancelHandler func(*CancelAuthResult) *CancelAuthReturn deliveryOrderAddHandler func(*DeliveryOrderAddResult) *DeliveryOrderAddReturn deliveryOrderTipsAddHandler func(*DeliveryOrderAddTipsResult) *DeliveryOrderAddTipsReturn deliveryOrderCancelHandler func(*DeliveryOrderCancelResult) *DeliveryOrderCancelReturn deliveryOrderReturnConfirmHandler func(*DeliveryOrderReturnConfirmResult) *DeliveryOrderReturnConfirmReturn deliveryOrderPreAddHandler func(*DeliveryOrderPreAddResult) *DeliveryOrderPreAddReturn deliveryOrderPreCancelHandler func(*DeliveryOrderPreCancelResult) *DeliveryOrderPreCancelReturn deliveryOrderQueryHandler func(*DeliveryOrderQueryResult) *DeliveryOrderQueryReturn deliveryOrderReaddHandler func(*DeliveryOrderReaddResult) *DeliveryOrderReaddReturn preAuthCodeGetHandler func(*PreAuthCodeGetResult) *PreAuthCodeGetReturn riderScoreSetHandler func(*RiderScoreSetResult) *RiderScoreSetReturn } // OnCustomerServiceTextMessage add handler to handle customer text service message. func (srv *Server) OnCustomerServiceTextMessage(fn func(*TextMessageResult) *TransferCustomerMessage) { srv.textMessageHandler = fn } // OnCustomerServiceImageMessage add handler to handle customer image service message. func (srv *Server) OnCustomerServiceImageMessage(fn func(*ImageMessageResult) *TransferCustomerMessage) { srv.imageMessageHandler = fn } // OnCustomerServiceCardMessage add handler to handle customer card service message. func (srv *Server) OnCustomerServiceCardMessage(fn func(*CardMessageResult) *TransferCustomerMessage) { srv.cardMessageHandler = fn } // OnUserTempsessionEnter add handler to handle customer service message. func (srv *Server) OnUserTempsessionEnter(fn func(*UserTempsessionEnterResult)) { srv.userTempsessionEnterHandler = fn } // OnMediaCheckAsync add handler to handle MediaCheckAsync. func (srv *Server) OnMediaCheckAsync(fn func(*MediaCheckAsyncResult)) { srv.mediaCheckAsyncHandler = fn } // OnExpressPathUpdate add handler to handle ExpressPathUpdate. func (srv *Server) OnExpressPathUpdate(fn func(*ExpressPathUpdateResult)) { srv.expressPathUpdateHandler = fn } // OnAddNearbyPoi add handler to handle AddNearbyPoiAudit. func (srv *Server) OnAddNearbyPoi(fn func(*AddNearbyPoiResult)) { srv.addNearbyPoiAuditHandler = fn } // OnAddExpressOrder add handler to handle AddExpressOrder. func (srv *Server) OnAddExpressOrder(fn func(*AddExpressOrderResult) *AddExpressOrderReturn) { srv.addExpressOrderHandler = fn } // OnCheckExpressBusiness add handler to handle CheckBusiness. func (srv *Server) OnCheckExpressBusiness(fn func(*CheckExpressBusinessResult) *CheckExpressBusinessReturn) { srv.checkExpressBusinessHandler = fn } // OnCancelExpressOrder add handler to handle ExpressOrderCancel. func (srv *Server) OnCancelExpressOrder(fn func(*CancelExpressOrderResult) *CancelExpressOrderReturn) { srv.expressOrderCancelHandler = fn } // OnGetExpressQuota add handler to handle QuotaGet. func (srv *Server) OnGetExpressQuota(fn func(*GetExpressQuotaResult) *GetExpressQuotaReturn) { srv.quotaGetHandler = fn } // OnDeliveryOrderStatusUpdate add handler to handle DeliveryOrderStatusUpdate. // OnDeliveryOrderStatusUpdate add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderStatusUpdate(fn func(*DeliveryOrderStatusUpdateResult) *DeliveryOrderStatusUpdateReturn) { srv.deliveryOrderStatusUpdateHandler = fn } // OnAgentPosQuery add handler to handle AgentPosQuery. func (srv *Server) OnAgentPosQuery(fn func(*AgentPosQueryResult) *AgentPosQueryReturn) { srv.agentPosQueryHandler = fn } // OnAuthInfoGet add handler to handle AuthInfoGet. func (srv *Server) OnAuthInfoGet(fn func(*AuthInfoGetResult) *AuthInfoGetReturn) { srv.authInfoGetHandler = fn } // OnCancelAuth add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnCancelAuth(fn func(*CancelAuthResult) *CancelAuthReturn) { srv.authAccountCancelHandler = fn } // OnDeliveryOrderAdd add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderAdd(fn func(*DeliveryOrderAddResult) *DeliveryOrderAddReturn) { srv.deliveryOrderAddHandler = fn } // OnDeliveryOrderAddTips add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderAddTips(fn func(*DeliveryOrderAddTipsResult) *DeliveryOrderAddTipsReturn) { srv.deliveryOrderTipsAddHandler = fn } // OnDeliveryOrderCancel add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderCancel(fn func(*DeliveryOrderCancelResult) *DeliveryOrderCancelReturn) { srv.deliveryOrderCancelHandler = fn } // OnDeliveryOrderReturnConfirm add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderReturnConfirm(fn func(*DeliveryOrderReturnConfirmResult) *DeliveryOrderReturnConfirmReturn) { srv.deliveryOrderReturnConfirmHandler = fn } // OnDeliveryOrderPreAdd add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderPreAdd(fn func(*DeliveryOrderPreAddResult) *DeliveryOrderPreAddReturn) { srv.deliveryOrderPreAddHandler = fn } // OnDeliveryOrderPreCancel add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderPreCancel(fn func(*DeliveryOrderPreCancelResult) *DeliveryOrderPreCancelReturn) { srv.deliveryOrderPreCancelHandler = fn } // OnDeliveryOrderQuery add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderQuery(fn func(*DeliveryOrderQueryResult) *DeliveryOrderQueryReturn) { srv.deliveryOrderQueryHandler = fn } // OnDeliveryOrderReadd add handler to handle deliveryOrderStatusUpdate. func (srv *Server) OnDeliveryOrderReadd(fn func(*DeliveryOrderReaddResult) *DeliveryOrderReaddReturn) { srv.deliveryOrderReaddHandler = fn } // OnPreAuthCodeGet add handler to handle preAuthCodeGet. func (srv *Server) OnPreAuthCodeGet(fn func(*PreAuthCodeGetResult) *PreAuthCodeGetReturn) { srv.preAuthCodeGetHandler = fn } // OnRiderScoreSet add handler to handle riderScoreSet. func (srv *Server) OnRiderScoreSet(fn func(*RiderScoreSetResult) *RiderScoreSetReturn) { srv.riderScoreSetHandler = fn } type dataType = string const ( dataTypeJSON dataType = "application/json" dataTypeXML = "text/xml" ) // NewServer 返回经过初始化的Server func NewServer(appID, token, aesKey, mchID, apiKey string, validate bool) (*Server, error) { key, err := base64.RawStdEncoding.DecodeString(aesKey) if err != nil { return nil, err } server := Server{ appID: appID, mchID: mchID, apiKey: apiKey, token: token, aesKey: key, validate: validate, } return &server, nil } func getDataType(req *http.Request) dataType { content := req.Header.Get("Content-Type") switch { case strings.Contains(content, dataTypeJSON): return dataTypeJSON case strings.Contains(content, dataTypeXML): return dataTypeXML default: return content } } func unmarshal(data []byte, tp dataType, v interface{}) error { switch tp { case dataTypeJSON: if err := json.Unmarshal(data, v); err != nil { return err } case dataTypeXML: if err := xml.Unmarshal(data, v); err != nil { return err } default: return errors.New("invalid content type: " + tp) } return nil } func marshal(data interface{}, tp dataType) ([]byte, error) { switch tp { case dataTypeJSON: return json.Marshal(data) case dataTypeXML: return xml.Marshal(data) default: return nil, errors.New("invalid content type: " + tp) } } // 处理消息体 func (srv *Server) handleRequest(w http.ResponseWriter, r *http.Request, isEncrpt bool, tp dataType) (interface{}, error) { raw, err := ioutil.ReadAll(r.Body) if err != nil { return nil, err } if isEncrpt { // 处理加密消息 query := r.URL.Query() nonce, signature, timestamp := query.Get("nonce"), query.Get("signature"), query.Get("timestamp") // 检验消息是否来自微信服务器 if !validateSignature(signature, srv.token, timestamp, nonce) { return nil, errors.New("failed to validate signature") } res := new(EncryptedResult) if err := unmarshal(raw, tp, res); err != nil { return nil, err } body, err := srv.decryptMsg(res.Encrypt) if err != nil { return nil, err } length := binary.BigEndian.Uint32(body[16:20]) raw = body[20 : 20+length] } res := new(CommonServerResult) if err := unmarshal(raw, tp, res); err != nil { return nil, err } switch res.MsgType { case MsgText: msg := new(TextMessageResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.textMessageHandler != nil { return srv.textMessageHandler(msg), nil } case MsgImg: msg := new(ImageMessageResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.imageMessageHandler != nil { return srv.imageMessageHandler(msg), nil } case MsgCard: msg := new(CardMessageResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.cardMessageHandler != nil { return srv.cardMessageHandler(msg), nil } case MsgEvent: switch res.Event { case EventUserTempsessionEnter: msg := new(UserTempsessionEnterResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.userTempsessionEnterHandler != nil { srv.userTempsessionEnterHandler(msg) } case EventQuotaGet: msg := new(GetExpressQuotaResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.quotaGetHandler != nil { return srv.quotaGetHandler(msg), nil } case EventMediaCheckAsync: msg := new(MediaCheckAsyncResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.mediaCheckAsyncHandler != nil { srv.mediaCheckAsyncHandler(msg) } case EventAddExpressOrder: msg := new(AddExpressOrderResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.addExpressOrderHandler != nil { return srv.addExpressOrderHandler(msg), nil } case EventExpressOrderCancel: msg := new(CancelExpressOrderResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.expressOrderCancelHandler != nil { return srv.expressOrderCancelHandler(msg), nil } case EventCheckBusiness: msg := new(CheckExpressBusinessResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.checkExpressBusinessHandler != nil { return srv.checkExpressBusinessHandler(msg), nil } case EventDeliveryOrderStatusUpdate: msg := new(DeliveryOrderStatusUpdateResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderStatusUpdateHandler != nil { return srv.deliveryOrderStatusUpdateHandler(msg), nil } case EventAgentPosQuery: msg := new(AgentPosQueryResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.agentPosQueryHandler != nil { return srv.agentPosQueryHandler(msg), nil } case EventAuthInfoGet: msg := new(AuthInfoGetResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.authInfoGetHandler != nil { return srv.authInfoGetHandler(msg), nil } case EventAuthAccountCancel: msg := new(CancelAuthResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.authAccountCancelHandler != nil { return srv.authAccountCancelHandler(msg), nil } case EventDeliveryOrderAdd: msg := new(DeliveryOrderAddResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderAddHandler != nil { return srv.deliveryOrderAddHandler(msg), nil } case EventDeliveryOrderTipsAdd: msg := new(DeliveryOrderAddTipsResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderTipsAddHandler != nil { return srv.deliveryOrderTipsAddHandler(msg), nil } case EventDeliveryOrderCancel: msg := new(DeliveryOrderCancelResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderCancelHandler != nil { return srv.deliveryOrderCancelHandler(msg), nil } case EventDeliveryOrderReturnConfirm: msg := new(DeliveryOrderReturnConfirmResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderReturnConfirmHandler != nil { return srv.deliveryOrderReturnConfirmHandler(msg), nil } case EventDeliveryOrderPreAdd: msg := new(DeliveryOrderPreAddResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderPreAddHandler != nil { return srv.deliveryOrderPreAddHandler(msg), nil } case EventDeliveryOrderPreCancel: msg := new(DeliveryOrderPreCancelResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderPreCancelHandler != nil { return srv.deliveryOrderPreCancelHandler(msg), nil } case EventDeliveryOrderQuery: msg := new(DeliveryOrderQueryResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderQueryHandler != nil { return srv.deliveryOrderQueryHandler(msg), nil } case EventDeliveryOrderReadd: msg := new(DeliveryOrderReaddResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.deliveryOrderReaddHandler != nil { return srv.deliveryOrderReaddHandler(msg), nil } case EventPreAuthCodeGet: msg := new(PreAuthCodeGetResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.preAuthCodeGetHandler != nil { return srv.preAuthCodeGetHandler(msg), nil } case EventRiderScoreSet: msg := new(RiderScoreSetResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.riderScoreSetHandler != nil { return srv.riderScoreSetHandler(msg), nil } case EventExpressPathUpdate: msg := new(ExpressPathUpdateResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.expressPathUpdateHandler != nil { srv.expressPathUpdateHandler(msg) } case EventNearbyPoiAuditInfoAdd: msg := new(AddNearbyPoiResult) if err := unmarshal(raw, tp, msg); err != nil { return nil, err } if srv.addNearbyPoiAuditHandler != nil { srv.addNearbyPoiAuditHandler(msg) } default: return nil, fmt.Errorf("unexpected message type '%s'", res.MsgType) } default: return nil, fmt.Errorf("unexpected message type '%s'", res.MsgType) } return nil, nil } // 判断 interface{} 是否为空 func isNil(i interface{}) bool { defer func() { recover() }() vi := reflect.ValueOf(i) return vi.IsNil() } // Serve 接收并处理微信通知服务 func (srv *Server) Serve(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "POST": tp := getDataType(r) isEncrpt := isEncrypted(r) res, err := srv.handleRequest(w, r, isEncrpt, tp) if err != nil { return fmt.Errorf("handle request content error: %s", err) } if !isNil(res) { raw, err := marshal(res, tp) if err != nil { return err } if isEncrpt { res, err := srv.encryptMsg(string(raw), time.Now().Unix()) if err != nil { return err } raw, err = marshal(res, tp) if err != nil { return err } } w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", tp) if _, err := w.Write(raw); err != nil { return err } } else { w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header w.WriteHeader(http.StatusOK) _, err = io.WriteString(w, "success") if err != nil { return err } } return nil case "GET": if srv.validate { // 请求来自微信验证成功后原样返回 echostr 参数内容 if !srv.validateServer(r) { return errors.New("验证消息来自微信服务器失败") } w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header w.WriteHeader(http.StatusOK) echostr := r.URL.Query().Get("echostr") _, err := io.WriteString(w, echostr) if err != nil { return err } } return nil default: return errors.New("invalid request method: " + r.Method) } } // 判断消息是否加密 func isEncrypted(req *http.Request) bool { return req.URL.Query().Get("encrypt_type") == "aes" } // 验证消息的确来自微信服务器 // 1.将token、timestamp、nonce三个参数进行字典序排序 // 2.将三个参数字符串拼接成一个字符串进行sha1加密 // 3.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 func (srv *Server) validateServer(req *http.Request) bool { query := req.URL.Query() nonce := query.Get("nonce") signature := query.Get("signature") timestamp := query.Get("timestamp") return validateSignature(signature, nonce, timestamp, srv.token) } // 加密消息 func (srv *Server) encryptMsg(message string, timestamp int64) (*EncryptedMsgRequest, error) { key := srv.aesKey //获得16位随机字符串,填充到明文之前 nonce := randomString(16) text := nonce + strconv.Itoa(len(message)) + message + srv.appID plaintext := pkcs7encode([]byte(text)) cipher, err := cbcEncrypt(key, plaintext, key) if err != nil { return nil, err } encrypt := base64.StdEncoding.EncodeToString(cipher) timestr := strconv.FormatInt(timestamp, 10) //生成安全签名 signature := createSignature(srv.token, timestr, nonce, encrypt) request := EncryptedMsgRequest{ Nonce: nonce, Encrypt: encrypt, TimeStamp: timestr, MsgSignature: signature, } return &request, nil } // 检验消息的真实性,并且获取解密后的明文. func (srv *Server) decryptMsg(encrypted string) ([]byte, error) { key := srv.aesKey ciphertext, err := base64.StdEncoding.DecodeString(encrypted) if err != nil { return nil, err } data, err := cbcDecrypt(key, ciphertext, key) if err != nil { return nil, err } return data, nil }