first commit
This commit is contained in:
406
pkg/mux/context.go
Normal file
406
pkg/mux/context.go
Normal file
@ -0,0 +1,406 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
stdCtx "context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/errno"
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/trace"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type HandlerFunc func(c Context)
|
||||
|
||||
type Trace = trace.T
|
||||
|
||||
const (
|
||||
_Alias = "_alias_"
|
||||
_TraceName = "_trace_"
|
||||
_LoggerName = "_logger_"
|
||||
_BodyName = "_body_"
|
||||
_PayloadName = "_payload_"
|
||||
_GraphPayloadName = "_graph_payload_"
|
||||
_AbortErrorName = "_abort_error_"
|
||||
_Auth = "_auth_"
|
||||
)
|
||||
|
||||
var contextPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return new(context)
|
||||
},
|
||||
}
|
||||
|
||||
func newContext(ctx *gin.Context) Context {
|
||||
getContext := contextPool.Get().(*context)
|
||||
getContext.ctx = ctx
|
||||
return getContext
|
||||
}
|
||||
|
||||
func releaseContext(ctx Context) {
|
||||
c := ctx.(*context)
|
||||
c.ctx = nil
|
||||
contextPool.Put(c)
|
||||
}
|
||||
|
||||
var _ Context = (*context)(nil)
|
||||
|
||||
type Context interface {
|
||||
init()
|
||||
|
||||
Context() *gin.Context
|
||||
|
||||
// ShouldBindQuery 反序列化 query
|
||||
// tag: `form:"xxx"` (注:不要写成 query)
|
||||
ShouldBindQuery(obj any) error
|
||||
|
||||
// ShouldBindPostForm 反序列化 x-www-from-urlencoded
|
||||
// tag: `form:"xxx"`
|
||||
ShouldBindPostForm(obj any) error
|
||||
|
||||
// ShouldBindForm 同时反序列化 form-data;
|
||||
// tag: `form:"xxx"`
|
||||
ShouldBindForm(obj any) error
|
||||
|
||||
// ShouldBindJSON 反序列化 post-json
|
||||
// tag: `json:"xxx"`
|
||||
ShouldBindJSON(obj any) error
|
||||
|
||||
// ShouldBindURI 反序列化 path 参数(如路由路径为 /user/:name)
|
||||
// tag: `uri:"xxx"`
|
||||
ShouldBindURI(obj any) error
|
||||
|
||||
// Redirect 重定向
|
||||
Redirect(code int, location string)
|
||||
|
||||
// Trace 获取 Trace 对象
|
||||
Trace() Trace
|
||||
setTrace(trace Trace)
|
||||
disableTrace()
|
||||
|
||||
// Logger 获取 Logger 对象
|
||||
Logger() *zap.Logger
|
||||
setLogger(logger *zap.Logger)
|
||||
|
||||
// Payload 正确返回
|
||||
Payload(payload any)
|
||||
getPayload() any
|
||||
|
||||
// GraphPayload GraphQL返回值 与 api 返回结构不同
|
||||
GraphPayload(payload any)
|
||||
getGraphPayload() any
|
||||
|
||||
// HTML 返回界面
|
||||
HTML(name string, obj any)
|
||||
|
||||
// AbortWithError 错误返回
|
||||
AbortWithError(err errno.Error)
|
||||
abortError() errno.Error
|
||||
|
||||
// Header 获取 Header 对象
|
||||
Header() http.Header
|
||||
// GetHeader 获取 Header
|
||||
GetHeader(key string) string
|
||||
// SetHeader 设置 Header
|
||||
SetHeader(key, value string)
|
||||
|
||||
Auth() any
|
||||
SetAuth(auth any)
|
||||
|
||||
// Authorization 获取请求认证信息
|
||||
Authorization() string
|
||||
|
||||
// Platform 平台标识
|
||||
Platform() string
|
||||
|
||||
// Alias 设置路由别名 for metrics uri
|
||||
Alias() string
|
||||
setAlias(path string)
|
||||
|
||||
// RequestInputParams 获取所有参数
|
||||
RequestInputParams() url.Values
|
||||
// RequestQueryParams 获取 Query 参数
|
||||
RequestQueryParams() url.Values
|
||||
// RequestPostFormParams 获取 PostForm 参数
|
||||
RequestPostFormParams() url.Values
|
||||
// Request 获取 Request 对象
|
||||
Request() *http.Request
|
||||
// RawData 获取 Request.Body
|
||||
RawData() []byte
|
||||
// Method 获取 Request.Method
|
||||
Method() string
|
||||
// Host 获取 Request.Host
|
||||
Host() string
|
||||
// Path 获取 请求的路径 Request.URL.Path (不附带 querystring)
|
||||
Path() string
|
||||
// URI 获取 unescape 后的 Request.URL.RequestURI()
|
||||
URI() string
|
||||
// RequestContext 获取请求的 context (当 client 关闭后,会自动 canceled)
|
||||
RequestContext() StdContext
|
||||
// ResponseWriter 获取 ResponseWriter 对象
|
||||
ResponseWriter() gin.ResponseWriter
|
||||
}
|
||||
|
||||
type context struct {
|
||||
ctx *gin.Context
|
||||
}
|
||||
|
||||
type StdContext struct {
|
||||
stdCtx.Context
|
||||
Trace
|
||||
*zap.Logger
|
||||
}
|
||||
|
||||
func (c *context) init() {
|
||||
body, err := c.ctx.GetRawData()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.ctx.Set(_BodyName, body) // cache body是为了trace使用
|
||||
c.ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body)) // re-construct req body
|
||||
}
|
||||
|
||||
func (c *context) Context() *gin.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
// ShouldBindQuery 反序列化querystring
|
||||
// tag: `form:"xxx"` (注:不要写成query)
|
||||
func (c *context) ShouldBindQuery(obj any) error {
|
||||
return c.ctx.ShouldBindWith(obj, binding.Query)
|
||||
}
|
||||
|
||||
// ShouldBindPostForm 反序列化 postform (querystring 会被忽略)
|
||||
// tag: `form:"xxx"`
|
||||
func (c *context) ShouldBindPostForm(obj any) error {
|
||||
return c.ctx.ShouldBindWith(obj, binding.FormPost)
|
||||
}
|
||||
|
||||
// ShouldBindForm 同时反序列化querystring和postform;
|
||||
// 当querystring和postform存在相同字段时,postform优先使用。
|
||||
// tag: `form:"xxx"`
|
||||
func (c *context) ShouldBindForm(obj any) error {
|
||||
return c.ctx.ShouldBindWith(obj, binding.Form)
|
||||
}
|
||||
|
||||
// ShouldBindJSON 反序列化postjson
|
||||
// tag: `json:"xxx"`
|
||||
func (c *context) ShouldBindJSON(obj any) error {
|
||||
return c.ctx.ShouldBindWith(obj, binding.JSON)
|
||||
}
|
||||
|
||||
// ShouldBindURI 反序列化path参数(如路由路径为 /user/:name)
|
||||
// tag: `uri:"xxx"`
|
||||
func (c *context) ShouldBindURI(obj any) error {
|
||||
return c.ctx.ShouldBindUri(obj)
|
||||
}
|
||||
|
||||
// Redirect 重定向
|
||||
func (c *context) Redirect(code int, location string) {
|
||||
c.ctx.Redirect(code, location)
|
||||
}
|
||||
|
||||
func (c *context) Trace() Trace {
|
||||
t, ok := c.ctx.Get(_TraceName)
|
||||
if !ok || t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.(Trace)
|
||||
}
|
||||
|
||||
func (c *context) setTrace(trace Trace) {
|
||||
c.ctx.Set(_TraceName, trace)
|
||||
}
|
||||
|
||||
func (c *context) disableTrace() {
|
||||
c.setTrace(nil)
|
||||
}
|
||||
|
||||
func (c *context) Logger() *zap.Logger {
|
||||
logger, ok := c.ctx.Get(_LoggerName)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return logger.(*zap.Logger)
|
||||
}
|
||||
|
||||
func (c *context) setLogger(logger *zap.Logger) {
|
||||
c.ctx.Set(_LoggerName, logger)
|
||||
}
|
||||
|
||||
func (c *context) getPayload() any {
|
||||
if payload, ok := c.ctx.Get(_PayloadName); ok != false {
|
||||
return payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Payload(payload any) {
|
||||
c.ctx.Set(_PayloadName, payload)
|
||||
}
|
||||
|
||||
func (c *context) getGraphPayload() any {
|
||||
if payload, ok := c.ctx.Get(_GraphPayloadName); ok != false {
|
||||
return payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) GraphPayload(payload any) {
|
||||
c.ctx.Set(_GraphPayloadName, payload)
|
||||
}
|
||||
|
||||
func (c *context) HTML(name string, obj any) {
|
||||
c.ctx.HTML(200, name+".html", obj)
|
||||
}
|
||||
|
||||
func (c *context) Header() http.Header {
|
||||
header := c.ctx.Request.Header
|
||||
|
||||
clone := make(http.Header, len(header))
|
||||
for k, v := range header {
|
||||
value := make([]string, len(v))
|
||||
copy(value, v)
|
||||
|
||||
clone[k] = value
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (c *context) GetHeader(key string) string {
|
||||
return c.ctx.GetHeader(key)
|
||||
}
|
||||
|
||||
func (c *context) SetHeader(key, value string) {
|
||||
c.ctx.Header(key, value)
|
||||
}
|
||||
|
||||
func (c *context) Auth() any {
|
||||
val, ok := c.ctx.Get(_Auth)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (c *context) SetAuth(auth any) {
|
||||
c.ctx.Set(_Auth, auth)
|
||||
}
|
||||
|
||||
func (c *context) Authorization() string {
|
||||
return c.ctx.GetHeader("Authorization")
|
||||
}
|
||||
|
||||
func (c *context) Platform() string {
|
||||
return c.ctx.GetHeader("X-Platform")
|
||||
}
|
||||
|
||||
func (c *context) AbortWithError(err errno.Error) {
|
||||
if err != nil {
|
||||
httpCode := err.GetHttpCode()
|
||||
if httpCode == 0 {
|
||||
httpCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
c.ctx.AbortWithStatus(httpCode)
|
||||
c.ctx.Set(_AbortErrorName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) abortError() errno.Error {
|
||||
err, _ := c.ctx.Get(_AbortErrorName)
|
||||
return err.(errno.Error)
|
||||
}
|
||||
|
||||
func (c *context) Alias() string {
|
||||
path, ok := c.ctx.Get(_Alias)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path.(string)
|
||||
}
|
||||
|
||||
func (c *context) setAlias(path string) {
|
||||
if path = strings.TrimSpace(path); path != "" {
|
||||
c.ctx.Set(_Alias, path)
|
||||
}
|
||||
}
|
||||
|
||||
// RequestInputParams 获取所有参数
|
||||
func (c *context) RequestInputParams() url.Values {
|
||||
_ = c.ctx.Request.ParseForm()
|
||||
return c.ctx.Request.Form
|
||||
}
|
||||
|
||||
// RequestQueryParams 获取Query参数
|
||||
func (c *context) RequestQueryParams() url.Values {
|
||||
query, _ := url.ParseQuery(c.ctx.Request.URL.RawQuery)
|
||||
return query
|
||||
}
|
||||
|
||||
// RequestPostFormParams 获取 PostForm 参数
|
||||
func (c *context) RequestPostFormParams() url.Values {
|
||||
_ = c.ctx.Request.ParseForm()
|
||||
return c.ctx.Request.PostForm
|
||||
}
|
||||
|
||||
// Request 获取 Request
|
||||
func (c *context) Request() *http.Request {
|
||||
return c.ctx.Request
|
||||
}
|
||||
|
||||
func (c *context) RawData() []byte {
|
||||
body, ok := c.ctx.Get(_BodyName)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return body.([]byte)
|
||||
}
|
||||
|
||||
// Method 请求的method
|
||||
func (c *context) Method() string {
|
||||
return c.ctx.Request.Method
|
||||
}
|
||||
|
||||
// Host 请求的host
|
||||
func (c *context) Host() string {
|
||||
return c.ctx.Request.Host
|
||||
}
|
||||
|
||||
// Path 请求的路径(不附带querystring)
|
||||
func (c *context) Path() string {
|
||||
return c.ctx.Request.URL.Path
|
||||
}
|
||||
|
||||
// URI unescape后的uri
|
||||
func (c *context) URI() string {
|
||||
uri, _ := url.QueryUnescape(c.ctx.Request.URL.RequestURI())
|
||||
return uri
|
||||
}
|
||||
|
||||
// RequestContext (包装 Trace + Logger) 获取请求的 context (当client关闭后,会自动canceled)
|
||||
func (c *context) RequestContext() StdContext {
|
||||
return StdContext{
|
||||
//c.ctx.Request.Context(),
|
||||
stdCtx.Background(),
|
||||
c.Trace(),
|
||||
c.Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseWriter 获取 ResponseWriter
|
||||
func (c *context) ResponseWriter() gin.ResponseWriter {
|
||||
return c.ctx.Writer
|
||||
}
|
466
pkg/mux/core.go
Normal file
466
pkg/mux/core.go
Normal file
@ -0,0 +1,466 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/color"
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/env"
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/errno"
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/limiter"
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/trace"
|
||||
"git.bvbej.com/bvbej/base-golang/pkg/validator"
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
cors "github.com/rs/cors/wrapper/gin"
|
||||
"go.uber.org/multierr"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Option func(*option)
|
||||
|
||||
type option struct {
|
||||
enableCors bool
|
||||
enablePProf bool
|
||||
enablePrometheus bool
|
||||
enableOpenBrowser string
|
||||
staticDirs []string
|
||||
panicNotify OnPanicNotify
|
||||
recordMetrics RecordMetrics
|
||||
rateLimiter limiter.RateLimiter
|
||||
}
|
||||
|
||||
const SuccessCode = 0
|
||||
|
||||
type Failure struct {
|
||||
ResultCode int `json:"result_code"` // 业务码
|
||||
ResultInfo string `json:"result_info"` // 描述信息
|
||||
|
||||
}
|
||||
|
||||
type Success struct {
|
||||
ResultCode int `json:"result_code"` // 业务码
|
||||
ResultData any `json:"result_data"` //返回数据
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// OnPanicNotify 发生panic时通知用
|
||||
type OnPanicNotify func(ctx Context, err any, stackInfo string)
|
||||
|
||||
// RecordMetrics 记录prometheus指标用
|
||||
// 如果使用AliasForRecordMetrics配置了别名,uri将被替换为别名。
|
||||
type RecordMetrics func(method, uri string, success bool, costSeconds float64)
|
||||
|
||||
// DisableTrace 禁用追踪链
|
||||
func DisableTrace(ctx Context) {
|
||||
ctx.disableTrace()
|
||||
}
|
||||
|
||||
// WithPanicNotify 设置panic时的通知回调
|
||||
func WithPanicNotify(notify OnPanicNotify) Option {
|
||||
return func(opt *option) {
|
||||
opt.panicNotify = notify
|
||||
fmt.Println(color.Green("* [register panic notify]"))
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecordMetrics 设置记录prometheus记录指标回调
|
||||
func WithRecordMetrics(record RecordMetrics) Option {
|
||||
return func(opt *option) {
|
||||
opt.recordMetrics = record
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableCors 开启CORS
|
||||
func WithEnableCors() Option {
|
||||
return func(opt *option) {
|
||||
opt.enableCors = true
|
||||
fmt.Println(color.Green("* [register cors]"))
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableRate 开启限流
|
||||
func WithEnableRate(limit rate.Limit, burst int) Option {
|
||||
return func(opt *option) {
|
||||
opt.rateLimiter = limiter.NewRateLimiter(limit, burst)
|
||||
fmt.Println(color.Green("* [register rate]"))
|
||||
}
|
||||
}
|
||||
|
||||
// WithStaticDir 设置静态文件目录
|
||||
func WithStaticDir(dirs []string) Option {
|
||||
return func(opt *option) {
|
||||
opt.staticDirs = dirs
|
||||
fmt.Println(color.Green("* [register rate]"))
|
||||
}
|
||||
}
|
||||
|
||||
// AliasForRecordMetrics 对请求uri起个别名,用于prometheus记录指标。
|
||||
// 如:Get /user/:username 这样的uri,因为username会有非常多的情况,这样记录prometheus指标会非常的不有好。
|
||||
func AliasForRecordMetrics(path string) HandlerFunc {
|
||||
return func(ctx Context) {
|
||||
ctx.setAlias(path)
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// RouterGroup 包装gin的RouterGroup
|
||||
type RouterGroup interface {
|
||||
Group(string, ...HandlerFunc) RouterGroup
|
||||
IRoutes
|
||||
}
|
||||
|
||||
var _ IRoutes = (*router)(nil)
|
||||
|
||||
// IRoutes 包装gin的IRoutes
|
||||
type IRoutes interface {
|
||||
Any(string, ...HandlerFunc)
|
||||
GET(string, ...HandlerFunc)
|
||||
POST(string, ...HandlerFunc)
|
||||
DELETE(string, ...HandlerFunc)
|
||||
PATCH(string, ...HandlerFunc)
|
||||
PUT(string, ...HandlerFunc)
|
||||
OPTIONS(string, ...HandlerFunc)
|
||||
HEAD(string, ...HandlerFunc)
|
||||
}
|
||||
|
||||
type router struct {
|
||||
group *gin.RouterGroup
|
||||
}
|
||||
|
||||
func (r *router) Group(relativePath string, handlers ...HandlerFunc) RouterGroup {
|
||||
group := r.group.Group(relativePath, wrapHandlers(handlers...)...)
|
||||
return &router{group: group}
|
||||
}
|
||||
|
||||
func (r *router) Any(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.Any(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) GET(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.GET(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) POST(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.POST(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) DELETE(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.DELETE(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) PATCH(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.PATCH(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) PUT(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.PUT(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) OPTIONS(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.OPTIONS(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func (r *router) HEAD(relativePath string, handlers ...HandlerFunc) {
|
||||
r.group.HEAD(relativePath, wrapHandlers(handlers...)...)
|
||||
}
|
||||
|
||||
func wrapHandlers(handlers ...HandlerFunc) []gin.HandlerFunc {
|
||||
list := make([]gin.HandlerFunc, len(handlers))
|
||||
for i, handler := range handlers {
|
||||
fn := handler
|
||||
list[i] = func(c *gin.Context) {
|
||||
ctx := newContext(c)
|
||||
defer releaseContext(ctx)
|
||||
|
||||
fn(ctx)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var _ Mux = (*mux)(nil)
|
||||
|
||||
type Mux interface {
|
||||
http.Handler
|
||||
Group(relativePath string, handlers ...HandlerFunc) RouterGroup
|
||||
Routes() gin.RoutesInfo
|
||||
HandlerFunc(relativePath string, handlerFunc gin.HandlerFunc)
|
||||
}
|
||||
|
||||
type mux struct {
|
||||
engine *gin.Engine
|
||||
}
|
||||
|
||||
func (m *mux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
m.engine.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func (m *mux) Group(relativePath string, handlers ...HandlerFunc) RouterGroup {
|
||||
return &router{
|
||||
group: m.engine.Group(relativePath, wrapHandlers(handlers...)...),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mux) Routes() gin.RoutesInfo {
|
||||
return m.engine.Routes()
|
||||
}
|
||||
|
||||
func (m *mux) HandlerFunc(relativePath string, handlerFunc gin.HandlerFunc) {
|
||||
m.engine.GET(relativePath, handlerFunc)
|
||||
}
|
||||
|
||||
func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
if logger == nil {
|
||||
return nil, errors.New("logger required")
|
||||
}
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
binding.Validator = validator.Validator
|
||||
newMux := &mux{
|
||||
engine: gin.New(),
|
||||
}
|
||||
|
||||
fmt.Println(color.Green(fmt.Sprintf("* [register env %s]", env.Active().Value())))
|
||||
|
||||
// withoutLogPaths 这些请求,默认不记录日志
|
||||
withoutTracePaths := map[string]bool{
|
||||
"/metrics": true,
|
||||
"/favicon.ico": true,
|
||||
"/system/health": true,
|
||||
}
|
||||
|
||||
opt := new(option)
|
||||
for _, f := range options {
|
||||
f(opt)
|
||||
}
|
||||
|
||||
if opt.enablePProf {
|
||||
pprof.Register(newMux.engine)
|
||||
fmt.Println(color.Green("* [register pprof]"))
|
||||
}
|
||||
|
||||
if opt.enablePrometheus {
|
||||
newMux.engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
||||
fmt.Println(color.Green("* [register prometheus]"))
|
||||
}
|
||||
|
||||
if opt.enableCors {
|
||||
newMux.engine.Use(cors.AllowAll())
|
||||
}
|
||||
|
||||
if opt.staticDirs != nil {
|
||||
for _, dir := range opt.staticDirs {
|
||||
newMux.engine.StaticFS(dir, gin.Dir(dir, false))
|
||||
}
|
||||
}
|
||||
|
||||
// recover两次,防止处理时发生panic,尤其是在OnPanicNotify中。
|
||||
newMux.engine.Use(func(ctx *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", string(debug.Stack())))
|
||||
}
|
||||
}()
|
||||
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
newMux.engine.Use(func(ctx *gin.Context) {
|
||||
ts := time.Now()
|
||||
|
||||
newCtx := newContext(ctx)
|
||||
defer releaseContext(newCtx)
|
||||
|
||||
newCtx.init()
|
||||
newCtx.setLogger(logger)
|
||||
|
||||
if !withoutTracePaths[ctx.Request.URL.Path] {
|
||||
if traceId := newCtx.GetHeader(trace.Header); traceId != "" {
|
||||
newCtx.setTrace(trace.New(traceId))
|
||||
} else {
|
||||
newCtx.setTrace(trace.New(""))
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
stackInfo := string(debug.Stack())
|
||||
logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo))
|
||||
newCtx.AbortWithError(errno.NewError(
|
||||
http.StatusInternalServerError,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError)),
|
||||
)
|
||||
|
||||
if notify := opt.panicNotify; notify != nil {
|
||||
notify(newCtx, err, stackInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Writer.Status() == http.StatusNotFound {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
response any
|
||||
businessCode int
|
||||
businessCodeMsg string
|
||||
abortErr error
|
||||
graphResponse any
|
||||
)
|
||||
|
||||
if ctx.IsAborted() {
|
||||
for i := range ctx.Errors { // gin error
|
||||
multierr.AppendInto(&abortErr, ctx.Errors[i])
|
||||
}
|
||||
|
||||
if err := newCtx.abortError(); err != nil { // customer err
|
||||
multierr.AppendInto(&abortErr, err.GetErr())
|
||||
response = err
|
||||
businessCode = err.GetBusinessCode()
|
||||
businessCodeMsg = err.GetMsg()
|
||||
|
||||
if x := newCtx.Trace(); x != nil {
|
||||
newCtx.SetHeader(trace.Header, x.ID())
|
||||
}
|
||||
|
||||
ctx.JSON(err.GetHttpCode(), &Failure{
|
||||
ResultCode: businessCode,
|
||||
ResultInfo: businessCodeMsg,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
response = newCtx.getPayload()
|
||||
if response != nil {
|
||||
if x := newCtx.Trace(); x != nil {
|
||||
newCtx.SetHeader(trace.Header, x.ID())
|
||||
}
|
||||
ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
|
||||
graphResponse = newCtx.getGraphPayload()
|
||||
|
||||
if opt.recordMetrics != nil {
|
||||
uri := newCtx.Path()
|
||||
if alias := newCtx.Alias(); alias != "" {
|
||||
uri = alias
|
||||
}
|
||||
|
||||
opt.recordMetrics(
|
||||
newCtx.Method(),
|
||||
uri,
|
||||
!ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK,
|
||||
time.Since(ts).Seconds(),
|
||||
)
|
||||
}
|
||||
|
||||
var t *trace.Trace
|
||||
if x := newCtx.Trace(); x != nil {
|
||||
t = x.(*trace.Trace)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
decodedURL, _ := url.QueryUnescape(ctx.Request.URL.RequestURI())
|
||||
|
||||
t.WithRequest(&trace.Request{
|
||||
TTL: "un-limit",
|
||||
Method: ctx.Request.Method,
|
||||
DecodedURL: decodedURL,
|
||||
Header: ctx.Request.Header,
|
||||
Body: string(newCtx.RawData()),
|
||||
})
|
||||
|
||||
var responseBody any
|
||||
|
||||
if response != nil {
|
||||
responseBody = response
|
||||
}
|
||||
|
||||
if graphResponse != nil {
|
||||
responseBody = graphResponse
|
||||
}
|
||||
|
||||
t.WithResponse(&trace.Response{
|
||||
Header: ctx.Writer.Header(),
|
||||
HttpCode: ctx.Writer.Status(),
|
||||
HttpCodeMsg: http.StatusText(ctx.Writer.Status()),
|
||||
BusinessCode: businessCode,
|
||||
BusinessCodeMsg: businessCodeMsg,
|
||||
Body: responseBody,
|
||||
CostSeconds: time.Since(ts).Seconds(),
|
||||
})
|
||||
|
||||
t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
|
||||
t.CostSeconds = time.Since(ts).Seconds()
|
||||
|
||||
logger.Info("core-interceptor",
|
||||
zap.Any("method", ctx.Request.Method),
|
||||
zap.Any("path", decodedURL),
|
||||
zap.Any("http_code", ctx.Writer.Status()),
|
||||
zap.Any("business_code", businessCode),
|
||||
zap.Any("success", t.Success),
|
||||
zap.Any("cost_seconds", t.CostSeconds),
|
||||
zap.Any("trace_id", t.Identifier),
|
||||
zap.Any("trace_info", t),
|
||||
zap.Error(abortErr),
|
||||
)
|
||||
}()
|
||||
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
if opt.rateLimiter != nil {
|
||||
newMux.engine.Use(func(ctx *gin.Context) {
|
||||
newCtx := newContext(ctx)
|
||||
defer releaseContext(newCtx)
|
||||
|
||||
if !opt.rateLimiter.Allow(ctx.ClientIP()) {
|
||||
newCtx.AbortWithError(errno.NewError(
|
||||
http.StatusTooManyRequests,
|
||||
http.StatusTooManyRequests,
|
||||
http.StatusText(http.StatusTooManyRequests)),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
})
|
||||
}
|
||||
|
||||
newMux.engine.NoMethod(wrapHandlers(DisableTrace)...)
|
||||
newMux.engine.NoRoute(wrapHandlers(DisableTrace)...)
|
||||
system := newMux.Group("/system")
|
||||
{
|
||||
// 健康检查
|
||||
system.GET("/health", func(ctx Context) {
|
||||
resp := &struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Environment string `json:"environment"`
|
||||
Host string `json:"host"`
|
||||
Status string `json:"status"`
|
||||
}{
|
||||
Timestamp: time.Now(),
|
||||
Environment: env.Active().Value(),
|
||||
Host: ctx.Host(),
|
||||
Status: "ok",
|
||||
}
|
||||
ctx.Payload(resp)
|
||||
})
|
||||
}
|
||||
|
||||
return newMux, nil
|
||||
}
|
Reference in New Issue
Block a user