diff --git a/go.mod b/go.mod index 9894012..53283d8 100644 --- a/go.mod +++ b/go.mod @@ -21,8 +21,14 @@ require ( ) require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/gofiber/fiber/v2 v2.38.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/klauspost/compress v1.15.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.40.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect mellium.im/sasl v0.3.0 // indirect diff --git a/go.sum b/go.sum index eb79b7a..ea5bcef 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -89,6 +91,8 @@ github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2B github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= +github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -165,6 +169,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -260,6 +266,12 @@ github.com/uptrace/bun/dialect/pgdialect v1.1.8 h1:wayJhjYDPGv8tgOBLolbBtSFQ0Tih github.com/uptrace/bun/dialect/pgdialect v1.1.8/go.mod h1:nNbU8PHTjTUM+CRtGmqyBb9zcuRAB8I680/qoFSmBUk= github.com/uptrace/bun/driver/pgdriver v1.1.8 h1:gyL22axRQfjJS2Umq0erzJnp0bLOdUE8/USKZHPQB8o= github.com/uptrace/bun/driver/pgdriver v1.1.8/go.mod h1:4tHK0h7a/UoldBoe9J3GU4tEYjr3mkd62U3Kq3PVk3E= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= +github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -300,6 +312,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -374,6 +387,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -435,6 +449,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/logger/middleware.go b/logger/middleware.go index c622f8f..69cff6f 100644 --- a/logger/middleware.go +++ b/logger/middleware.go @@ -1,48 +1,87 @@ package logger import ( - "fmt" + "os" + "strconv" + "sync" "time" - "github.com/gin-gonic/gin" + "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - start := time.Now() - path := c.Request.URL.Path - raw := c.Request.URL.RawQuery +// Config defines the config for middleware +type LogMiddlewareConfig struct { + // Next defines a function to skip this middleware when returned true. + // + // Optional. Default: nil + Next func(c *fiber.Ctx) bool - // Process request - c.Next() + // Logger defines zap logger instance + Logger *zap.Logger +} - latency := time.Since(start) +// New creates a new middleware handler +func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler { + var ( + errPadding = 15 + start, stop time.Time + once sync.Once + errHandler fiber.ErrorHandler + ) - clientIP := c.ClientIP() - method := c.Request.Method - statusCode := c.Writer.Status() - - comment := c.Errors.ByType(gin.ErrorTypePrivate).String() - - if raw != "" { - path = path + "?" + raw + return func(c *fiber.Ctx) error { + if config.Next != nil && config.Next(c) { + return c.Next() } + once.Do(func() { + errHandler = c.App().Config().ErrorHandler + stack := c.App().Stack() + for m := range stack { + for r := range stack[m] { + if len(stack[m][r].Path) > errPadding { + errPadding = len(stack[m][r].Path) + } + } + } + }) + + start = time.Now() + + chainErr := c.Next() + + if chainErr != nil { + if err := errHandler(c, chainErr); err != nil { + _ = c.SendStatus(fiber.StatusInternalServerError) + } + } + + stop = time.Now() + fields := []zap.Field{ - zap.Int("statusCode", statusCode), - zap.Duration("latency", latency), - zap.String("clientIP", clientIP), - zap.String("method", method), - zap.String("path", path), + zap.Namespace("context"), + zap.String("pid", strconv.Itoa(os.Getpid())), + zap.String("time", stop.Sub(start).String()), + zap.Object("response", Resp(c.Response())), + zap.Object("request", Req(c)), } - if comment != "" { - logger.Named("Gin").Error( - comment, - fields..., - ) - } else { - logger.Named("Gin").Info(fmt.Sprintf("%s -> [%d] %s %s.", clientIP, statusCode, method, path), fields...) + + if u := c.Locals("userId"); u != nil { + fields = append(fields, zap.Uint("userId", u.(uint))) } + + formatErr := "" + if chainErr != nil { + formatErr = chainErr.Error() + fields = append(fields, zap.String("error", formatErr)) + config.Logger.With(fields...).Error(formatErr) + + return nil + } + + config.Logger.With(fields...).Info("api.request") + + return nil } } diff --git a/logger/middleware_types.go b/logger/middleware_types.go new file mode 100644 index 0000000..13eec78 --- /dev/null +++ b/logger/middleware_types.go @@ -0,0 +1,119 @@ +package logger + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp" + "go.uber.org/zap/zapcore" +) + +func getAllowedHeaders() map[string]bool { + return map[string]bool{ + "User-Agent": true, + "X-Mobile": true, + } +} + +type resp struct { + code int + _type string +} + +func Resp(r *fasthttp.Response) *resp { + return &resp{ + code: r.StatusCode(), + _type: bytes.NewBuffer(r.Header.ContentType()).String(), + } +} + +func (r *resp) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("type", r._type) + enc.AddInt("code", r.code) + + return nil +} + +type req struct { + body string + fullPath string + user string + ip string + method string + route string + headers *headerbag +} + +func Req(c *fiber.Ctx) *req { + reqq := c.Request() + var body []byte + buffer := new(bytes.Buffer) + err := json.Compact(buffer, reqq.Body()) + if err != nil { + body = reqq.Body() + } else { + body = buffer.Bytes() + } + + headers := &headerbag{ + vals: make(map[string]string), + } + allowedHeaders := getAllowedHeaders() + reqq.Header.VisitAll(func(key, val []byte) { + k := bytes.NewBuffer(key).String() + if _, exist := allowedHeaders[k]; exist { + headers.vals[strings.ToLower(k)] = bytes.NewBuffer(val).String() + } + }) + + var userEmail string + if u := c.Locals("userEmail"); u != nil { + userEmail = u.(string) + } + + return &req{ + body: bytes.NewBuffer(body).String(), + fullPath: bytes.NewBuffer(reqq.RequestURI()).String(), + headers: headers, + ip: c.IP(), + method: c.Method(), + route: c.Route().Path, + user: userEmail, + } +} + +func (r *req) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("fullPath", r.fullPath) + enc.AddString("ip", r.ip) + enc.AddString("method", r.method) + enc.AddString("route", r.route) + + if r.body != "" { + enc.AddString("body", r.body) + } + + if r.user != "" { + enc.AddString("user", r.user) + } + + err := enc.AddObject("headers", r.headers) + if err != nil { + return err + } + + return nil +} + +type headerbag struct { + vals map[string]string +} + +func (h *headerbag) MarshalLogObject(enc zapcore.ObjectEncoder) error { + for k, v := range h.vals { + enc.AddString(k, v) + } + + return nil +} diff --git a/main.go b/main.go index 090710f..0ccc5f4 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,6 @@ import ( "strconv" "time" - "github.com/gin-gonic/gin" jsontime "github.com/liamylian/jsontime/v2/v2" "github.com/samber/lo" "github.com/shopspring/decimal" @@ -189,7 +188,6 @@ func main() { // 本次停用检测的原因是:使用Ping来保持数据库链接看起来没有什么用处。 // go DBConnectionKeepLive() go RedisOrphanCleanup() - gin.SetMode(config.ServerSettings.RunMode) - r := router.Router() - r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) + app := router.App() + app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) } diff --git a/response/base_response.go b/response/base_response.go index 689aa86..c156f9f 100644 --- a/response/base_response.go +++ b/response/base_response.go @@ -2,13 +2,12 @@ package response import ( "electricity_bill_calc/config" - "net/http" - "github.com/gin-gonic/gin" + "github.com/gofiber/fiber/v2" ) type Result struct { - Ctx *gin.Context + Ctx *fiber.Ctx } type BaseResponse struct { @@ -22,11 +21,11 @@ type PagedResponse struct { Total int64 `json:"total"` } -func NewResult(ctx *gin.Context) *Result { - return &Result{Ctx: ctx} +func NewResult(ctx *fiber.Ctx) Result { + return Result{Ctx: ctx} } -func (r *Result) response(status, code int, msg string, payloads ...map[string]interface{}) { +func (r Result) response(status, code int, msg string, payloads ...map[string]interface{}) error { var finalPayload = make(map[string]interface{}, 0) finalPayload["code"] = code finalPayload["message"] = msg @@ -37,68 +36,78 @@ func (r *Result) response(status, code int, msg string, payloads ...map[string]i } } - r.Ctx.JSON(status, finalPayload) + return r.Ctx.Status(status).JSON(finalPayload) +} + +// 禁止访问响应 +func (r Result) Forbidden(msg string) error { + return r.response(fiber.StatusForbidden, fiber.StatusForbidden, msg) } // 操作出错(未成功)响应 -func (r *Result) Failure(code int, msg string) { - r.response(http.StatusInternalServerError, code, msg) +func (r Result) Failure(code int, msg string) error { + return r.response(fiber.StatusInternalServerError, code, msg) } // 操作出错(未成功)响应 -func (r *Result) Error(code int, msg string) { - r.response(http.StatusOK, code, msg) +func (r Result) Error(code int, msg string) error { + return r.response(fiber.StatusOK, code, msg) +} + +// 不能解析传入的参数 +func (r Result) UnableToParse(msg string) error { + return r.response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, msg) } // 用户未获得授权)响应 -func (r *Result) Unauthorized(msg string) { - r.response(http.StatusOK, http.StatusUnauthorized, msg) +func (r *Result) Unauthorized(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusUnauthorized, msg) } // 简易操作成功信息 -func (r *Result) Success(msg string, payloads ...map[string]interface{}) { - r.response(http.StatusOK, http.StatusOK, msg, payloads...) +func (r *Result) Success(msg string, payloads ...map[string]interface{}) error { + return r.response(fiber.StatusOK, fiber.StatusOK, msg, payloads...) } // 数据成功创建 -func (r *Result) Created(msg string, payloads ...map[string]interface{}) { - r.response(http.StatusOK, http.StatusCreated, msg, payloads...) +func (r Result) Created(msg string, payloads ...map[string]interface{}) error { + return r.response(fiber.StatusOK, fiber.StatusCreated, msg, payloads...) } // 数据成功更新 -func (r *Result) Updated(msg string) { - r.response(http.StatusOK, http.StatusAccepted, msg) +func (r Result) Updated(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusAccepted, msg) } // 数据已成功删除 -func (r *Result) Deleted(msg string) { - r.response(http.StatusOK, http.StatusNoContent, msg) +func (r Result) Deleted(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusNoContent, msg) } // 指定操作未被接受 -func (r *Result) BadRequest(msg string) { - r.response(http.StatusOK, http.StatusBadRequest, msg) +func (r Result) BadRequest(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusBadRequest, msg) } // 指定操作未被接受 -func (r *Result) NotAccept(msg string) { - r.response(http.StatusOK, http.StatusNotAcceptable, msg) +func (r Result) NotAccept(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusNotAcceptable, msg) } // 数据未找到 -func (r *Result) NotFound(msg string) { - r.response(http.StatusOK, http.StatusNotFound, msg) +func (r Result) NotFound(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusNotFound, msg) } // 数据存在冲突 -func (r *Result) Conflict(msg string) { - r.response(http.StatusOK, http.StatusConflict, msg) +func (r Result) Conflict(msg string) error { + return r.response(fiber.StatusOK, fiber.StatusConflict, msg) } // 快速自由JSON格式响应 // ! 注意,给定的map中,同名的键会被覆盖。 -func (r *Result) Json(code int, msg string, payloads ...map[string]interface{}) { - r.response(http.StatusOK, code, msg, payloads...) +func (r Result) Json(code int, msg string, payloads ...map[string]interface{}) error { + return r.response(fiber.StatusOK, code, msg, payloads...) } func NewPagedResponse(page int, total int64) *PagedResponse { @@ -106,7 +115,7 @@ func NewPagedResponse(page int, total int64) *PagedResponse { } func (r PagedResponse) ToMap() map[string]interface{} { - return gin.H{ + return fiber.Map{ "current": r.Page, "pageSize": r.Size, "total": r.Total, diff --git a/response/user_response.go b/response/user_response.go index eb488db..6cfed8e 100644 --- a/response/user_response.go +++ b/response/user_response.go @@ -3,6 +3,8 @@ package response import ( "electricity_bill_calc/model" "net/http" + + "github.com/gofiber/fiber/v2" ) type LoginResponse struct { @@ -17,7 +19,7 @@ func (r *Result) LoginSuccess(session *model.Session) { res.Message = "用户已成功登录。" res.NeedReset = false res.Session = session - r.Ctx.JSON(http.StatusOK, res) + r.Ctx.Status(fiber.StatusOK).JSON(res) } func (r *Result) LoginNeedReset() { @@ -25,5 +27,5 @@ func (r *Result) LoginNeedReset() { res.Code = http.StatusUnauthorized res.Message = "用户凭据已失效。" res.NeedReset = true - r.Ctx.JSON(http.StatusOK, res) + r.Ctx.Status(fiber.StatusOK).JSON(res) } diff --git a/router/router.go b/router/router.go index 106c631..319fdc9 100644 --- a/router/router.go +++ b/router/router.go @@ -3,52 +3,65 @@ package router import ( "electricity_bill_calc/controller" "electricity_bill_calc/logger" - "electricity_bill_calc/response" "electricity_bill_calc/security" - "time" + "fmt" + "runtime" - ginzap "github.com/gin-contrib/zap" - "github.com/gin-gonic/gin" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/compress" + "github.com/gofiber/fiber/v2/middleware/recover" "go.uber.org/zap" ) -func Router() *gin.Engine { - router := gin.New() - router.Use(ginzap.Ginzap(logger.GetLogger(), time.RFC3339, false)) - router.Use(ginzap.RecoveryWithZap(logger.GetLogger(), true)) - router.Use(security.SessionRecovery) +func App() *fiber.App { + app := fiber.New(fiber.Config{ + BodyLimit: 10 * 1024 * 1024, + EnablePrintRoutes: true, + EnableTrustedProxyCheck: false, + Prefork: false, + ErrorHandler: errorHandler, + }) + app.Use(compress.New()) + app.Use(recover.New(recover.Config{ + EnableStackTrace: true, + StackTraceHandler: stackTraceHandler, + })) + app.Use(logger.NewLogMiddleware(logger.LogMiddlewareConfig{ + Logger: logger.Named("App"), + })) + app.Use(security.SessionRecovery) - controller.InitializeUserController(router) - controller.InitializeRegionController(router) - controller.InitializeChargesController(router) - controller.InitializeParkController(router) - controller.InitializeMaintenanceFeeController(router) - controller.InitializeMeter04kVController(router) - controller.InitializeReportController(router) - controller.InitializeEndUserController(router) - controller.InitializeWithdrawController(router) - controller.InitializeStatisticsController(router) - controller.InitializeGodModeController(router) + controller.InitializeUserController(app) + controller.InitializeRegionController(app) + controller.InitializeChargesController(app) + controller.InitializeParkController(app) + controller.InitializeMaintenanceFeeController(app) + controller.InitializeMeter04kVController(app) + controller.InitializeReportController(app) + controller.InitializeEndUserController(app) + controller.InitializeWithdrawController(app) + controller.InitializeStatisticsController(app) + controller.InitializeGodModeController(app) - return router + return app } -// 404 -func HandleNotFound(c *gin.Context) { - response.NewResult(c).NotFound("指定资源未找到。") +// 全局错误处理 +func errorHandler(c *fiber.Ctx, err error) error { + code := fiber.StatusInternalServerError + if e, ok := err.(*fiber.Error); ok { + code = e.Code + } + e := c.Status(code).SendString(err.Error()) + if e != nil { + return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) + } + return nil } -// 500 -func Recover(c *gin.Context) { - defer func() { - if r := recover(); r != nil { - //打印错误堆栈信息 - if err, ok := r.(error); ok { - logger.Error(err.Error(), zap.Error(err)) - } - // response.NewResult(c).Error(500, "服务器内部错误") - } - }() - //继续后续接口调用 - c.Next() +// 处理Recover中间件输出的栈追踪信息 +func stackTraceHandler(c *fiber.Ctx, e interface{}) { + buf := make([]byte, 1024) + buf = buf[:runtime.Stack(buf, false)] + logger.Named("App", "StackTrace").Warn(fmt.Sprintf("panic: %+v", e), zap.ByteString("trace", buf), zap.Any("origin", e)) } diff --git a/security/security.go b/security/security.go index c09ac08..151b097 100644 --- a/security/security.go +++ b/security/security.go @@ -3,89 +3,69 @@ package security import ( "electricity_bill_calc/cache" "electricity_bill_calc/model" - "net/http" + "electricity_bill_calc/response" "strings" - "github.com/gin-gonic/gin" + "github.com/gofiber/fiber/v2" ) // 用于解析Authorization头,并从缓存中获取用户会话信息注入上下文的中间件。 // 如果没有获取到用户会话信息,将直接跳过会话信息注入。 // ! 仅通过该中间件是不能保证上下文中一定保存有用户会话信息的。 -func SessionRecovery(c *gin.Context) { - auth := c.Request.Header.Get("Authorization") - if len(auth) > 0 { +func SessionRecovery(c *fiber.Ctx) error { + if auth := c.Get("Authorization", ""); len(auth) > 0 { token := strings.Fields(auth)[1] session, err := cache.RetreiveSession(token) if err == nil && session != nil { - c.Set("session", session) + c.Locals("session", session) } } - c.Next() + return c.Next() } // 用于强制确定用户已经登录了系统,即具有有效的用户会话 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 -func MustAuthenticated(c *gin.Context) { - session, exists := c.Get("session") - if !exists || session == nil { - c.AbortWithStatus(http.StatusForbidden) +func MustAuthenticated(c *fiber.Ctx) error { + if session, ok := c.Locals("session").(*model.Session); !ok || session == nil { + return response.NewResult(c).Forbidden("用户必须在登录以后才能访问系统。") } - if _, ok := session.(*model.Session); !ok { - c.AbortWithStatus(http.StatusForbidden) - } - c.Next() + return c.Next() } // 用于对用户会话进行是否企业用户的判断 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 -func EnterpriseAuthorize(c *gin.Context) { - session, exists := c.Get("session") - if !exists || session == nil { - c.AbortWithStatus(http.StatusForbidden) +func EnterpriseAuthorize(c *fiber.Ctx) error { + if sess, ok := c.Locals("session").(*model.Session); !ok || sess.Type != model.USER_TYPE_ENT { + return response.NewResult(c).Forbidden("指定接口资源只限企业用户访问。") } - if sess, ok := session.(*model.Session); !ok || sess.Type != model.USER_TYPE_ENT { - c.AbortWithStatus(http.StatusForbidden) - } - c.Next() + return c.Next() } // 用于对用户会话进行是否监管用户或运维用户的判断 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 -func ManagementAuthorize(c *gin.Context) { - session, exists := c.Get("session") - if !exists || session == nil { - c.AbortWithStatus(http.StatusForbidden) +func ManagementAuthorize(c *fiber.Ctx) error { + if sess, ok := c.Locals("session").(*model.Session); !ok || (sess.Type != model.USER_TYPE_SUP && sess.Type != model.USER_TYPE_OPS) { + r := response.NewResult(c) + return r.Forbidden("指定接口资源只限市场监管用户访问。") } - if sess, ok := session.(*model.Session); !ok || (sess.Type != model.USER_TYPE_SUP && sess.Type != model.USER_TYPE_OPS) { - c.AbortWithStatus(http.StatusForbidden) - } - c.Next() + return c.Next() } // 用于对用户会话进行是否运维用户的判断 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 -func OPSAuthorize(c *gin.Context) { - session, exists := c.Get("session") - if !exists { - c.AbortWithStatus(http.StatusForbidden) +func OPSAuthorize(c *fiber.Ctx) error { + if sess, ok := c.Locals("session").(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS { + return response.NewResult(c).Forbidden("指定接口资源只限系统运维用户访问。") } - if sess, ok := session.(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS { - c.AbortWithStatus(http.StatusForbidden) - } - c.Next() + return c.Next() } // 用于对用户会话进行是否核心运维用户的判断 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 -func SingularityAuthorize(c *gin.Context) { - session, exists := c.Get("session") - if !exists { - c.AbortWithStatus(http.StatusForbidden) +func SingularityAuthorize(c *fiber.Ctx) error { + if sess, ok := c.Locals("session").(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS || sess.Uid != "000" { + return response.NewResult(c).Forbidden("指定接口资源只限系统核心运维用户访问。") } - if sess, ok := session.(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS || sess.Uid != "000" { - c.AbortWithStatus(http.StatusForbidden) - } - c.Next() + return c.Next() }