refactor(app):基本完成基础服务框架的功能构建。

This commit is contained in:
徐涛 2022-09-28 16:01:16 +08:00
parent df9bf83bb8
commit 4b08952916
9 changed files with 334 additions and 152 deletions

6
go.mod
View File

@ -21,8 +21,14 @@ require (
) )
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/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/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/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
mellium.im/sasl v0.3.0 // indirect mellium.im/sasl v0.3.0 // indirect

16
go.sum
View File

@ -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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/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 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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= 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/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 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/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.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/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/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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/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 h1:gyL22axRQfjJS2Umq0erzJnp0bLOdUE8/USKZHPQB8o=
github.com/uptrace/bun/driver/pgdriver v1.1.8/go.mod h1:4tHK0h7a/UoldBoe9J3GU4tEYjr3mkd62U3Kq3PVk3E= 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 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 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= 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-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-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-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-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 h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-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-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-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 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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= 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-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-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-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-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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -1,48 +1,87 @@
package logger package logger
import ( import (
"fmt" "os"
"strconv"
"sync"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
"go.uber.org/zap" "go.uber.org/zap"
) )
func Logger() gin.HandlerFunc { // Config defines the config for middleware
return func(c *gin.Context) { type LogMiddlewareConfig struct {
start := time.Now() // Next defines a function to skip this middleware when returned true.
path := c.Request.URL.Path //
raw := c.Request.URL.RawQuery // Optional. Default: nil
Next func(c *fiber.Ctx) bool
// Process request // Logger defines zap logger instance
c.Next() 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() return func(c *fiber.Ctx) error {
method := c.Request.Method if config.Next != nil && config.Next(c) {
statusCode := c.Writer.Status() return c.Next()
comment := c.Errors.ByType(gin.ErrorTypePrivate).String()
if raw != "" {
path = path + "?" + raw
} }
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{ fields := []zap.Field{
zap.Int("statusCode", statusCode), zap.Namespace("context"),
zap.Duration("latency", latency), zap.String("pid", strconv.Itoa(os.Getpid())),
zap.String("clientIP", clientIP), zap.String("time", stop.Sub(start).String()),
zap.String("method", method), zap.Object("response", Resp(c.Response())),
zap.String("path", path), zap.Object("request", Req(c)),
} }
if comment != "" {
logger.Named("Gin").Error( if u := c.Locals("userId"); u != nil {
comment, fields = append(fields, zap.Uint("userId", u.(uint)))
fields...,
)
} else {
logger.Named("Gin").Info(fmt.Sprintf("%s -> [%d] %s %s.", clientIP, statusCode, method, path), fields...)
} }
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
} }
} }

119
logger/middleware_types.go Normal file
View File

@ -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
}

View File

@ -17,7 +17,6 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/gin-gonic/gin"
jsontime "github.com/liamylian/jsontime/v2/v2" jsontime "github.com/liamylian/jsontime/v2/v2"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
@ -189,7 +188,6 @@ func main() {
// 本次停用检测的原因是使用Ping来保持数据库链接看起来没有什么用处。 // 本次停用检测的原因是使用Ping来保持数据库链接看起来没有什么用处。
// go DBConnectionKeepLive() // go DBConnectionKeepLive()
go RedisOrphanCleanup() go RedisOrphanCleanup()
gin.SetMode(config.ServerSettings.RunMode) app := router.App()
r := router.Router() app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))
r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))
} }

View File

