更新前后端代码
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"filestoragesystem/internal/model"
|
||||
"filestoragesystem/internal/service"
|
||||
"filestoragesystem/internal/utils"
|
||||
"filestoragesystem/pkg/apperr"
|
||||
)
|
||||
|
||||
// 上下文键
|
||||
const (
|
||||
CtxUser = "authUser" // *model.User
|
||||
CtxUserID = "authUserID" // uint
|
||||
CtxUsername = "authUsername" // string
|
||||
CtxRoleCode = "authRoleCode" // string
|
||||
CtxPermissions = "authPermissions" // map[string]bool
|
||||
)
|
||||
|
||||
// Auth 认证中间件:支持 JWT Bearer 与 API Key HMAC-SHA256签名两种方式
|
||||
func Auth(authSvc *service.AuthService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var user *model.User
|
||||
|
||||
// 1. JWT Bearer Token
|
||||
header := c.GetHeader("Authorization")
|
||||
if strings.HasPrefix(header, "Bearer ") {
|
||||
claims, err := utils.ParseToken(strings.TrimPrefix(header, "Bearer "))
|
||||
if err != nil {
|
||||
utils.Unauthorized(c, "Token无效或已过期")
|
||||
return
|
||||
}
|
||||
u, err := authSvc.LoadUserByID(claims.UserID)
|
||||
if err != nil {
|
||||
utils.Unauthorized(c, apperr.ErrUnauthorized.Error())
|
||||
return
|
||||
}
|
||||
user = u
|
||||
} else if ak := c.GetHeader("X-Access-Key"); ak != "" {
|
||||
// 2. API Key + Secret HMAC-SHA256签名
|
||||
u, err := authSvc.VerifyAPIKeySignature(
|
||||
ak,
|
||||
c.GetHeader("X-Timestamp"),
|
||||
c.GetHeader("X-Nonce"),
|
||||
c.GetHeader("X-Signature"),
|
||||
c.Request.Method,
|
||||
c.Request.URL.Path,
|
||||
)
|
||||
if err != nil {
|
||||
utils.Unauthorized(c, err.Error())
|
||||
return
|
||||
}
|
||||
user = u
|
||||
} else {
|
||||
utils.Unauthorized(c, "缺少认证信息")
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
utils.Unauthorized(c, apperr.ErrUnauthorized.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Set(CtxUser, user)
|
||||
c.Set(CtxUserID, user.ID)
|
||||
c.Set(CtxUsername, user.Username)
|
||||
roleCode := ""
|
||||
if user.Role != nil {
|
||||
roleCode = user.Role.Code
|
||||
}
|
||||
c.Set(CtxRoleCode, roleCode)
|
||||
c.Set(CtxPermissions, service.PermissionCodesOf(user))
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// CurrentUser 从上下文获取当前用户
|
||||
func CurrentUser(c *gin.Context) *model.User {
|
||||
if v, ok := c.Get(CtxUser); ok {
|
||||
if u, ok := v.(*model.User); ok {
|
||||
return u
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CurrentUserID 当前用户ID
|
||||
func CurrentUserID(c *gin.Context) uint {
|
||||
if v, ok := c.Get(CtxUserID); ok {
|
||||
if id, ok := v.(uint); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsSuperAdmin 是否超级管理员
|
||||
func IsSuperAdmin(c *gin.Context) bool {
|
||||
return c.GetString(CtxRoleCode) == "super_admin"
|
||||
}
|
||||
|
||||
// IsAdmin 是否管理员(super_admin或admin)
|
||||
func IsAdmin(c *gin.Context) bool {
|
||||
rc := c.GetString(CtxRoleCode)
|
||||
return rc == "super_admin" || rc == "admin"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CORS 跨域中间件
|
||||
func CORS() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
origin := c.GetHeader("Origin")
|
||||
if origin != "" {
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
c.Header("Access-Control-Allow-Headers",
|
||||
"Origin, Content-Type, Accept, Authorization, X-Access-Key, X-Timestamp, X-Nonce, X-Signature, X-Request-Id")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
|
||||
c.Header("Access-Control-Max-Age", "86400")
|
||||
}
|
||||
if c.Request.Method == http.MethodOptions {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"filestoragesystem/internal/utils"
|
||||
)
|
||||
|
||||
// RequestID 请求ID中间件
|
||||
func RequestID() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id := c.GetHeader("X-Request-Id")
|
||||
if id == "" {
|
||||
id = utils.RandomKey(8)
|
||||
}
|
||||
c.Set("requestID", id)
|
||||
c.Header("X-Request-Id", id)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Logger 访问日志中间件
|
||||
func Logger() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
|
||||
c.Next()
|
||||
|
||||
latency := time.Since(start)
|
||||
status := c.Writer.Status()
|
||||
|
||||
args := []interface{}{
|
||||
"status", status,
|
||||
"method", c.Request.Method,
|
||||
"path", path,
|
||||
"ip", utils.ClientIP(c),
|
||||
"latency", latency.String(),
|
||||
"request_id", c.GetString("requestID"),
|
||||
}
|
||||
if uid := CurrentUserID(c); uid > 0 {
|
||||
args = append(args, "user_id", uid)
|
||||
}
|
||||
if err := c.Errors.ByType(gin.ErrorTypePrivate).Last(); err != nil {
|
||||
args = append(args, "error", err.Error())
|
||||
}
|
||||
|
||||
sugar := utils.Logger().Sugar()
|
||||
switch {
|
||||
case status >= 500:
|
||||
sugar.Errorw("access", args...)
|
||||
case status >= 400:
|
||||
sugar.Warnw("access", args...)
|
||||
default:
|
||||
sugar.Infow("access", args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"filestoragesystem/internal/utils"
|
||||
)
|
||||
|
||||
// RequirePermission 权限检查中间件;super_admin拥有全部权限
|
||||
func RequirePermission(code string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
perms, ok := c.Get(CtxPermissions)
|
||||
if !ok {
|
||||
utils.Unauthorized(c, "未认证")
|
||||
return
|
||||
}
|
||||
m, ok := perms.(map[string]bool)
|
||||
if !ok {
|
||||
utils.Forbidden(c, "权限信息异常")
|
||||
return
|
||||
}
|
||||
if m["*"] || m[code] {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
utils.Forbidden(c, "没有操作权限: "+code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"filestoragesystem/internal/repository"
|
||||
"filestoragesystem/internal/utils"
|
||||
)
|
||||
|
||||
// RateLimit 令牌桶限流中间件
|
||||
// 限额从系统设置api_rate_limit读取(次/分钟),按用户ID(已认证)或IP进行限制
|
||||
func RateLimit(settingRepo *repository.SettingRepo) gin.HandlerFunc {
|
||||
var mu sync.Mutex
|
||||
buckets := make(map[string]*rate.Limiter)
|
||||
lastClean := time.Now()
|
||||
|
||||
getLimit := func() int {
|
||||
if v, err := settingRepo.GetValue("api_rate_limit"); err == nil {
|
||||
var n int
|
||||
if _, e := fmt.Sscanf(v, "%d", &n); e == nil && n > 0 {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 100
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
// 管理员后台不限流
|
||||
if IsAdmin(c) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
key := utils.ClientIP(c)
|
||||
if uid := CurrentUserID(c); uid > 0 {
|
||||
key = fmt.Sprintf("user:%d", uid)
|
||||
}
|
||||
|
||||
limit := getLimit()
|
||||
mu.Lock()
|
||||
// 定期清理空闲桶
|
||||
if time.Since(lastClean) > 10*time.Minute {
|
||||
buckets = make(map[string]*rate.Limiter)
|
||||
lastClean = time.Now()
|
||||
}
|
||||
limiter, ok := buckets[key]
|
||||
if !ok {
|
||||
// 每分钟limit次:速率=limit/60每秒,突发=limit
|
||||
limiter = rate.NewLimiter(rate.Limit(float64(limit)/60.0), limit)
|
||||
buckets[key] = limiter
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
if !limiter.Allow() {
|
||||
c.Header("Retry-After", "60")
|
||||
c.AbortWithStatusJSON(http.StatusTooManyRequests, utils.Response{
|
||||
Code: 429, Message: "请求过于频繁,请稍后再试",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"filestoragesystem/internal/service"
|
||||
"filestoragesystem/internal/utils"
|
||||
)
|
||||
|
||||
// SiteStatus 站点状态中间件
|
||||
// 站点关闭(维护模式)时:放行状态接口/登录接口/管理员请求,其余返回503
|
||||
func SiteStatus(adminSvc *service.AdminService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if adminSvc.IsSiteEnabled() {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 放行的路径
|
||||
path := c.Request.URL.Path
|
||||
if path == "/api/v1/status" || path == "/api/v1/auth/login" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 已认证的管理员放行(需在Auth之后挂载)
|
||||
if IsAdmin(c) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
msg := "系统维护中,请稍后再访问"
|
||||
if m, ok := adminSvc.SiteStatus()["maintenance_msg"].(string); ok && m != "" {
|
||||
msg = m
|
||||
}
|
||||
c.AbortWithStatusJSON(http.StatusServiceUnavailable, utils.Response{
|
||||
Code: http.StatusServiceUnavailable, Message: msg,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user