core.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. package mux
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "runtime/debug"
  8. "time"
  9. "git.bvbej.com/bvbej/base-golang/pkg/color"
  10. "git.bvbej.com/bvbej/base-golang/pkg/env"
  11. "git.bvbej.com/bvbej/base-golang/pkg/errno"
  12. "git.bvbej.com/bvbej/base-golang/pkg/limiter"
  13. "git.bvbej.com/bvbej/base-golang/pkg/trace"
  14. "git.bvbej.com/bvbej/base-golang/pkg/validator"
  15. "github.com/gin-contrib/pprof"
  16. "github.com/gin-gonic/gin"
  17. "github.com/gin-gonic/gin/binding"
  18. "github.com/prometheus/client_golang/prometheus/promhttp"
  19. cors "github.com/rs/cors/wrapper/gin"
  20. "go.uber.org/multierr"
  21. "go.uber.org/zap"
  22. "golang.org/x/time/rate"
  23. )
  24. type Option func(*option)
  25. type option struct {
  26. enableCors bool
  27. enablePProf bool
  28. enablePrometheus bool
  29. enableOpenBrowser string
  30. staticDirs []string
  31. panicNotify OnPanicNotify
  32. recordMetrics RecordMetrics
  33. rateLimiter limiter.RateLimiter
  34. }
  35. const SuccessCode = 0
  36. type Failure struct {
  37. ResultCode int `json:"result_code"` // 业务码
  38. ResultInfo string `json:"result_info"` // 描述信息
  39. }
  40. type Success struct {
  41. ResultCode int `json:"result_code"` // 业务码
  42. ResultData any `json:"result_data"` //返回数据
  43. }
  44. /******************************************************************************/
  45. // OnPanicNotify 发生panic时通知用
  46. type OnPanicNotify func(ctx Context, err any, stackInfo string)
  47. // RecordMetrics 记录prometheus指标用
  48. // 如果使用AliasForRecordMetrics配置了别名,uri将被替换为别名。
  49. type RecordMetrics func(method, uri string, success bool, costSeconds float64)
  50. // DisableTrace 禁用追踪链
  51. func DisableTrace(ctx Context) {
  52. ctx.disableTrace()
  53. }
  54. // WithPanicNotify 设置panic时的通知回调
  55. func WithPanicNotify(notify OnPanicNotify) Option {
  56. return func(opt *option) {
  57. opt.panicNotify = notify
  58. fmt.Println(color.Green("* [register panic notify]"))
  59. }
  60. }
  61. // WithRecordMetrics 设置记录prometheus记录指标回调
  62. func WithRecordMetrics(record RecordMetrics) Option {
  63. return func(opt *option) {
  64. opt.recordMetrics = record
  65. }
  66. }
  67. // WithEnableCors 开启CORS
  68. func WithEnableCors() Option {
  69. return func(opt *option) {
  70. opt.enableCors = true
  71. fmt.Println(color.Green("* [register cors]"))
  72. }
  73. }
  74. // WithEnableRate 开启限流
  75. func WithEnableRate(limit rate.Limit, burst int) Option {
  76. return func(opt *option) {
  77. opt.rateLimiter = limiter.NewRateLimiter(limit, burst)
  78. fmt.Println(color.Green("* [register rate]"))
  79. }
  80. }
  81. // WithStaticDir 设置静态文件目录
  82. func WithStaticDir(dirs []string) Option {
  83. return func(opt *option) {
  84. opt.staticDirs = dirs
  85. fmt.Println(color.Green("* [register rate]"))
  86. }
  87. }
  88. // AliasForRecordMetrics 对请求uri起个别名,用于prometheus记录指标。
  89. // 如:Get /user/:username 这样的uri,因为username会有非常多的情况,这样记录prometheus指标会非常的不有好。
  90. func AliasForRecordMetrics(path string) HandlerFunc {
  91. return func(ctx Context) {
  92. ctx.setAlias(path)
  93. }
  94. }
  95. /******************************************************************************/
  96. // RouterGroup 包装gin的RouterGroup
  97. type RouterGroup interface {
  98. Group(string, ...HandlerFunc) RouterGroup
  99. IRoutes
  100. }
  101. var _ IRoutes = (*router)(nil)
  102. // IRoutes 包装gin的IRoutes
  103. type IRoutes interface {
  104. Any(string, ...HandlerFunc)
  105. GET(string, ...HandlerFunc)
  106. POST(string, ...HandlerFunc)
  107. DELETE(string, ...HandlerFunc)
  108. PATCH(string, ...HandlerFunc)
  109. PUT(string, ...HandlerFunc)
  110. OPTIONS(string, ...HandlerFunc)
  111. HEAD(string, ...HandlerFunc)
  112. }
  113. type router struct {
  114. group *gin.RouterGroup
  115. }
  116. func (r *router) Group(relativePath string, handlers ...HandlerFunc) RouterGroup {
  117. group := r.group.Group(relativePath, wrapHandlers(handlers...)...)
  118. return &router{group: group}
  119. }
  120. func (r *router) Any(relativePath string, handlers ...HandlerFunc) {
  121. r.group.Any(relativePath, wrapHandlers(handlers...)...)
  122. }
  123. func (r *router) GET(relativePath string, handlers ...HandlerFunc) {
  124. r.group.GET(relativePath, wrapHandlers(handlers...)...)
  125. }
  126. func (r *router) POST(relativePath string, handlers ...HandlerFunc) {
  127. r.group.POST(relativePath, wrapHandlers(handlers...)...)
  128. }
  129. func (r *router) DELETE(relativePath string, handlers ...HandlerFunc) {
  130. r.group.DELETE(relativePath, wrapHandlers(handlers...)...)
  131. }
  132. func (r *router) PATCH(relativePath string, handlers ...HandlerFunc) {
  133. r.group.PATCH(relativePath, wrapHandlers(handlers...)...)
  134. }
  135. func (r *router) PUT(relativePath string, handlers ...HandlerFunc) {
  136. r.group.PUT(relativePath, wrapHandlers(handlers...)...)
  137. }
  138. func (r *router) OPTIONS(relativePath string, handlers ...HandlerFunc) {
  139. r.group.OPTIONS(relativePath, wrapHandlers(handlers...)...)
  140. }
  141. func (r *router) HEAD(relativePath string, handlers ...HandlerFunc) {
  142. r.group.HEAD(relativePath, wrapHandlers(handlers...)...)
  143. }
  144. func wrapHandlers(handlers ...HandlerFunc) []gin.HandlerFunc {
  145. list := make([]gin.HandlerFunc, len(handlers))
  146. for i, handler := range handlers {
  147. fn := handler
  148. list[i] = func(c *gin.Context) {
  149. ctx := newContext(c)
  150. defer releaseContext(ctx)
  151. fn(ctx)
  152. }
  153. }
  154. return list
  155. }
  156. /******************************************************************************/
  157. var _ Mux = (*mux)(nil)
  158. type Mux interface {
  159. http.Handler
  160. Group(relativePath string, handlers ...HandlerFunc) RouterGroup
  161. Routes() gin.RoutesInfo
  162. HandlerFunc(relativePath string, handlerFunc gin.HandlerFunc)
  163. }
  164. type mux struct {
  165. engine *gin.Engine
  166. }
  167. func (m *mux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  168. m.engine.ServeHTTP(w, req)
  169. }
  170. func (m *mux) Group(relativePath string, handlers ...HandlerFunc) RouterGroup {
  171. return &router{
  172. group: m.engine.Group(relativePath, wrapHandlers(handlers...)...),
  173. }
  174. }
  175. func (m *mux) Routes() gin.RoutesInfo {
  176. return m.engine.Routes()
  177. }
  178. func (m *mux) HandlerFunc(relativePath string, handlerFunc gin.HandlerFunc) {
  179. m.engine.GET(relativePath, handlerFunc)
  180. }
  181. func New(logger *zap.Logger, options ...Option) (Mux, error) {
  182. if logger == nil {
  183. return nil, errors.New("logger required")
  184. }
  185. gin.SetMode(gin.ReleaseMode)
  186. binding.Validator = validator.Validator
  187. newMux := &mux{
  188. engine: gin.New(),
  189. }
  190. fmt.Println(color.Green(fmt.Sprintf("* [register env %s]", env.Active().Value())))
  191. // withoutLogPaths 这些请求,默认不记录日志
  192. withoutTracePaths := map[string]bool{
  193. "/metrics": true,
  194. "/favicon.ico": true,
  195. "/system/health": true,
  196. }
  197. opt := new(option)
  198. for _, f := range options {
  199. f(opt)
  200. }
  201. if opt.enablePProf {
  202. pprof.Register(newMux.engine)
  203. fmt.Println(color.Green("* [register pprof]"))
  204. }
  205. if opt.enablePrometheus {
  206. newMux.engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
  207. fmt.Println(color.Green("* [register prometheus]"))
  208. }
  209. if opt.enableCors {
  210. newMux.engine.Use(cors.AllowAll())
  211. }
  212. if opt.staticDirs != nil {
  213. for _, dir := range opt.staticDirs {
  214. newMux.engine.StaticFS(dir, gin.Dir(dir, false))
  215. }
  216. }
  217. // recover两次,防止处理时发生panic,尤其是在OnPanicNotify中。
  218. newMux.engine.Use(func(ctx *gin.Context) {
  219. defer func() {
  220. if err := recover(); err != nil {
  221. logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", string(debug.Stack())))
  222. }
  223. }()
  224. ctx.Next()
  225. })
  226. newMux.engine.Use(func(ctx *gin.Context) {
  227. ts := time.Now()
  228. newCtx := newContext(ctx)
  229. defer releaseContext(newCtx)
  230. newCtx.init()
  231. newCtx.setLogger(logger)
  232. if !withoutTracePaths[ctx.Request.URL.Path] {
  233. if traceId := newCtx.GetHeader(trace.Header); traceId != "" {
  234. newCtx.setTrace(trace.New(traceId))
  235. } else {
  236. newCtx.setTrace(trace.New(""))
  237. }
  238. }
  239. defer func() {
  240. if err := recover(); err != nil {
  241. stackInfo := string(debug.Stack())
  242. logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo))
  243. newCtx.AbortWithError(errno.NewError(
  244. http.StatusInternalServerError,
  245. http.StatusInternalServerError,
  246. http.StatusText(http.StatusInternalServerError)),
  247. )
  248. if notify := opt.panicNotify; notify != nil {
  249. notify(newCtx, err, stackInfo)
  250. }
  251. }
  252. if ctx.Writer.Status() == http.StatusNotFound {
  253. return
  254. }
  255. var (
  256. response any
  257. businessCode int
  258. businessCodeMsg string
  259. abortErr error
  260. graphResponse any
  261. )
  262. if ctx.IsAborted() {
  263. for i := range ctx.Errors { // gin error
  264. multierr.AppendInto(&abortErr, ctx.Errors[i])
  265. }
  266. if err := newCtx.abortError(); err != nil { // customer err
  267. multierr.AppendInto(&abortErr, err.GetErr())
  268. response = err
  269. businessCode = err.GetBusinessCode()
  270. businessCodeMsg = err.GetMsg()
  271. if x := newCtx.Trace(); x != nil {
  272. newCtx.SetHeader(trace.Header, x.ID())
  273. }
  274. ctx.JSON(err.GetHttpCode(), &Failure{
  275. ResultCode: businessCode,
  276. ResultInfo: businessCodeMsg,
  277. })
  278. }
  279. } else {
  280. response = newCtx.getPayload()
  281. if response != nil {
  282. if x := newCtx.Trace(); x != nil {
  283. newCtx.SetHeader(trace.Header, x.ID())
  284. }
  285. ctx.JSON(http.StatusOK, response)
  286. }
  287. }
  288. graphResponse = newCtx.getGraphPayload()
  289. if opt.recordMetrics != nil {
  290. uri := newCtx.Path()
  291. if alias := newCtx.Alias(); alias != "" {
  292. uri = alias
  293. }
  294. opt.recordMetrics(
  295. newCtx.Method(),
  296. uri,
  297. !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK,
  298. time.Since(ts).Seconds(),
  299. )
  300. }
  301. var t *trace.Trace
  302. if x := newCtx.Trace(); x != nil {
  303. t = x.(*trace.Trace)
  304. } else {
  305. return
  306. }
  307. decodedURL, _ := url.QueryUnescape(ctx.Request.URL.RequestURI())
  308. t.WithRequest(&trace.Request{
  309. TTL: "un-limit",
  310. Method: ctx.Request.Method,
  311. DecodedURL: decodedURL,
  312. Header: ctx.Request.Header,
  313. Body: string(newCtx.RawData()),
  314. })
  315. var responseBody any
  316. if response != nil {
  317. responseBody = response
  318. }
  319. if graphResponse != nil {
  320. responseBody = graphResponse
  321. }
  322. t.WithResponse(&trace.Response{
  323. Header: ctx.Writer.Header(),
  324. HttpCode: ctx.Writer.Status(),
  325. HttpCodeMsg: http.StatusText(ctx.Writer.Status()),
  326. BusinessCode: businessCode,
  327. BusinessCodeMsg: businessCodeMsg,
  328. Body: responseBody,
  329. CostSeconds: time.Since(ts).Seconds(),
  330. })
  331. t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
  332. t.CostSeconds = time.Since(ts).Seconds()
  333. logger.Info("core-interceptor",
  334. zap.Any("method", ctx.Request.Method),
  335. zap.Any("path", decodedURL),
  336. zap.Any("http_code", ctx.Writer.Status()),
  337. zap.Any("business_code", businessCode),
  338. zap.Any("success", t.Success),
  339. zap.Any("cost_seconds", t.CostSeconds),
  340. zap.Any("trace_id", t.Identifier),
  341. zap.Any("trace_info", t),
  342. zap.Error(abortErr),
  343. )
  344. }()
  345. ctx.Next()
  346. })
  347. if opt.rateLimiter != nil {
  348. newMux.engine.Use(func(ctx *gin.Context) {
  349. newCtx := newContext(ctx)
  350. defer releaseContext(newCtx)
  351. if !opt.rateLimiter.Allow(ctx.ClientIP()) {
  352. newCtx.AbortWithError(errno.NewError(
  353. http.StatusTooManyRequests,
  354. http.StatusTooManyRequests,
  355. http.StatusText(http.StatusTooManyRequests)),
  356. )
  357. return
  358. }
  359. ctx.Next()
  360. })
  361. }
  362. newMux.engine.NoMethod(wrapHandlers(DisableTrace)...)
  363. newMux.engine.NoRoute(wrapHandlers(DisableTrace)...)
  364. system := newMux.Group("/system")
  365. {
  366. // 健康检查
  367. system.GET("/health", func(ctx Context) {
  368. resp := &struct {
  369. Timestamp time.Time `json:"timestamp"`
  370. Environment string `json:"environment"`
  371. Host string `json:"host"`
  372. Status string `json:"status"`
  373. }{
  374. Timestamp: time.Now(),
  375. Environment: env.Active().Value(),
  376. Host: ctx.Host(),
  377. Status: "ok",
  378. }
  379. ctx.Payload(resp)
  380. })
  381. }
  382. return newMux, nil
  383. }