From dd1674467649e5489105d94aace6cfdcd25fb08a Mon Sep 17 00:00:00 2001 From: linwenxiang <991154416@qq.com> Date: Mon, 29 Mar 2021 09:05:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor=20=F0=9F=8E=A8=EF=BC=9A=20admin?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=85=AC=E5=85=B1=E4=B8=AD=E9=97=B4=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=88=B0common?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/middleware/auth.go | 36 ++++ common/middleware/customerror.go | 50 ++++++ common/middleware/handler/auth.go | 203 ++++++++++++++++++++++ common/middleware/handler/httpshandler.go | 22 +++ common/middleware/handler/login.go | 33 ++++ common/middleware/handler/ping.go | 11 ++ common/middleware/handler/role.go | 24 +++ common/middleware/handler/user.go | 40 +++++ common/middleware/header.go | 48 +++++ common/middleware/init.go | 22 +++ common/middleware/logger.go | 132 ++++++++++++++ 11 files changed, 621 insertions(+) create mode 100644 common/middleware/auth.go create mode 100644 common/middleware/customerror.go create mode 100644 common/middleware/handler/auth.go create mode 100644 common/middleware/handler/httpshandler.go create mode 100644 common/middleware/handler/login.go create mode 100644 common/middleware/handler/ping.go create mode 100644 common/middleware/handler/role.go create mode 100644 common/middleware/handler/user.go create mode 100644 common/middleware/header.go create mode 100644 common/middleware/init.go create mode 100644 common/middleware/logger.go diff --git a/common/middleware/auth.go b/common/middleware/auth.go new file mode 100644 index 00000000..95e035a1 --- /dev/null +++ b/common/middleware/auth.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "time" + + "github.com/go-admin-team/go-admin-core/sdk/config" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/common/middleware/handler" +) + +// AuthInit jwt验证new +func AuthInit() (*jwt.GinJWTMiddleware, error) { + timeout := time.Hour + if config.ApplicationConfig.Mode == "dev" { + timeout = time.Duration(876010) * time.Hour + } else { + if config.JwtConfig.Timeout != 0 { + timeout = time.Duration(config.JwtConfig.Timeout) * time.Second + } + } + return jwt.New(&jwt.GinJWTMiddleware{ + Realm: "test zone", + Key: []byte(config.ApplicationConfig.JwtSecret), + Timeout: timeout, + MaxRefresh: time.Hour, + PayloadFunc: handler.PayloadFunc, + IdentityHandler: handler.IdentityHandler, + Authenticator: handler.Authenticator, + Authorizator: handler.Authorizator, + Unauthorized: handler.Unauthorized, + TokenLookup: "header: Authorization, query: token, cookie: jwt", + TokenHeadName: "Bearer", + TimeFunc: time.Now, + }) + +} diff --git a/common/middleware/customerror.go b/common/middleware/customerror.go new file mode 100644 index 00000000..4c56baee --- /dev/null +++ b/common/middleware/customerror.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func CustomError(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + + if c.IsAborted() { + c.Status(200) + } + switch errStr := err.(type) { + case string: + p := strings.Split(errStr, "#") + if len(p) == 3 && p[0] == "CustomError" { + statusCode, e := strconv.Atoi(p[1]) + if e != nil { + break + } + c.Status(statusCode) + fmt.Println( + time.Now().Format("2006-01-02 15:04:05"), + "[ERROR]", + c.Request.Method, + c.Request.URL, + statusCode, + c.Request.RequestURI, + c.ClientIP(), + p[2], + ) + c.JSON(http.StatusOK, gin.H{ + "code": statusCode, + "msg": p[2], + }) + } + default: + panic(err) + } + } + }() + c.Next() +} diff --git a/common/middleware/handler/auth.go b/common/middleware/handler/auth.go new file mode 100644 index 00000000..1021c59a --- /dev/null +++ b/common/middleware/handler/auth.go @@ -0,0 +1,203 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/mojocn/base64Captcha" + "github.com/mssola/user_agent" + + "go-admin/app/admin/models/system" + "go-admin/common/global" +) + +var store = base64Captcha.DefaultMemStore + +func PayloadFunc(data interface{}) jwt.MapClaims { + if v, ok := data.(map[string]interface{}); ok { + u, _ := v["user"].(SysUser) + r, _ := v["role"].(SysRole) + return jwt.MapClaims{ + jwt.IdentityKey: u.UserId, + jwt.RoleIdKey: r.RoleId, + jwt.RoleKey: r.RoleKey, + jwt.NiceKey: u.Username, + jwt.DataScopeKey: r.DataScope, + jwt.RoleNameKey: r.RoleName, + } + } + return jwt.MapClaims{} +} + +func IdentityHandler(c *gin.Context) interface{} { + claims := jwt.ExtractClaims(c) + return map[string]interface{}{ + "IdentityKey": claims["identity"], + "UserName": claims["nice"], + "RoleKey": claims["rolekey"], + "UserId": claims["identity"], + "RoleIds": claims["roleid"], + "DataScope": claims["datascope"], + } +} + +// @Summary 登陆 +// @Description 获取token +// @Description LoginHandler can be used by clients to get a jwt token. +// @Description Payload needs to be json in the form of {"username": "USERNAME", "password": "PASSWORD"}. +// @Description Reply will be of the form {"token": "TOKEN"}. +// @Description dev mode:It should be noted that all fields cannot be empty, and a value of 0 can be passed in addition to the account password +// @Description 注意:开发模式:需要注意全部字段不能为空,账号密码外可以传入0值 +// @Accept application/json +// @Product application/json +// @Param account body system.Login true "account" +// @Success 200 {string} string "{"code": 200, "expire": "2019-08-07T12:45:48+08:00", "token": ".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A" }" +// @Router /login [post] +func Authenticator(c *gin.Context) (interface{}, error) { + log := api.GetRequestLogger(c) + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db error, %s", err.Error()) + response.Error(c, http.StatusInternalServerError, err, "数据库连接获取失败") + return nil, jwt.ErrFailedAuthentication + } + + var loginVals Login + var status = "2" + var msg = "登录成功" + var username = "" + defer func() { + LoginLogToDB(c, status, msg, username) + }() + + if err = c.ShouldBind(&loginVals); err != nil { + username = loginVals.Username + msg = "数据解析失败" + status = "1" + + return nil, jwt.ErrMissingLoginValues + } + if config.ApplicationConfig.Mode != "dev" { + if !store.Verify(loginVals.UUID, loginVals.Code, true) { + username = loginVals.Username + msg = "验证码错误" + status = "1" + + return nil, jwt.ErrInvalidVerificationode + } + } + user, role, e := loginVals.GetUser(db) + if e == nil { + username = loginVals.Username + + return map[string]interface{}{"user": user, "role": role}, nil + } else { + msg = "登录失败" + status = "1" + log.Warnf("%s login failed!", loginVals.Username) + } + return nil, jwt.ErrFailedAuthentication +} + +// LoginLogToDB Write log to database +func LoginLogToDB(c *gin.Context, status string, msg string, username string) { + if !config.LoggerConfig.EnabledDB { + return + } + log := api.GetRequestLogger(c) + l := make(map[string]interface{}) + + ua := user_agent.New(c.Request.UserAgent()) + l["ipaddr"] = c.ClientIP() + l["loginLocation"] = pkg.GetLocation(c.ClientIP()) + l["loginTime"] = pkg.GetCurrentTime() + l["status"] = status + l["remark"] = c.Request.UserAgent() + browserName, browserVersion := ua.Browser() + l["browser"] = browserName + " " + browserVersion + l["os"] = ua.OS() + l["platform"] = ua.Platform() + l["username"] = username + l["msg"] = msg + + q := sdk.Runtime.GetCachePrefix(c.Request.Host) + message, err := sdk.Runtime.GetStreamMessage("", global.LoginLog, l) + if err != nil { + log.Errorf("GetStreamMessage error, %s", err.Error()) + //日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Errorf("Append message error, %s", err.Error()) + } + } +} + +// @Summary 退出登录 +// @Description 获取token +// LoginHandler can be used by clients to get a jwt token. +// Reply will be of the form {"token": "TOKEN"}. +// @Accept application/json +// @Product application/json +// @Success 200 {string} string "{"code": 200, "msg": "成功退出系统" }" +// @Router /logout [post] +// @Security Bearer +func LogOut(c *gin.Context) { + LoginLogToDB(c, "2", "退出成功", user.GetUserName(c)) + //var loginLog system.SysLoginLog + //loginLog.Ipaddr = c.ClientIP() + //location := pkg.GetLocation(c.ClientIP()) + //loginLog.LoginLocation = location + //loginLog.LoginTime = pkg.GetCurrentTime() + //loginLog.Status = "2" + //loginLog.Remark = c.Request.UserAgent() + //browserName, browserVersion := ua.Browser() + //loginLog.Browser = browserName + " " + browserVersion + //loginLog.Os = ua.OS() + //loginLog.Platform = ua.Platform() + //loginLog.Username = user.GetUserName(c) + //loginLog.Msg = "退出成功" + //db, err := pkg.GetOrm(c) + //if err != nil { + // log.Errorf("获取Orm失败, error:%s", err) + //} + //serviceLoginLog := service.SysLoginLog{} + //serviceLoginLog.Orm = db + //_ = serviceLoginLog.InsertSysLoginLog(loginLog.Generate()) + + c.JSON(http.StatusOK, gin.H{ + "code": 200, + "msg": "退出成功", + }) + +} + +func Authorizator(data interface{}, c *gin.Context) bool { + + if v, ok := data.(map[string]interface{}); ok { + u, _ := v["user"].(system.SysUser) + r, _ := v["role"].(system.SysRole) + c.Set("role", r.RoleName) + c.Set("roleIds", r.RoleId) + c.Set("userId", u.UserId) + c.Set("userName", u.Username) + c.Set("dataScope", r.DataScope) + + return true + } + return false +} + +func Unauthorized(c *gin.Context, code int, message string) { + c.JSON(http.StatusOK, gin.H{ + "code": code, + "msg": message, + }) +} diff --git a/common/middleware/handler/httpshandler.go b/common/middleware/handler/httpshandler.go new file mode 100644 index 00000000..e1274390 --- /dev/null +++ b/common/middleware/handler/httpshandler.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" + + "github.com/go-admin-team/go-admin-core/sdk/config" +) + +func TlsHandler() gin.HandlerFunc { + return func(c *gin.Context) { + secureMiddleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: config.SslConfig.Domain, + }) + err := secureMiddleware.Process(c.Writer, c.Request) + if err != nil { + return + } + c.Next() + } +} diff --git a/common/middleware/handler/login.go b/common/middleware/handler/login.go new file mode 100644 index 00000000..619ac0f5 --- /dev/null +++ b/common/middleware/handler/login.go @@ -0,0 +1,33 @@ +package handler + +import ( + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" +) + +type Login struct { + Username string `form:"UserName" json:"username" binding:"required"` + Password string `form:"Password" json:"password" binding:"required"` + Code string `form:"Code" json:"code" binding:"required"` + UUID string `form:"UUID" json:"uuid" binding:"required"` +} + +func (u *Login) GetUser(tx *gorm.DB) (user SysUser, role SysRole, err error) { + err = tx.Table("sys_user").Where("username = ? and status = 2", u.Username).First(&user).Error + if err != nil { + log.Errorf("get user error, %s", err.Error()) + return + } + _, err = pkg.CompareHashAndPassword(user.Password, u.Password) + if err != nil { + log.Errorf("user login error, %s", err.Error()) + return + } + err = tx.Table("sys_role").Where("role_id = ? ", user.RoleId).First(&role).Error + if err != nil { + log.Errorf("get role error, %s", err.Error()) + return + } + return +} diff --git a/common/middleware/handler/ping.go b/common/middleware/handler/ping.go new file mode 100644 index 00000000..ab4d645b --- /dev/null +++ b/common/middleware/handler/ping.go @@ -0,0 +1,11 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +func Ping(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "ok", + }) +} diff --git a/common/middleware/handler/role.go b/common/middleware/handler/role.go new file mode 100644 index 00000000..0895ebcd --- /dev/null +++ b/common/middleware/handler/role.go @@ -0,0 +1,24 @@ +package handler + +import "go-admin/common/models" + +type SysRole struct { + RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码 + RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称 + Status string `json:"status" gorm:"size:4;"` // + RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码 + RoleSort int `json:"roleSort" gorm:""` //角色排序 + Flag string `json:"flag" gorm:"size:128;"` // + Remark string `json:"remark" gorm:"size:255;"` //备注 + Admin bool `json:"admin" gorm:"size:4;"` + DataScope string `json:"dataScope" gorm:"size:128;"` + Params string `json:"params" gorm:"-"` + MenuIds []int `json:"menuIds" gorm:"-"` + DeptIds []int `json:"deptIds" gorm:"-"` + models.ControlBy + models.ModelTime +} + +func (SysRole) TableName() string { + return "sys_role" +} diff --git a/common/middleware/handler/user.go b/common/middleware/handler/user.go new file mode 100644 index 00000000..346d59d0 --- /dev/null +++ b/common/middleware/handler/user.go @@ -0,0 +1,40 @@ +package handler + +import ( + "go-admin/common/models" + "gorm.io/gorm" +) + +type SysUser struct { + models.ControlBy + models.ModelTime + UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"` + Username string `json:"username" gorm:"type:varchar(64);comment:用户名"` + Password string `json:"-" gorm:"type:varchar(128);comment:密码"` + NickName string `json:"nickName" gorm:"type:varchar(128);comment:昵称"` + Phone string `json:"phone" gorm:"type:varchar(11);comment:手机号"` + RoleId int `json:"roleId" gorm:"type:bigint(20);comment:角色ID"` + Salt string `json:"-" gorm:"type:varchar(255);comment:加盐"` + Avatar string `json:"avatar" gorm:"type:varchar(255);comment:头像"` + Sex string `json:"sex" gorm:"type:varchar(255);comment:性别"` + Email string `json:"email" gorm:"type:varchar(128);comment:邮箱"` + DeptId int `json:"deptId" gorm:"type:bigint(20);comment:部门"` + PostId int `json:"postId" gorm:"type:bigint(20);comment:岗位"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + Status string `json:"status" gorm:"type:varchar(4);comment:状态"` + DeptIds []int `json:"deptIds" gorm:"-"` + PostIds []int `json:"postIds" gorm:"-"` + RoleIds []int `json:"roleIds" gorm:"-"` + //Dept *SysDept `json:"dept"` +} + +func (SysUser) TableName() string { + return "sys_user" +} + +func (e *SysUser) AfterFind(_ *gorm.DB) error { + e.DeptIds = []int{e.DeptId} + e.PostIds = []int{e.PostId} + e.RoleIds = []int{e.RoleId} + return nil +} diff --git a/common/middleware/header.go b/common/middleware/header.go new file mode 100644 index 00000000..b1411e56 --- /dev/null +++ b/common/middleware/header.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +// NoCache is a middleware function that appends headers +// to prevent the client from caching the HTTP response. +func NoCache(c *gin.Context) { + c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") + c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") + c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) + c.Next() +} + +// Options is a middleware function that appends headers +// for options requests and aborts then exits the middleware +// chain and ends the request. +func Options(c *gin.Context) { + if c.Request.Method != "OPTIONS" { + c.Next() + } else { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") + c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") + c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") + c.Header("Content-Type", "application/json") + c.AbortWithStatus(200) + } +} + +// Secure is a middleware function that appends security +// and resource access headers. +func Secure(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + //c.Header("X-Frame-Options", "DENY") + c.Header("X-Content-Type-Options", "nosniff") + c.Header("X-XSS-Protection", "1; mode=block") + if c.Request.TLS != nil { + c.Header("Strict-Transport-Security", "max-age=31536000") + } + + // Also consider adding Content-Security-Policy headers + // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") +} diff --git a/common/middleware/init.go b/common/middleware/init.go new file mode 100644 index 00000000..d81a4f77 --- /dev/null +++ b/common/middleware/init.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" +) + +func InitMiddleware(r *gin.Engine) { + // 数据库链接 + r.Use(WithContextDb) + // 日志处理 + r.Use(LoggerToFile()) + // 自定义错误处理 + r.Use(CustomError) + // NoCache is a middleware function that appends headers + r.Use(NoCache) + // 跨域处理 + r.Use(Options) + // Secure is a middleware function that appends security + r.Use(Secure) + // 链路追踪 + //r.Use(middleware.Trace()) +} diff --git a/common/middleware/logger.go b/common/middleware/logger.go new file mode 100644 index 00000000..acc5952b --- /dev/null +++ b/common/middleware/logger.go @@ -0,0 +1,132 @@ +package middleware + +import ( + "encoding/json" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + + "go-admin/common/global" +) + +// LoggerToFile 日志记录到文件 +func LoggerToFile() gin.HandlerFunc { + return func(c *gin.Context) { + // 开始时间 + startTime := time.Now() + // 处理请求 + + c.Next() + // 结束时间 + endTime := time.Now() + if c.Request.Method == http.MethodOptions { + return + } + log := api.GetRequestLogger(c) + + bd, bl := c.Get("body") + var body = "" + if bl { + body = bd.(string) + } + + rt, bl := c.Get("result") + var result = "" + if bl { + rb, err := json.Marshal(rt) + if err != nil { + log.Warnf("json Marshal result error, %s", err.Error()) + } else { + result = string(rb) + } + } + + st, bl := c.Get("status") + var statusBus = 0 + if bl { + statusBus = st.(int) + } + + // 请求方式 + reqMethod := c.Request.Method + // 请求路由 + reqUri := c.Request.RequestURI + // 状态码 + statusCode := c.Writer.Status() + // 请求IP + clientIP := c.ClientIP() + // 执行时间 + latencyTime := endTime.Sub(startTime) + // 日志格式 + logData := map[string]interface{}{ + "statusCode": statusCode, + "latencyTime": latencyTime, + "clientIP": clientIP, + "method": reqMethod, + "uri": reqUri, + } + log.Info(logData) + //l := logger.Logger{Logger: log.Fields(logData)} + //l.Info(logData) + if c.Request.Method != "GET" && c.Request.Method != "OPTIONS" && config.LoggerConfig.EnabledDB { + SetDBOperLog(c, clientIP, statusCode, reqUri, reqMethod, latencyTime, body, result, statusBus) + } + } +} + +// SetDBOperLog 写入操作日志表 fixme 该方法后续即将弃用 +func SetDBOperLog(c *gin.Context, clientIP string, statusCode int, reqUri string, reqMethod string, latencyTime time.Duration, body string, result string, status int) { + log := api.GetRequestLogger(c) + l := make(map[string]interface{}) + l["_fullPath"] = c.FullPath() + l["operUrl"] = reqUri + l["method"] = reqMethod + l["operIp"] = clientIP + l["operLocation"] = pkg.GetLocation(clientIP) + l["operName"] = user.GetUserName(c) + l["requestMethod"] = c.Request.Method + l["operParam"] = body + l["operTime"] = time.Now() + if reqUri == "/login" { + l["businessType"] = "10" + l["title"] = "用户登录" + l["operName"] = "-" + } else if strings.Contains(reqUri, "/api/v1/logout") { + l["businessType"] = "11" + l["title"] = "退出登录" + } else if strings.Contains(reqUri, "/api/v1/getCaptcha") { + l["businessType"] = "12" + l["title"] = "验证码" + } else { + if reqMethod == "POST" { + l["businessType"] = "1" + } else if reqMethod == "PUT" { + l["businessType"] = "2" + } else if reqMethod == "DELETE" { + l["businessType"] = "3" + } + } + if status == http.StatusOK { + l["status"] = "2" + } else { + l["status"] = "1" + } + q := sdk.Runtime.GetCachePrefix(c.Request.Host) + message, err := sdk.Runtime.GetStreamMessage("", global.OperateLog, l) + if err != nil { + log.Errorf("GetStreamMessage error, %s", err.Error()) + //日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Errorf("Append message error, %s", err.Error()) + } + } +}