@ -2,13 +2,12 @@ package response
import ( import (
"electricity_bill_calc/config" "electricity_bill_calc/config"
"net/http"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
) )
type Result struct { type Result struct {
Ctx *gin.Context Ctx *fiber.Ctx
} }
type BaseResponse struct { type BaseResponse struct {
@ -22,11 +21,11 @@ type PagedResponse struct {
Total int64 `json:"total"` Total int64 `json:"total"`
} }
func NewResult(ctx *gin.Context) *Result { func NewResult(ctx *fiber.Ctx) Result {
return &Result{Ctx: ctx} 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) var finalPayload = make(map[string]interface{}, 0)
finalPayload["code"] = code finalPayload["code"] = code
finalPayload["message"] = msg 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) { func (r Result) Failure(code int, msg string) error {
r.response(http.StatusInternalServerError, code, msg) return r.response(fiber.StatusInternalServerError, code, msg)
} }
// 操作出错(未成功)响应 // 操作出错(未成功)响应
func (r *Result) Error(code int, msg string) { func (r Result) Error(code int, msg string) error {
r.response(http.StatusOK, code, msg) 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) { func (r *Result) Unauthorized(msg string) error {
r.response(http.StatusOK, http.StatusUnauthorized, msg) return r.response(fiber.StatusOK, fiber.StatusUnauthorized, msg)
} }
// 简易操作成功信息 // 简易操作成功信息
func (r *Result) Success(msg string, payloads ...map[string]interface{}) { func (r *Result) Success(msg string, payloads ...map[string]interface{}) error {
r.response(http.StatusOK, http.StatusOK, msg, payloads...) return r.response(fiber.StatusOK, fiber.StatusOK, msg, payloads...)
} }
// 数据成功创建 // 数据成功创建
func (r *Result) Created(msg string, payloads ...map[string]interface{}) { func (r Result) Created(msg string, payloads ...map[string]interface{}) error {
r.response(http.StatusOK, http.StatusCreated, msg, payloads...) return r.response(fiber.StatusOK, fiber.StatusCreated, msg, payloads...)
} }
// 数据成功更新 // 数据成功更新
func (r *Result) Updated(msg string) { func (r Result) Updated(msg string) error {
r.response(http.StatusOK, http.StatusAccepted, msg) return r.response(fiber.StatusOK, fiber.StatusAccepted, msg)
} }
// 数据已成功删除 // 数据已成功删除
func (r *Result) Deleted(msg string) { func (r Result) Deleted(msg string) error {
r.response(http.StatusOK, http.StatusNoContent, msg) return r.response(fiber.StatusOK, fiber.StatusNoContent, msg)
} }
// 指定操作未被接受 // 指定操作未被接受
func (r *Result) BadRequest(msg string) { func (r Result) BadRequest(msg string) error {
r.response(http.StatusOK, http.StatusBadRequest, msg) return r.response(fiber.StatusOK, fiber.StatusBadRequest, msg)
} }
// 指定操作未被接受 // 指定操作未被接受
func (r *Result) NotAccept(msg string) { func (r Result) NotAccept(msg string) error {
r.response(http.StatusOK, http.StatusNotAcceptable, msg) return r.response(fiber.StatusOK, fiber.StatusNotAcceptable, msg)
} }
// 数据未找到 // 数据未找到
func (r *Result) NotFound(msg string) { func (r Result) NotFound(msg string) error {
r.response(http.StatusOK, http.StatusNotFound, msg) return r.response(fiber.StatusOK, fiber.StatusNotFound, msg)
} }
// 数据存在冲突 // 数据存在冲突
func (r *Result) Conflict(msg string) { func (r Result) Conflict(msg string) error {
r.response(http.StatusOK, http.StatusConflict, msg) return r.response(fiber.StatusOK, fiber.StatusConflict, msg)
} }
// 快速自由JSON格式响应 // 快速自由JSON格式响应
// ! 注意给定的map中同名的键会被覆盖。 // ! 注意给定的map中同名的键会被覆盖。
func (r *Result) Json(code int, msg string, payloads ...map[string]interface{}) { func (r Result) Json(code int, msg string, payloads ...map[string]interface{}) error {
r.response(http.StatusOK, code, msg, payloads...) return r.response(fiber.StatusOK, code, msg, payloads...)
} }
func NewPagedResponse(page int, total int64) *PagedResponse { 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{} { func (r PagedResponse) ToMap() map[string]interface{} {
return gin.H{ return fiber.Map{
"current": r.Page, "current": r.Page,
"pageSize": r.Size, "pageSize": r.Size,
"total": r.Total, "total": r.Total,

View File

@ -3,6 +3,8 @@ package response
import ( import (
"electricity_bill_calc/model" "electricity_bill_calc/model"
"net/http" "net/http"
"github.com/gofiber/fiber/v2"
) )
type LoginResponse struct { type LoginResponse struct {
@ -17,7 +19,7 @@ func (r *Result) LoginSuccess(session *model.Session) {
res.Message = "用户已成功登录。" res.Message = "用户已成功登录。"
res.NeedReset = false res.NeedReset = false
res.Session = session res.Session = session
r.Ctx.JSON(http.StatusOK, res) r.Ctx.Status(fiber.StatusOK).JSON(res)
} }
func (r *Result) LoginNeedReset() { func (r *Result) LoginNeedReset() {
@ -25,5 +27,5 @@ func (r *Result) LoginNeedReset() {
res.Code = http.StatusUnauthorized res.Code = http.StatusUnauthorized
res.Message = "用户凭据已失效。" res.Message = "用户凭据已失效。"
res.NeedReset = true res.NeedReset = true
r.Ctx.JSON(http.StatusOK, res) r.Ctx.Status(fiber.StatusOK).JSON(res)
} }

View File

@ -3,52 +3,65 @@ package router
import ( import (
"electricity_bill_calc/controller" "electricity_bill_calc/controller"
"electricity_bill_calc/logger" "electricity_bill_calc/logger"
"electricity_bill_calc/response"
"electricity_bill_calc/security" "electricity_bill_calc/security"
"time" "fmt"
"runtime"
ginzap "github.com/gin-contrib/zap" "github.com/gofiber/fiber/v2"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/recover"
"go.uber.org/zap" "go.uber.org/zap"
) )
func Router() *gin.Engine { func App() *fiber.App {
router := gin.New() app := fiber.New(fiber.Config{
router.Use(ginzap.Ginzap(logger.GetLogger(), time.RFC3339, false)) BodyLimit: 10 * 1024 * 1024,
router.Use(ginzap.RecoveryWithZap(logger.GetLogger(), true)) EnablePrintRoutes: true,
router.Use(security.SessionRecovery) 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.InitializeUserController(app)
controller.InitializeRegionController(router) controller.InitializeRegionController(app)
controller.InitializeChargesController(router) controller.InitializeChargesController(app)
controller.InitializeParkController(router) controller.InitializeParkController(app)
controller.InitializeMaintenanceFeeController(router) controller.InitializeMaintenanceFeeController(app)
controller.InitializeMeter04kVController(router) controller.InitializeMeter04kVController(app)
controller.InitializeReportController(router) controller.InitializeReportController(app)
controller.InitializeEndUserController(router) controller.InitializeEndUserController(app)
controller.InitializeWithdrawController(router) controller.InitializeWithdrawController(app)
controller.InitializeStatisticsController(router) controller.InitializeStatisticsController(app)
controller.InitializeGodModeController(router) controller.InitializeGodModeController(app)
return router return app
} }
// 404 // 全局错误处理
func HandleNotFound(c *gin.Context) { func errorHandler(c *fiber.Ctx, err error) error {
response.NewResult(c).NotFound("指定资源未找到。") 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 // 处理Recover中间件输出的栈追踪信息
func Recover(c *gin.Context) { func stackTraceHandler(c *fiber.Ctx, e interface{}) {
defer func() { buf := make([]byte, 1024)
if r := recover(); r != nil { buf = buf[:runtime.Stack(buf, false)]
//打印错误堆栈信息 logger.Named("App", "StackTrace").Warn(fmt.Sprintf("panic: %+v", e), zap.ByteString("trace", buf), zap.Any("origin", e))
if err, ok := r.(error); ok {
logger.Error(err.Error(), zap.Error(err))
}
// response.NewResult(c).Error(500, "服务器内部错误")
}
}()
//继续后续接口调用
c.Next()
} }

View File

@ -3,89 +3,69 @@ package security
import ( import (
"electricity_bill_calc/cache" "electricity_bill_calc/cache"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"net/http" "electricity_bill_calc/response"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
) )
// 用于解析Authorization头并从缓存中获取用户会话信息注入上下文的中间件。 // 用于解析Authorization头并从缓存中获取用户会话信息注入上下文的中间件。
// 如果没有获取到用户会话信息,将直接跳过会话信息注入。 // 如果没有获取到用户会话信息,将直接跳过会话信息注入。
// ! 仅通过该中间件是不能保证上下文中一定保存有用户会话信息的。 // ! 仅通过该中间件是不能保证上下文中一定保存有用户会话信息的。
func SessionRecovery(c *gin.Context) { func SessionRecovery(c *fiber.Ctx) error {
auth := c.Request.Header.Get("Authorization") if auth := c.Get("Authorization", ""); len(auth) > 0 {
if len(auth) > 0 {
token := strings.Fields(auth)[1] token := strings.Fields(auth)[1]
session, err := cache.RetreiveSession(token) session, err := cache.RetreiveSession(token)
if err == nil && session != nil { if err == nil && session != nil {
c.Set("session", session) c.Locals("session", session)
} }
} }
c.Next() return c.Next()
} }
// 用于强制确定用户已经登录了系统,即具有有效的用户会话 // 用于强制确定用户已经登录了系统,即具有有效的用户会话
// ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。
func MustAuthenticated(c *gin.Context) { func MustAuthenticated(c *fiber.Ctx) error {
session, exists := c.Get("session") if session, ok := c.Locals("session").(*model.Session); !ok || session == nil {
if !exists || session == nil { return response.NewResult(c).Forbidden("用户必须在登录以后才能访问系统。")
c.AbortWithStatus(http.StatusForbidden)
} }
if _, ok := session.(*model.Session); !ok { return c.Next()
c.AbortWithStatus(http.StatusForbidden)
}
c.Next()
} }
// 用于对用户会话进行是否企业用户的判断 // 用于对用户会话进行是否企业用户的判断
// ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。
func EnterpriseAuthorize(c *gin.Context) { func EnterpriseAuthorize(c *fiber.Ctx) error {
session, exists := c.Get("session") if sess, ok := c.Locals("session").(*model.Session); !ok || sess.Type != model.USER_TYPE_ENT {
if !exists || session == nil { return response.NewResult(c).Forbidden("指定接口资源只限企业用户访问。")
c.AbortWithStatus(http.StatusForbidden)
} }
if sess, ok := session.(*model.Session); !ok || sess.Type != model.USER_TYPE_ENT { return c.Next()
c.AbortWithStatus(http.StatusForbidden)
}
c.Next()
} }
// 用于对用户会话进行是否监管用户或运维用户的判断 // 用于对用户会话进行是否监管用户或运维用户的判断
// ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。
func ManagementAuthorize(c *gin.Context) { func ManagementAuthorize(c *fiber.Ctx) error {
session, exists := c.Get("session") if sess, ok := c.Locals("session").(*model.Session); !ok || (sess.Type != model.USER_TYPE_SUP && sess.Type != model.USER_TYPE_OPS) {
if !exists || session == nil { r := response.NewResult(c)
c.AbortWithStatus(http.StatusForbidden) return r.Forbidden("指定接口资源只限市场监管用户访问。")
} }
if sess, ok := session.(*model.Session); !ok || (sess.Type != model.USER_TYPE_SUP && sess.Type != model.USER_TYPE_OPS) { return c.Next()
c.AbortWithStatus(http.StatusForbidden)
}
c.Next()
} }
// 用于对用户会话进行是否运维用户的判断 // 用于对用户会话进行是否运维用户的判断
// ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。
func OPSAuthorize(c *gin.Context) { func OPSAuthorize(c *fiber.Ctx) error {
session, exists := c.Get("session") if sess, ok := c.Locals("session").(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS {
if !exists { return response.NewResult(c).Forbidden("指定接口资源只限系统运维用户访问。")
c.AbortWithStatus(http.StatusForbidden)
} }
if sess, ok := session.(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS { return c.Next()
c.AbortWithStatus(http.StatusForbidden)
}
c.Next()
} }
// 用于对用户会话进行是否核心运维用户的判断 // 用于对用户会话进行是否核心运维用户的判断
// ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。 // ! 通过该中间件以后,是可以保证上下文中一定具有用户会话信息的。
func SingularityAuthorize(c *gin.Context) { func SingularityAuthorize(c *fiber.Ctx) error {
session, exists := c.Get("session") if sess, ok := c.Locals("session").(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS || sess.Uid != "000" {
if !exists { return response.NewResult(c).Forbidden("指定接口资源只限系统核心运维用户访问。")
c.AbortWithStatus(http.StatusForbidden)
} }
if sess, ok := session.(*model.Session); !ok || sess.Type != model.USER_TYPE_OPS || sess.Uid != "000" { return c.Next()
c.AbortWithStatus(http.StatusForbidden)
}
c.Next()
} }