core

package
v0.0.0-...-4034f70 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2020 License: Apache-2.0 Imports: 28 Imported by: 0

README

微信公众号 SDK 核心 package

微信公众号的处理逻辑都在这个 package 里面, 其他的模块都是在这个 package 基础上的再封装!

回调请求处理

一个回调地址(多个公众号可以共用一个回调地址)的 http 请求对应了一个 http handler(http.Handler, gin.HandlerFunc…), 这个 http handler 里面的主要逻辑是调用对应公众号的 core.Server 的 ServeHTTP 方法来处理回调请求, core.Server.ServeHTTP 做签名的验证和消息解密, 然后调用 core.Server 的 core.Handler 属性的 ServeMsg 方法来处理消息(事件).
回调请求处理逻辑图

Documentation

Overview

微信公众平台(订阅号&服务号) SDK 的核心库

Index

Examples

Constants

View Source
const (
	ErrCodeOK                 = 0
	ErrCodeInvalidCredential  = 40001 // access_token 过期错误码
	ErrCodeAccessTokenExpired = 42001 // access_token 过期错误码(maybe!!!)
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AccessTokenServer

type AccessTokenServer interface {
	Token() (token string, err error)                           // 请求中控服务器返回缓存的 access_token
	RefreshToken(currentToken string) (token string, err error) // 请求中控服务器刷新 access_token
	IID01332E16DF5011E5A9D5A4DB30FED8E1()                       // 接口标识, 没有实际意义
}

access_token 中控服务器接口.

type Client

type Client struct {
	AccessTokenServer
	HttpClient *http.Client
}

func NewClient

func NewClient(srv AccessTokenServer, clt *http.Client) *Client

NewClient 创建一个新的 Client.

如果 clt == nil 则默认用 util.DefaultHttpClient

func (*Client) GetJSON

func (clt *Client) GetJSON(incompleteURL string, response interface{}) (err error)

GetJSON HTTP GET 微信资源, 然后将微信服务器返回的 JSON 用 encoding/json 解析到 response.

NOTE:
1. 一般不需要调用这个方法, 请直接调用高层次的封装函数;
2. 最终的 URL == incompleteURL + access_token;
3. response 格式有要求, 要么是 *Error, 要么是下面结构体的指针(注意 Error 必须是第一个 Field):
    struct {
        Error
        ...
    }

func (*Client) PostJSON

func (clt *Client) PostJSON(incompleteURL string, request interface{}, response interface{}) (err error)

PostJSON 用 encoding/json 把 request marshal 为 JSON, HTTP POST 到微信服务器, 然后将微信服务器返回的 JSON 用 encoding/json 解析到 response.

NOTE:
1. 一般不需要调用这个方法, 请直接调用高层次的封装函数;
2. 最终的 URL == incompleteURL + access_token;
3. response 格式有要求, 要么是 *Error, 要么是下面结构体的指针(注意 Error 必须是第一个 Field):
    struct {
        Error
        ...
    }

func (*Client) PostMultipartForm

func (clt *Client) PostMultipartForm(incompleteURL string, fields []MultipartFormField, response interface{}) (err error)

PostMultipartForm 通用上传接口.

--BOUNDARY
Content-Disposition: form-data; name="FIELDNAME"; filename="FILENAME"
Content-Type: application/octet-stream

FILE-CONTENT
--BOUNDARY
Content-Disposition: form-data; name="FIELDNAME"

JSON-DESCRIPTION
--BOUNDARY--

NOTE:
1. 一般不需要调用这个方法, 请直接调用高层次的封装函数;
2. 最终的 URL == incompleteURL + access_token;
3. response 格式有要求, 要么是 *Error, 要么是下面结构体的指针(注意 Error 必须是第一个 Field):
    struct {
        Error
        ...
    }

type Context

type Context struct {
	ResponseWriter http.ResponseWriter
	Request        *http.Request

	QueryParams  url.Values // 回调请求 URL 的查询参数集合
	EncryptType  string     // 回调请求 URL 的加密方式参数: encrypt_type
	MsgSignature string     // 回调请求 URL 的消息体签名参数: msg_signature
	Signature    string     // 回调请求 URL 的签名参数: signature
	Timestamp    int64      // 回调请求 URL 的时间戳参数: timestamp
	Nonce        string     // 回调请求 URL 的随机数参数: nonce

	MsgCiphertext []byte    // 消息的密文文本
	MsgPlaintext  []byte    // 消息的明文文本, xml格式
	MixedMsg      *MixedMsg // 消息

	Token  string // 当前消息所属公众号的 Token
	AESKey []byte // 当前消息加密所用的 aes-key, read-only!!!
	Random []byte // 当前消息加密所用的 random, 16-bytes
	AppId  string // 当前消息加密所用的 AppId
	// contains filtered or unexported fields
}

Context 是 Handler 处理消息(事件)的上下文环境. 非并发安全!

func (*Context) AESResponse

func (ctx *Context) AESResponse(msg interface{}, timestamp int64, nonce string, random []byte) (err error)

AESResponse 回复aes加密的消息给微信服务器.

msg:       经过 encoding/xml.Marshal 得到的结果符合微信消息格式的任何数据结构
timestamp: 时间戳, 如果为 0 则默认使用 Context.Timestamp
nonce:     随机数, 如果为 "" 则默认使用 Context.Nonce
random:    16字节的随机字符串, 如果为 nil 则默认使用 Context.Random

func (*Context) Abort

func (ctx *Context) Abort()

Abort 阻止系统调用当前 handler 后续的 handlers, 即当前的 handler 处理完毕就返回, 一般在 middleware 中调用.

func (*Context) Get

func (ctx *Context) Get(key string) (value interface{}, exists bool)

Get 返回 Context 中 key 对应的 value, 如果 key 存在的返回 (value, true), 否则返回 (nil, false).

func (*Context) IsAborted

func (ctx *Context) IsAborted() bool

IsAborted 返回 true 如果 Context.Abort() 被调用了, 否则返回 false.

func (*Context) MustGet

func (ctx *Context) MustGet(key string) interface{}

MustGet 返回 Context 中 key 对应的 value, 如果 key 不存在则会 panic.

func (*Context) Next

func (ctx *Context) Next()

Next 中断当前 handler 程序逻辑执行其后续的 handlers, 一般在 middleware 中调用.

func (*Context) NoneResponse

func (ctx *Context) NoneResponse() (err error)

NoneResponse 表示没有消息回复给微信服务器.

func (*Context) RawResponse

func (ctx *Context) RawResponse(msg interface{}) (err error)

RawResponse 回复明文消息给微信服务器.

msg: 经过 encoding/xml.Marshal 得到的结果符合微信消息格式的任何数据结构

func (*Context) Set

func (ctx *Context) Set(key string, value interface{})

Set 存储 key-value pair 到 Context 中.

func (*Context) SetHandlers

func (ctx *Context) SetHandlers(handlers HandlerChain)

SetHandlers 设置 handlers 给 Context.Next() 调用, 务必在 Context.Next() 调用之前设置, 否则会 panic.

NOTE: 此方法一般用不到, 除非你自己实现一个 Handler 给 Server 使用, 参考 ServeMux.

type DefaultAccessTokenServer

type DefaultAccessTokenServer struct {
	// contains filtered or unexported fields
}

DefaultAccessTokenServer 实现了 AccessTokenServer 接口.

NOTE:
1. 用于单进程环境.
2. 因为 DefaultAccessTokenServer 同时也是一个简单的中控服务器, 而不是仅仅实现 AccessTokenServer 接口,
   所以整个系统只能存在一个 DefaultAccessTokenServer 实例!

func NewDefaultAccessTokenServer

func NewDefaultAccessTokenServer(appId, appSecret string, httpClient *http.Client) (srv *DefaultAccessTokenServer)

NewDefaultAccessTokenServer 创建一个新的 DefaultAccessTokenServer, 如果 httpClient == nil 则默认使用 util.DefaultHttpClient.

func (*DefaultAccessTokenServer) IID01332E16DF5011E5A9D5A4DB30FED8E1

func (srv *DefaultAccessTokenServer) IID01332E16DF5011E5A9D5A4DB30FED8E1()

func (*DefaultAccessTokenServer) RefreshToken

func (srv *DefaultAccessTokenServer) RefreshToken(currentToken string) (token string, err error)

func (*DefaultAccessTokenServer) Token

func (srv *DefaultAccessTokenServer) Token() (token string, err error)

type Error

type Error struct {
	ErrCode int64  `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}

func (*Error) Error

func (err *Error) Error() string

type ErrorHandler

type ErrorHandler interface {
	ServeError(http.ResponseWriter, *http.Request, error)
}
var DefaultErrorHandler ErrorHandler = ErrorHandlerFunc(defaultErrorHandlerFunc)

type ErrorHandlerFunc

type ErrorHandlerFunc func(http.ResponseWriter, *http.Request, error)

func (ErrorHandlerFunc) ServeError

func (fn ErrorHandlerFunc) ServeError(w http.ResponseWriter, r *http.Request, err error)

type EventType

type EventType string

type Handler

type Handler interface {
	ServeMsg(*Context)
}

type HandlerChain

type HandlerChain []Handler

type HandlerFunc

type HandlerFunc func(*Context)

func (HandlerFunc) ServeMsg

func (fn HandlerFunc) ServeMsg(ctx *Context)

type MixedMsg

type MixedMsg struct {
	XMLName struct{} `xml:"xml" json:"-"`
	MsgHeader
	EventType EventType `xml:"Event" json:"Event"`

	MsgId        int64   `xml:"MsgId"        json:"MsgId"`        // request
	Content      string  `xml:"Content"      json:"Content"`      // request
	MediaId      string  `xml:"MediaId"      json:"MediaId"`      // request
	PicURL       string  `xml:"PicUrl"       json:"PicUrl"`       // request
	Format       string  `xml:"Format"       json:"Format"`       // request
	Recognition  string  `xml:"Recognition"  json:"Recognition"`  // request
	ThumbMediaId string  `xml:"ThumbMediaId" json:"ThumbMediaId"` // request
	LocationX    float64 `xml:"Location_X"   json:"Location_X"`   // request
	LocationY    float64 `xml:"Location_Y"   json:"Location_Y"`   // request
	Scale        int     `xml:"Scale"        json:"Scale"`        // request
	Label        string  `xml:"Label"        json:"Label"`        // request
	Title        string  `xml:"Title"        json:"Title"`        // request
	Description  string  `xml:"Description"  json:"Description"`  // request
	URL          string  `xml:"Url"          json:"Url"`          // request
	EventKey     string  `xml:"EventKey"     json:"EventKey"`     // request, menu
	Ticket       string  `xml:"Ticket"       json:"Ticket"`       // request
	Latitude     float64 `xml:"Latitude"     json:"Latitude"`     // request
	Longitude    float64 `xml:"Longitude"    json:"Longitude"`    // request
	Precision    float64 `xml:"Precision"    json:"Precision"`    // request

	// menu
	MenuId       int64 `xml:"MenuId" json:"MenuId"`
	ScanCodeInfo *struct {
		ScanType   string `xml:"ScanType"   json:"ScanType"`
		ScanResult string `xml:"ScanResult" json:"ScanResult"`
	} `xml:"ScanCodeInfo,omitempty" json:"ScanCodeInfo,omitempty"`
	SendPicsInfo *struct {
		Count   int `xml:"Count" json:"Count"`
		PicList []struct {
			PicMd5Sum string `xml:"PicMd5Sum" json:"PicMd5Sum"`
		} `xml:"PicList>item,omitempty" json:"PicList,omitempty"`
	} `xml:"SendPicsInfo,omitempty" json:"SendPicsInfo,omitempty"`
	SendLocationInfo *struct {
		LocationX float64 `xml:"Location_X" json:"Location_X"`
		LocationY float64 `xml:"Location_Y" json:"Location_Y"`
		Scale     int     `xml:"Scale"      json:"Scale"`
		Label     string  `xml:"Label"      json:"Label"`
		PoiName   string  `xml:"Poiname"    json:"Poiname"`
	} `xml:"SendLocationInfo,omitempty" json:"SendLocationInfo,omitempty"`

	MsgID                int64  `xml:"MsgID"  json:"MsgID"`  // template, mass
	Status               string `xml:"Status" json:"Status"` // template, mass
	TotalCount           int    `xml:"TotalCount,omitempty"  json:"TotalCount"`
	FilterCount          int    `xml:"FilterCount" json:"FilterCount"`
	SentCount            int    `xml:"SentCount"   json:"SentCount"`
	ErrorCount           int    `xml:"ErrorCount"  json:"ErrorCount"`
	CopyrightCheckResult *struct {
		Count      int `xml:"Count" json:"Count"`
		ResultList []struct {
			Item *struct {
				ArticleIdx            int    `xml:"ArticleIdx"  json:"ArticleIdx"`
				UserDeclareState      int    `xml:"UserDeclareState"  json:"UserDeclareState"`
				AuditState            int    `xml:"AuditState"  json:"AuditState"`
				OriginalArticleUrl    string `xml:"OriginalArticleUrl"  json:"OriginalArticleUrl"`
				OriginalArticleType   int    `xml:"OriginalArticleType"  json:"OriginalArticleType"`
				CanReprint            int    `xml:"CanReprint"  json:"CanReprint"`
				NeedReplaceContent    int    `xml:"NeedReplaceContent"  json:"NeedReplaceContent"`
				NeedShowReprintSource int    `xml:"NeedShowReprintSource"  json:"NeedShowReprintSource"`
			} `xml:"item" json:"item"`
		} `xml:"ResultList,omitempty" json:"ResultList"`
		CheckState int `xml:"CheckState" json:"CheckState"`
	} `xml:"CopyrightCheckResult" json:"CopyrightCheckResult,omitempty"`
	ArticleUrlResult *struct {
		Count      int `xml:"Count"  json:"Count"`
		ResultList []*struct {
			Item *struct {
				ArticleIdx int    `xml:"ArticleIdx"  json:"ArticleIdx"`
				ArticleUrl string `xml:"ArticleUrl"  json:"ArticleUrl"`
			} `xml:"item" json:"item"`
		} `xml:"ResultList"  json:"ResultList"`
	} `xml:"ArticleUrlResult" json:"ArticleUrlResult,omitempty"`

	// shakearound
	ChosenBeacon *struct {
		UUID     string  `xml:"Uuid"     json:"Uuid"`
		Major    int     `xml:"Major"    json:"Major"`
		Minor    int     `xml:"Minor"    json:"Minor"`
		Distance float64 `xml:"Distance" json:"Distance"`
	} `xml:"ChosenBeacon,omitempty" json:"ChosenBeacon,omitempty"`
	AroundBeacons []struct {
		UUID     string  `xml:"Uuid"     json:"Uuid"`
		Major    int     `xml:"Major"    json:"Major"`
		Minor    int     `xml:"Minor"    json:"Minor"`
		Distance float64 `xml:"Distance" json:"Distance"`
	} `xml:"AroundBeacons>AroundBeacon,omitempty" json:"AroundBeacons,omitempty"`
	// contains filtered or unexported fields
}

微信服务器推送过来的消息(事件)的合集.

type MsgHeader

type MsgHeader struct {
	ToUserName   string  `xml:"ToUserName"   json:"ToUserName"`
	FromUserName string  `xml:"FromUserName" json:"FromUserName"`
	CreateTime   int64   `xml:"CreateTime"   json:"CreateTime"`
	MsgType      MsgType `xml:"MsgType"      json:"MsgType"`
}

微信服务器推送过来的消息(事件)的通用消息头.

type MsgType

type MsgType string

type MultipartFormField

type MultipartFormField struct {
	IsFile   bool
	Name     string
	FileName string
	Value    io.Reader
}

type ServeMux

type ServeMux struct {
	// contains filtered or unexported fields
}

ServeMux 是一个消息(事件)路由器, 同时也是一个 Handler 的实现.

NOTE: ServeMux 非并发安全, 如果需要并发安全的 Handler, 可以参考 ServeMux 实现一个.

func NewServeMux

func NewServeMux() *ServeMux

func (*ServeMux) DefaultEventHandle

func (mux *ServeMux) DefaultEventHandle(handlers ...Handler)

DefaultEventHandle 设置 handlers 以处理没有匹配到具体类型的 HandlerChain 的事件.

func (*ServeMux) DefaultEventHandleFunc

func (mux *ServeMux) DefaultEventHandleFunc(handlers ...func(*Context))

DefaultEventHandleFunc 设置 handlers 以处理没有匹配到具体类型的 HandlerChain 的事件.

func (*ServeMux) DefaultMsgHandle

func (mux *ServeMux) DefaultMsgHandle(handlers ...Handler)

DefaultMsgHandle 设置 handlers 以处理没有匹配到具体类型的 HandlerChain 的消息.

func (*ServeMux) DefaultMsgHandleFunc

func (mux *ServeMux) DefaultMsgHandleFunc(handlers ...func(*Context))

DefaultMsgHandleFunc 设置 handlers 以处理没有匹配到具体类型的 HandlerChain 的消息.

func (*ServeMux) EventHandle

func (mux *ServeMux) EventHandle(eventType EventType, handlers ...Handler)

EventHandle 设置 handlers 以处理特定类型的事件.

func (*ServeMux) EventHandleFunc

func (mux *ServeMux) EventHandleFunc(eventType EventType, handlers ...func(*Context))

EventHandleFunc 设置 handlers 以处理特定类型的事件.

func (*ServeMux) MsgHandle

func (mux *ServeMux) MsgHandle(msgType MsgType, handlers ...Handler)

MsgHandle 设置 handlers 以处理特定类型的消息.

func (*ServeMux) MsgHandleFunc

func (mux *ServeMux) MsgHandleFunc(msgType MsgType, handlers ...func(*Context))

MsgHandleFunc 设置 handlers 以处理特定类型的消息.

func (*ServeMux) ServeMsg

func (mux *ServeMux) ServeMsg(ctx *Context)

ServeMsg 实现 Handler 接口.

func (*ServeMux) Use

func (mux *ServeMux) Use(middlewares ...Handler)

Use 注册(新增) middlewares 使其在所有消息(事件)的 Handler 之前处理该处理消息(事件).

func (*ServeMux) UseForEvent

func (mux *ServeMux) UseForEvent(middlewares ...Handler)

UseForEvent 注册(新增) middlewares 使其在所有事件的 Handler 之前处理该处理事件.

func (*ServeMux) UseForMsg

func (mux *ServeMux) UseForMsg(middlewares ...Handler)

UseForMsg 注册(新增) middlewares 使其在所有消息的 Handler 之前处理该处理消息.

func (*ServeMux) UseFunc

func (mux *ServeMux) UseFunc(middlewares ...func(*Context))

UseFunc 注册(新增) middlewares 使其在所有消息(事件)的 Handler 之前处理该处理消息(事件).

func (*ServeMux) UseFuncForEvent

func (mux *ServeMux) UseFuncForEvent(middlewares ...func(*Context))

UseFuncForEvent 注册(新增) middlewares 使其在所有事件的 Handler 之前处理该处理事件.

func (*ServeMux) UseFuncForMsg

func (mux *ServeMux) UseFuncForMsg(middlewares ...func(*Context))

UseFuncForMsg 注册(新增) middlewares 使其在所有消息的 Handler 之前处理该处理消息.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server 用于处理微信服务器的回调请求, 并发安全!

通常情况下一个 Server 实例用于处理一个公众号的消息(事件), 此时建议指定 oriId(原始ID) 和 appId(明文模式下无需指定) 用于约束消息(事件);
特殊情况下也可以一个 Server 实例用于处理多个公众号的消息(事件), 此时要求这些公众号的 token 是一样的, 并且 oriId 和 appId 必须设置为 "".

func NewServer

func NewServer(oriId, appId, token, base64AESKey string, handler Handler, errorHandler ErrorHandler) (srv *Server)

NewServer 创建一个新的 Server.

oriId:        可选; 公众号的原始ID(微信公众号管理后台查看), 如果设置了值则该Server只能处理 ToUserName 为该值的公众号的消息(事件);
appId:        可选; 公众号的AppId, 如果设置了值则安全模式时该Server只能处理 AppId 为该值的公众号的消息(事件);
token:        必须; 公众号用于验证签名的token;
base64AESKey: 可选; aes加密解密key, 43字节长(base64编码, 去掉了尾部的'='), 安全模式必须设置;
handler:      必须; 处理微信服务器推送过来的消息(事件)的Handler;
errorHandler: 可选; 用于处理Server在处理消息(事件)过程中产生的错误, 如果没有设置则默认使用 DefaultErrorHandler.

func (*Server) AppId

func (srv *Server) AppId() string

func (*Server) OriId

func (srv *Server) OriId() string

func (*Server) ServeHTTP

func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request, query url.Values)

ServeHTTP 处理微信服务器的回调请求, query 参数可以为 nil.

Example
package main

import (
	"net/http"

	"github.com/chanxuehong/wechat/mp/core"
)

func main() {
	mux := core.NewServeMux() // 创建 core.Handler, 也可以用自己实现的 core.Handler

	// 注册消息(事件)处理 Handler, 都不是必须的!
	{
		mux.UseFunc(func(ctx *core.Context) { // 注册中间件, 处理所有的消息(事件)
			// TODO: 中间件处理逻辑
		})
		mux.UseFuncForMsg(func(ctx *core.Context) { // 注册中间件, 处理所有的消息
			// TODO: 中间件处理逻辑
		})
		mux.UseFuncForEvent(func(ctx *core.Context) { // 注册中间件, 处理所有的事件
			// TODO: 中间件处理逻辑
		})

		mux.DefaultMsgHandleFunc(func(ctx *core.Context) { // 设置默认消息处理 Handler
			// TODO: 消息处理逻辑
		})
		mux.DefaultEventHandleFunc(func(ctx *core.Context) { // 设置默认事件处理 Handler
			// TODO: 事件处理逻辑
		})

		mux.MsgHandleFunc("{MsgType}", func(ctx *core.Context) { // 设置具体类型的消息处理 Handler
			// TODO: 消息处理逻辑
		})
		mux.EventHandleFunc("{EventType}", func(ctx *core.Context) { // 设置具体类型的事件处理 Handler
			// TODO: 事件处理逻辑
		})
	}

	// 创建 Server, 设置正确的参数.
	// 通常一个 Server 对应一个公众号, 当然一个 Server 也可以对应多个公众号, 这个时候 oriId 和 appId 都应该设置为空值!
	srv := core.NewServer("{oriId}", "{appId}", "{token}", "{base64AESKey}", mux, nil)

	// 在回调 URL 的 Handler 里处理消息(事件)
	http.HandleFunc("/wechat_callback", func(w http.ResponseWriter, r *http.Request) {
		srv.ServeHTTP(w, r, nil)
	})
}
Output:

func (*Server) SetAESKey

func (srv *Server) SetAESKey(base64AESKey string) (err error)

SetAESKey 设置aes加密解密key.

base64AESKey: aes加密解密key, 43字节长(base64编码, 去掉了尾部的'=').

func (*Server) SetToken

func (srv *Server) SetToken(token string) (err error)

SetToken 设置签名token.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL