1
Some checks failed
Build / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
build / Build (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Issue Check Inactive / issue-check-inactive (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
build / Build (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Issue Check Inactive / issue-check-inactive (push) Has been cancelled
This commit is contained in:
178
common/middleware/auth.go
Normal file
178
common/middleware/auth.go
Normal file
@ -0,0 +1,178 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go-admin/common/middleware/handler"
|
||||
rediskey "go-admin/common/redis_key"
|
||||
"go-admin/common/statuscode"
|
||||
"go-admin/utils/redishelper"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
)
|
||||
|
||||
// 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.JwtConfig.Secret),
|
||||
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,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// apikey授权认证
|
||||
func FrontedAuth(c *gin.Context) {
|
||||
// 从请求头中获取 token 和 os
|
||||
apikey := c.GetHeader("x-api-key")
|
||||
// 如果 token 不存在,返回未登录的状态
|
||||
if len(apikey) == 0 {
|
||||
err := ResponseWithStatus(c, statuscode.Unauthorized)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Abort() // 停止后续中间件的执行
|
||||
return
|
||||
}
|
||||
// 验证 token 并获取结果
|
||||
key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, apikey)
|
||||
val, err := redishelper.DefaultRedis.GetString(key)
|
||||
|
||||
if err != nil || val == "" {
|
||||
ResponseWithStatus(c, statuscode.Unauthorized)
|
||||
c.Abort() // 停止后续中间件的执行
|
||||
}
|
||||
|
||||
// 将解析后的 token 设置到请求头中
|
||||
c.Set("apiKey", apikey)
|
||||
// 继续处理请求
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// ResponseWithStatus 带状态的响应
|
||||
func ResponseWithStatus(ctx *gin.Context, code int, data ...interface{}) error {
|
||||
// 获取语言对应的 msg
|
||||
// msg := statuscode.GetStatusCodeDescription(ctx, code)
|
||||
// if msg == `` {
|
||||
// msg = strconv.Itoa(code)
|
||||
// } else {
|
||||
// // 配置了语言包参数
|
||||
// if strings.Contains(msg, "%") && len(data) > 1 {
|
||||
// msg = fmt.Sprintf(msg, data[1:]...)
|
||||
// }
|
||||
// }
|
||||
|
||||
resp := statuscode.Response{
|
||||
Code: code,
|
||||
Msg: "un authorized",
|
||||
}
|
||||
|
||||
// resp.RequestID = pkg.GenerateMsgIDFromContext(ctx)
|
||||
if len(data) > 0 {
|
||||
resp.Data = data[0]
|
||||
}
|
||||
|
||||
switch code {
|
||||
case 401, 500, 405, 404:
|
||||
ctx.JSON(code, resp)
|
||||
default:
|
||||
ctx.JSON(200, resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func AuthApiInit() (*jwt.GinJWTMiddleware, error) gin.HandlerFunc {
|
||||
// return func(c *gin.Context) {
|
||||
// // 1. 从请求头或查询参数中获取 API Key
|
||||
// // 优先从 Header "X-API-Key" 获取,其次从查询参数 "api_key" 获取
|
||||
// apiKey := c.GetHeader("X-API-Key")
|
||||
// if apiKey == "" {
|
||||
// apiKey = c.Query("api_key")
|
||||
// }
|
||||
|
||||
// // 2. 检查 API Key 是否存在
|
||||
// if apiKey == "" {
|
||||
// logger.Debug("API Key not provided in header or query parameter.")
|
||||
// response.Unauthorized(c, "API Key is required.")
|
||||
// c.Abort() // 终止请求处理链
|
||||
// return
|
||||
// }
|
||||
|
||||
// // 3. 验证 API Key 的有效性
|
||||
// // 实际项目中,这里需要查询数据库或缓存来验证 API Key,并获取其关联的用户ID或权限
|
||||
// // 伪代码示例:
|
||||
// // user, isValid := service.AuthService.ValidateApiKey(apiKey)
|
||||
// // if !isValid {
|
||||
// // response.Unauthorized(c, "Invalid API Key.")
|
||||
// // c.Abort()
|
||||
// // return
|
||||
// // }
|
||||
// // c.Set("user_id", user.ID) // 将关联的用户ID设置到 Gin Context 中
|
||||
|
||||
// // 简化示例:从配置中获取一个硬编码的有效 API Key
|
||||
// // **警告:生产环境中,绝不能硬编码 API Key!这只是一个演示。**
|
||||
// // 假设 config.APIKeyConfig.ValidKeys 是一个 map[string]string,存储 apiKey -> userID 的映射
|
||||
// // 或者 config.APIKeyConfig.MasterKey 是一个单一的预设主密钥
|
||||
|
||||
// isValid := false
|
||||
// associatedUserID := "" // 存储与 API Key 关联的用户ID
|
||||
|
||||
// // 假设 API Key 存储在 config.APIKeyConfig 中,例如:
|
||||
// // type APIKeyConfig struct {
|
||||
// // MasterKey string `mapstructure:"master_key"`
|
||||
// // ValidKeys map[string]string `mapstructure:"valid_keys"` // key: API Key, value: UserID
|
||||
// // }
|
||||
// // 你可能需要根据你的 config.go 中 API Key 配置的实际结构来调整
|
||||
|
||||
// // 示例:检查是否是 MasterKey
|
||||
// if apiKey == config.APIKeyConfig.MasterKey { // 假设你有一个MasterKey
|
||||
// isValid = true
|
||||
// associatedUserID = "platform_admin" // 或者一个特殊的管理员ID
|
||||
// } else {
|
||||
// // 示例:检查是否在有效键列表中
|
||||
// for key, uid := range config.APIKeyConfig.ValidKeys { // 假设 ValidKeys 是一个 map
|
||||
// if apiKey == key {
|
||||
// isValid = true
|
||||
// associatedUserID = uid
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !isValid {
|
||||
// logger.Warnf("Attempted to use invalid API Key: %s", apiKey)
|
||||
// response.Unauthorized(c, "Invalid API Key.")
|
||||
// c.Abort()
|
||||
// return
|
||||
// }
|
||||
|
||||
// // 4. API Key 验证成功,将关联的用户ID或信息设置到 Gin Context
|
||||
// // 这样后续的业务逻辑(如翻译服务中的扣费)就可以通过 c.GetString("user_id") 获取到用户身份
|
||||
// c.Set("user_id", associatedUserID)
|
||||
// logger.Debugf("API Key authenticated successfully for user: %s", associatedUserID)
|
||||
|
||||
// // 5. 继续处理请求
|
||||
// c.Next()
|
||||
// }
|
||||
// }
|
||||
61
common/middleware/customerror.go
Normal file
61
common/middleware/customerror.go
Normal file
@ -0,0 +1,61 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"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],
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 500,
|
||||
"msg": errStr,
|
||||
})
|
||||
}
|
||||
case runtime.Error:
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 500,
|
||||
"msg": errStr.Error(),
|
||||
})
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
11
common/middleware/db.go
Normal file
11
common/middleware/db.go
Normal file
@ -0,0 +1,11 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
)
|
||||
|
||||
func WithContextDb(c *gin.Context) {
|
||||
c.Set("db", sdk.Runtime.GetDbByKey(c.Request.Host).WithContext(c))
|
||||
c.Next()
|
||||
}
|
||||
29
common/middleware/demo.go
Normal file
29
common/middleware/demo.go
Normal file
@ -0,0 +1,29 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DemoEvn() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
if config.ApplicationConfig.Mode == "demo" {
|
||||
if method == "GET" ||
|
||||
method == "OPTIONS" ||
|
||||
c.Request.RequestURI == "/api/v1/login" ||
|
||||
c.Request.RequestURI == "/api/v1/logout" {
|
||||
c.Next()
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 500,
|
||||
"msg": "谢谢您的参与,但为了大家更好的体验,所以本次提交就算了吧!\U0001F600\U0001F600\U0001F600",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
182
common/middleware/handler/auth.go
Normal file
182
common/middleware/handler/auth.go
Normal file
@ -0,0 +1,182 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/common"
|
||||
"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"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/captcha"
|
||||
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/mssola/user_agent"
|
||||
"go-admin/common/global"
|
||||
)
|
||||
|
||||
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"],
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticator 获取token
|
||||
// @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值
|
||||
// @Tags 登陆
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param account body Login true "account"
|
||||
// @Success 200 {string} string "{"code": 200, "expire": "2019-08-07T12:45:48+08:00", "token": ".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A" }"
|
||||
// @Router /api/v1/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, 500, 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 !captcha.Verify(loginVals.UUID, loginVals.Code, true) {
|
||||
username = loginVals.Username
|
||||
msg = "验证码错误"
|
||||
status = "1"
|
||||
|
||||
return nil, jwt.ErrInvalidVerificationode
|
||||
}
|
||||
}
|
||||
sysUser, role, e := loginVals.GetUser(db)
|
||||
if e == nil {
|
||||
username = loginVals.Username
|
||||
|
||||
return map[string]interface{}{"user": sysUser, "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"] = common.GetClientIP(c)
|
||||
l["loginLocation"] = "" // pkg.GetLocation(common.GetClientIP(c),gaConfig.ExtConfig.AMap.Key)
|
||||
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.GetMemoryQueue(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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LogOut
|
||||
// @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))
|
||||
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"].(models.SysUser)
|
||||
r, _ := v["role"].(models.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,
|
||||
})
|
||||
}
|
||||
22
common/middleware/handler/httpshandler.go
Normal file
22
common/middleware/handler/httpshandler.go
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
33
common/middleware/handler/login.go
Normal file
33
common/middleware/handler/login.go
Normal file
@ -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
|
||||
}
|
||||
11
common/middleware/handler/ping.go
Normal file
11
common/middleware/handler/ping.go
Normal file
@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Ping(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
24
common/middleware/handler/role.go
Normal file
24
common/middleware/handler/role.go
Normal file
@ -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"
|
||||
}
|
||||
40
common/middleware/handler/user.go
Normal file
40
common/middleware/handler/user.go
Normal file
@ -0,0 +1,40 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"go-admin/common/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SysUser struct {
|
||||
UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"`
|
||||
Username string `json:"username" gorm:"size:64;comment:用户名"`
|
||||
Password string `json:"-" gorm:"size:128;comment:密码"`
|
||||
NickName string `json:"nickName" gorm:"size:128;comment:昵称"`
|
||||
Phone string `json:"phone" gorm:"size:11;comment:手机号"`
|
||||
RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"`
|
||||
Salt string `json:"-" gorm:"size:255;comment:加盐"`
|
||||
Avatar string `json:"avatar" gorm:"size:255;comment:头像"`
|
||||
Sex string `json:"sex" gorm:"size:255;comment:性别"`
|
||||
Email string `json:"email" gorm:"size:128;comment:邮箱"`
|
||||
DeptId int `json:"deptId" gorm:"size:20;comment:部门"`
|
||||
PostId int `json:"postId" gorm:"size:20;comment:岗位"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:备注"`
|
||||
Status string `json:"status" gorm:"size:4;comment:状态"`
|
||||
DeptIds []int `json:"deptIds" gorm:"-"`
|
||||
PostIds []int `json:"postIds" gorm:"-"`
|
||||
RoleIds []int `json:"roleIds" gorm:"-"`
|
||||
//Dept *SysDept `json:"dept"`
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
48
common/middleware/header.go
Normal file
48
common/middleware/header.go
Normal file
@ -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")
|
||||
}
|
||||
35
common/middleware/init.go
Normal file
35
common/middleware/init.go
Normal file
@ -0,0 +1,35 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
"go-admin/common/actions"
|
||||
)
|
||||
|
||||
const (
|
||||
JwtTokenCheck string = "JwtToken"
|
||||
RoleCheck string = "AuthCheckRole"
|
||||
PermissionCheck string = "PermissionAction"
|
||||
)
|
||||
|
||||
func InitMiddleware(r *gin.Engine) {
|
||||
r.Use(DemoEvn())
|
||||
// 数据库链接
|
||||
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())
|
||||
sdk.Runtime.SetMiddleware(JwtTokenCheck, (*jwt.GinJWTMiddleware).MiddlewareFunc)
|
||||
sdk.Runtime.SetMiddleware(RoleCheck, AuthCheckRole())
|
||||
sdk.Runtime.SetMiddleware(PermissionCheck, actions.PermissionAction())
|
||||
}
|
||||
136
common/middleware/logger.go
Normal file
136
common/middleware/logger.go
Normal file
@ -0,0 +1,136 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"go-admin/app/admin/service/dto"
|
||||
"go-admin/common"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"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/jwtauth/user"
|
||||
|
||||
"go-admin/common/global"
|
||||
)
|
||||
|
||||
// LoggerToFile 日志记录到文件
|
||||
func LoggerToFile() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
log := api.GetRequestLogger(c)
|
||||
// 开始时间
|
||||
startTime := time.Now()
|
||||
// 处理请求
|
||||
var body string
|
||||
switch c.Request.Method {
|
||||
case http.MethodPost, http.MethodPut, http.MethodGet, http.MethodDelete:
|
||||
bf := bytes.NewBuffer(nil)
|
||||
wt := bufio.NewWriter(bf)
|
||||
_, err := io.Copy(wt, c.Request.Body)
|
||||
if err != nil {
|
||||
log.Warnf("copy body error, %s", err.Error())
|
||||
err = nil
|
||||
}
|
||||
rb, _ := ioutil.ReadAll(bf)
|
||||
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(rb))
|
||||
body = string(rb)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
url := c.Request.RequestURI
|
||||
if strings.Index(url, "logout") > -1 ||
|
||||
strings.Index(url, "login") > -1 {
|
||||
return
|
||||
}
|
||||
// 结束时间
|
||||
endTime := time.Now()
|
||||
if c.Request.Method == http.MethodOptions {
|
||||
return
|
||||
}
|
||||
|
||||
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 := common.GetClientIP(c)
|
||||
// 执行时间
|
||||
latencyTime := endTime.Sub(startTime)
|
||||
// 日志格式
|
||||
logData := map[string]interface{}{
|
||||
"statusCode": statusCode,
|
||||
"latencyTime": latencyTime,
|
||||
"clientIP": clientIP,
|
||||
"method": reqMethod,
|
||||
"uri": reqUri,
|
||||
}
|
||||
log.WithFields(logData).Info()
|
||||
|
||||
if c.Request.Method != "OPTIONS" && config.LoggerConfig.EnabledDB && statusCode != 404 {
|
||||
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["operIp"] = clientIP
|
||||
l["operLocation"] = "" // pkg.GetLocation(clientIP, gaConfig.ExtConfig.AMap.Key)
|
||||
l["operName"] = user.GetUserName(c)
|
||||
l["requestMethod"] = reqMethod
|
||||
l["operParam"] = body
|
||||
l["operTime"] = time.Now()
|
||||
l["jsonResult"] = result
|
||||
l["latencyTime"] = latencyTime.String()
|
||||
l["statusCode"] = statusCode
|
||||
l["userAgent"] = c.Request.UserAgent()
|
||||
l["createBy"] = user.GetUserId(c)
|
||||
l["updateBy"] = user.GetUserId(c)
|
||||
if status == http.StatusOK {
|
||||
l["status"] = dto.OperaStatusEnabel
|
||||
} else {
|
||||
l["status"] = dto.OperaStatusDisable
|
||||
}
|
||||
q := sdk.Runtime.GetMemoryQueue(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())
|
||||
}
|
||||
}
|
||||
}
|
||||
61
common/middleware/permission.go
Normal file
61
common/middleware/permission.go
Normal file
@ -0,0 +1,61 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/casbin/casbin/v2/util"
|
||||
"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/pkg/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
)
|
||||
|
||||
// AuthCheckRole 权限检查中间件
|
||||
func AuthCheckRole() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
log := api.GetRequestLogger(c)
|
||||
data, _ := c.Get(jwtauth.JwtPayloadKey)
|
||||
v := data.(jwtauth.MapClaims)
|
||||
e := sdk.Runtime.GetCasbinKey(c.Request.Host)
|
||||
var res, casbinExclude bool
|
||||
var err error
|
||||
//检查权限
|
||||
if v["rolekey"] == "admin" {
|
||||
res = true
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
for _, i := range CasbinExclude {
|
||||
if util.KeyMatch2(c.Request.URL.Path, i.Url) && c.Request.Method == i.Method {
|
||||
casbinExclude = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if casbinExclude {
|
||||
log.Infof("Casbin exclusion, no validation method:%s path:%s", c.Request.Method, c.Request.URL.Path)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
res, err = e.Enforce(v["rolekey"], c.Request.URL.Path, c.Request.Method)
|
||||
if err != nil {
|
||||
log.Errorf("AuthCheckRole error:%s method:%s path:%s", err, c.Request.Method, c.Request.URL.Path)
|
||||
response.Error(c, 500, err, "")
|
||||
return
|
||||
}
|
||||
|
||||
if res {
|
||||
log.Infof("isTrue: %v role: %s method: %s path: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path)
|
||||
c.Next()
|
||||
} else {
|
||||
log.Warnf("isTrue: %v role: %s method: %s path: %s message: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path, "当前request无权限,请管理员确认!")
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 403,
|
||||
"msg": "对不起,您没有该接口访问权限,请联系管理员",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
36
common/middleware/request_id.go
Normal file
36
common/middleware/request_id.go
Normal file
@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// RequestId 自动增加requestId
|
||||
func RequestId(trafficKey string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.Method == http.MethodOptions {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
requestId := c.GetHeader(trafficKey)
|
||||
if requestId == "" {
|
||||
requestId = c.GetHeader(strings.ToLower(trafficKey))
|
||||
}
|
||||
if requestId == "" {
|
||||
requestId = uuid.New().String()
|
||||
}
|
||||
c.Request.Header.Set(trafficKey, requestId)
|
||||
c.Set(trafficKey, requestId)
|
||||
c.Set(pkg.LoggerKey,
|
||||
logger.NewHelper(logger.DefaultLogger).
|
||||
WithFields(map[string]interface{}{
|
||||
trafficKey: requestId,
|
||||
}))
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
30
common/middleware/sentinel.go
Normal file
30
common/middleware/sentinel.go
Normal file
@ -0,0 +1,30 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/alibaba/sentinel-golang/core/system"
|
||||
sentinel "github.com/alibaba/sentinel-golang/pkg/adapters/gin"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
)
|
||||
|
||||
// Sentinel 限流
|
||||
func Sentinel() gin.HandlerFunc {
|
||||
if _, err := system.LoadRules([]*system.Rule{
|
||||
{
|
||||
MetricType: system.InboundQPS,
|
||||
TriggerCount: 200,
|
||||
Strategy: system.BBR,
|
||||
},
|
||||
}); err != nil {
|
||||
log.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
return sentinel.SentinelMiddleware(
|
||||
sentinel.WithBlockFallback(func(ctx *gin.Context) {
|
||||
ctx.AbortWithStatusJSON(200, map[string]interface{}{
|
||||
"msg": "too many request; the quota used up!",
|
||||
"code": 500,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
43
common/middleware/settings.go
Normal file
43
common/middleware/settings.go
Normal file
@ -0,0 +1,43 @@
|
||||
package middleware
|
||||
|
||||
type UrlInfo struct {
|
||||
Url string
|
||||
Method string
|
||||
}
|
||||
|
||||
// CasbinExclude casbin 排除的路由列表
|
||||
var CasbinExclude = []UrlInfo{
|
||||
{Url: "/api/v1/dict/type-option-select", Method: "GET"},
|
||||
{Url: "/api/v1/dict-data/option-select", Method: "GET"},
|
||||
{Url: "/api/v1/deptTree", Method: "GET"},
|
||||
{Url: "/api/v1/db/tables/page", Method: "GET"},
|
||||
{Url: "/api/v1/db/columns/page", Method: "GET"},
|
||||
{Url: "/api/v1/gen/toproject/:tableId", Method: "GET"},
|
||||
{Url: "/api/v1/gen/todb/:tableId", Method: "GET"},
|
||||
{Url: "/api/v1/gen/tabletree", Method: "GET"},
|
||||
{Url: "/api/v1/gen/preview/:tableId", Method: "GET"},
|
||||
{Url: "/api/v1/gen/apitofile/:tableId", Method: "GET"},
|
||||
{Url: "/api/v1/getCaptcha", Method: "GET"},
|
||||
{Url: "/api/v1/getinfo", Method: "GET"},
|
||||
{Url: "/api/v1/menuTreeselect", Method: "GET"},
|
||||
{Url: "/api/v1/menurole", Method: "GET"},
|
||||
{Url: "/api/v1/menuids", Method: "GET"},
|
||||
{Url: "/api/v1/roleMenuTreeselect/:roleId", Method: "GET"},
|
||||
{Url: "/api/v1/roleDeptTreeselect/:roleId", Method: "GET"},
|
||||
{Url: "/api/v1/refresh_token", Method: "GET"},
|
||||
{Url: "/api/v1/configKey/:configKey", Method: "GET"},
|
||||
{Url: "/api/v1/app-config", Method: "GET"},
|
||||
{Url: "/api/v1/user/profile", Method: "GET"},
|
||||
{Url: "/info", Method: "GET"},
|
||||
{Url: "/api/v1/login", Method: "POST"},
|
||||
{Url: "/api/v1/logout", Method: "POST"},
|
||||
{Url: "/api/v1/user/avatar", Method: "POST"},
|
||||
{Url: "/api/v1/user/pwd", Method: "PUT"},
|
||||
{Url: "/api/v1/metrics", Method: "GET"},
|
||||
{Url: "/api/v1/health", Method: "GET"},
|
||||
{Url: "/", Method: "GET"},
|
||||
{Url: "/api/v1/server-monitor", Method: "GET"},
|
||||
{Url: "/api/v1/public/uploadFile", Method: "POST"},
|
||||
{Url: "/api/v1/user/pwd/set", Method: "PUT"},
|
||||
{Url: "/api/v1/sys-user", Method: "PUT"},
|
||||
}
|
||||
27
common/middleware/trace.go
Normal file
27
common/middleware/trace.go
Normal file
@ -0,0 +1,27 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
// Trace 链路追踪
|
||||
func Trace() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
var sp opentracing.Span
|
||||
opName := ctx.Request.URL.Path
|
||||
// Attempt to join a trace by getting trace context from the headers.
|
||||
wireContext, err := opentracing.GlobalTracer().Extract(
|
||||
opentracing.TextMap,
|
||||
opentracing.HTTPHeadersCarrier(ctx.Request.Header))
|
||||
if err != nil {
|
||||
// If for whatever reason we can't join, go ahead and start a new root span.
|
||||
sp = opentracing.StartSpan(opName)
|
||||
} else {
|
||||
sp = opentracing.StartSpan(opName, opentracing.ChildOf(wireContext))
|
||||
}
|
||||
ctx.Set("traceSpan", sp)
|
||||
ctx.Next()
|
||||
sp.Finish()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user