后端重构
This commit is contained in:
@@ -24,7 +24,7 @@ func SetCasbinConfig(cfg *CasbinConfig) {
|
||||
casbinConfig = cfg
|
||||
}
|
||||
|
||||
// CasbinHandler Casbin权限拦截器(与 gva 保持一致)
|
||||
// CasbinHandler Casbin权限拦截器(与 kra 保持一致)
|
||||
func CasbinHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
waitUse, _ := utils.GetClaims(c)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GinCORSWhitelist Gin CORS白名单配置
|
||||
type GinCORSWhitelist struct {
|
||||
AllowOrigin string
|
||||
AllowHeaders string
|
||||
AllowMethods string
|
||||
ExposeHeaders string
|
||||
AllowCredentials bool
|
||||
}
|
||||
|
||||
// GinCORSConfig Gin CORS配置
|
||||
type GinCORSConfig struct {
|
||||
// Mode 模式: allow-all, whitelist, strict-whitelist
|
||||
Mode string
|
||||
Whitelist []GinCORSWhitelist
|
||||
}
|
||||
|
||||
// 全局CORS配置
|
||||
var ginCorsConfig *GinCORSConfig
|
||||
|
||||
// SetGinCORSConfig 设置Gin CORS配置
|
||||
func SetGinCORSConfig(cfg *GinCORSConfig) {
|
||||
ginCorsConfig = cfg
|
||||
}
|
||||
|
||||
// CorsByRules 按照配置处理跨域请求(与 kra 保持一致)
|
||||
func CorsByRules() gin.HandlerFunc {
|
||||
// 放行全部
|
||||
if ginCorsConfig == nil || ginCorsConfig.Mode == "allow-all" {
|
||||
return Cors()
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
origin := c.GetHeader("origin")
|
||||
whitelist := checkGinCors(origin)
|
||||
|
||||
// 通过检查, 添加请求头
|
||||
if whitelist != nil {
|
||||
c.Header("Access-Control-Allow-Origin", whitelist.AllowOrigin)
|
||||
c.Header("Access-Control-Allow-Headers", whitelist.AllowHeaders)
|
||||
c.Header("Access-Control-Allow-Methods", whitelist.AllowMethods)
|
||||
c.Header("Access-Control-Expose-Headers", whitelist.ExposeHeaders)
|
||||
if whitelist.AllowCredentials {
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
}
|
||||
|
||||
// 严格白名单模式且未通过检查,直接拒绝处理请求
|
||||
if whitelist == nil && ginCorsConfig.Mode == "strict-whitelist" {
|
||||
// 健康检查放行
|
||||
if !(c.Request.Method == "GET" && c.Request.URL.Path == "/health") {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 非严格白名单模式,无论是否通过检查均放行所有 OPTIONS 方法
|
||||
if c.Request.Method == http.MethodOptions {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func checkGinCors(currentOrigin string) *GinCORSWhitelist {
|
||||
if ginCorsConfig == nil {
|
||||
return nil
|
||||
}
|
||||
for _, whitelist := range ginCorsConfig.Whitelist {
|
||||
// 遍历配置中的跨域头,寻找匹配项
|
||||
if currentOrigin == whitelist.AllowOrigin {
|
||||
return &whitelist
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// EmailSender 邮件发送接口
|
||||
type EmailSender interface {
|
||||
ErrorToEmail(subject, body string) error
|
||||
}
|
||||
|
||||
// UserFinder 用户查找接口
|
||||
type UserFinder interface {
|
||||
FindUsernameByID(id int) (string, error)
|
||||
}
|
||||
|
||||
// EmailConfig 邮件中间件配置
|
||||
type EmailConfig struct {
|
||||
EmailSender EmailSender
|
||||
UserFinder UserFinder
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
// 全局邮件配置
|
||||
var emailConfig *EmailConfig
|
||||
|
||||
// SetEmailConfig 设置邮件配置
|
||||
func SetEmailConfig(cfg *EmailConfig) {
|
||||
emailConfig = cfg
|
||||
}
|
||||
|
||||
// ErrorToEmail 错误发送邮件中间件(与 kra 保持一致)
|
||||
func ErrorToEmail() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var username string
|
||||
|
||||
// 获取用户信息
|
||||
claims, _ := utils.GetClaims(c)
|
||||
if claims != nil && claims.Username != "" {
|
||||
username = claims.Username
|
||||
} else {
|
||||
id, _ := strconv.Atoi(c.Request.Header.Get("x-user-id"))
|
||||
if id > 0 && emailConfig != nil && emailConfig.UserFinder != nil {
|
||||
if name, err := emailConfig.UserFinder.FindUsernameByID(id); err == nil {
|
||||
username = name
|
||||
} else {
|
||||
username = "Unknown"
|
||||
}
|
||||
} else {
|
||||
username = "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// 读取请求体
|
||||
body, _ := io.ReadAll(c.Request.Body)
|
||||
// 将原body塞回去
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
|
||||
// 记录请求信息
|
||||
record := struct {
|
||||
IP string
|
||||
Method string
|
||||
Path string
|
||||
Agent string
|
||||
Body string
|
||||
}{
|
||||
IP: c.ClientIP(),
|
||||
Method: c.Request.Method,
|
||||
Path: c.Request.URL.Path,
|
||||
Agent: c.Request.UserAgent(),
|
||||
Body: string(body),
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
c.Next()
|
||||
|
||||
latency := time.Since(now)
|
||||
status := c.Writer.Status()
|
||||
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||
|
||||
// 构建邮件内容
|
||||
str := "接收到的请求为" + record.Body + "\n" +
|
||||
"请求方式为" + record.Method + "\n" +
|
||||
"报错信息如下" + errorMessage + "\n" +
|
||||
"耗时" + latency.String() + "\n"
|
||||
|
||||
// 如果状态码不是200,发送邮件
|
||||
if status != 200 {
|
||||
subject := username + " " + record.IP + "调用了" + record.Path + "报错了"
|
||||
if emailConfig != nil && emailConfig.EmailSender != nil {
|
||||
if err := emailConfig.EmailSender.ErrorToEmail(subject, str); err != nil {
|
||||
if emailConfig.Logger != nil {
|
||||
emailConfig.Logger.Error("ErrorToEmail Failed, err:", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func SetJWTConfig(cfg *JWTConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// JWTAuth JWT认证中间件(与 gva 保持一致)
|
||||
// JWTAuth JWT认证中间件(与 kra 保持一致)
|
||||
func JWTAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// LimitConfig IP限流配置
|
||||
@@ -25,12 +26,18 @@ type LimitConfig struct {
|
||||
}
|
||||
|
||||
var redisClient redis.UniversalClient
|
||||
var limitLogger *zap.Logger
|
||||
|
||||
// SetRedisClient 设置Redis客户端
|
||||
func SetRedisClient(client redis.UniversalClient) {
|
||||
redisClient = client
|
||||
}
|
||||
|
||||
// SetLimitLogger 设置限流日志
|
||||
func SetLimitLogger(logger *zap.Logger) {
|
||||
limitLogger = logger
|
||||
}
|
||||
|
||||
// LimitWithTime 返回限流中间件
|
||||
func (l LimitConfig) LimitWithTime() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
@@ -53,7 +60,13 @@ func DefaultCheckOrMark(key string, expire int, limit int) error {
|
||||
if redisClient == nil {
|
||||
return nil // Redis未配置,跳过限流
|
||||
}
|
||||
return SetLimitWithTime(key, limit, time.Duration(expire)*time.Second)
|
||||
if err := SetLimitWithTime(key, limit, time.Duration(expire)*time.Second); err != nil {
|
||||
if limitLogger != nil {
|
||||
limitLogger.Error("limit", zap.Error(err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultLimit 默认限流中间件
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/unrolled/secure"
|
||||
)
|
||||
|
||||
// TLSConfig TLS配置
|
||||
type TLSConfig struct {
|
||||
// SSLHost SSL主机地址(例如:localhost:443)
|
||||
SSLHost string
|
||||
// SSLRedirect 是否重定向到HTTPS
|
||||
SSLRedirect bool
|
||||
}
|
||||
|
||||
// 默认TLS配置
|
||||
var defaultTLSConfig = TLSConfig{
|
||||
SSLHost: "localhost:443",
|
||||
SSLRedirect: true,
|
||||
}
|
||||
|
||||
// SetTLSConfig 设置TLS配置
|
||||
func SetTLSConfig(cfg TLSConfig) {
|
||||
defaultTLSConfig = cfg
|
||||
}
|
||||
|
||||
// LoadTls TLS加载中间件(与 kra 保持一致)
|
||||
// 用https把这个中间件在router里面use一下就好
|
||||
func LoadTls() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
middleware := secure.New(secure.Options{
|
||||
SSLRedirect: defaultTLSConfig.SSLRedirect,
|
||||
SSLHost: defaultTLSConfig.SSLHost,
|
||||
})
|
||||
err := middleware.Process(c.Writer, c.Request)
|
||||
if err != nil {
|
||||
// 如果出现错误,请不要继续
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// 继续往下处理
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// LoadTlsWithConfig 使用自定义配置的TLS加载中间件
|
||||
func LoadTlsWithConfig(cfg TLSConfig) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
middleware := secure.New(secure.Options{
|
||||
SSLRedirect: cfg.SSLRedirect,
|
||||
SSLHost: cfg.SSLHost,
|
||||
})
|
||||
err := middleware.Process(c.Writer, c.Request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// OperationRecord 操作记录结构
|
||||
type OperationRecord struct {
|
||||
// SysOperationRecord 操作记录结构
|
||||
type SysOperationRecord struct {
|
||||
IP string `json:"ip"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
@@ -42,7 +42,7 @@ type OperationRecord struct {
|
||||
|
||||
// OperationRecordCreator 操作记录创建接口
|
||||
type OperationRecordCreator interface {
|
||||
CreateOperationRecord(record *OperationRecord) error
|
||||
CreateOperationRecord(record *SysOperationRecord) error
|
||||
}
|
||||
|
||||
// 全局操作记录创建器
|
||||
@@ -59,7 +59,13 @@ func SetOperationLogger(logger *zap.Logger) {
|
||||
operationLogger = logger
|
||||
}
|
||||
|
||||
// OperationRecordMiddleware 操作记录中间件(与 gva 保持一致)
|
||||
// OperationRecord 操作记录中间件(与 kra 保持一致的命名)
|
||||
// 别名函数,保持与 KRA 的兼容性
|
||||
func OperationRecord() gin.HandlerFunc {
|
||||
return OperationRecordMiddleware()
|
||||
}
|
||||
|
||||
// OperationRecordMiddleware 操作记录中间件(与 kra 保持一致)
|
||||
func OperationRecordMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var body []byte
|
||||
@@ -100,7 +106,7 @@ func OperationRecordMiddleware() gin.HandlerFunc {
|
||||
userId = id
|
||||
}
|
||||
|
||||
record := OperationRecord{
|
||||
record := SysOperationRecord{
|
||||
IP: c.ClientIP(),
|
||||
Method: c.Request.Method,
|
||||
Path: c.Request.URL.Path,
|
||||
|
||||
@@ -22,7 +22,7 @@ func SetRecoveryLogger(logger *zap.Logger) {
|
||||
recoveryLogger = logger
|
||||
}
|
||||
|
||||
// GinRecovery 自定义Recovery中间件(与 gva 保持一致)
|
||||
// GinRecovery 自定义Recovery中间件(与 kra 保持一致)
|
||||
func GinRecovery(stack bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"kra/pkg/jwt"
|
||||
|
||||
khttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// claimsKey 用于context存储claims的key
|
||||
type claimsKey struct{}
|
||||
|
||||
// GetAuthorityID 从Kratos HTTP Context获取用户角色ID
|
||||
func GetAuthorityID(ctx khttp.Context) uint {
|
||||
claims, ok := GetClaims(ctx)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return claims.AuthorityID
|
||||
}
|
||||
|
||||
// GetUserID 从Kratos HTTP Context获取用户ID
|
||||
func GetUserID(ctx khttp.Context) uint {
|
||||
claims, ok := GetClaims(ctx)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return claims.BaseClaims.ID
|
||||
}
|
||||
|
||||
// GetClaims 从Kratos HTTP Context获取JWT Claims
|
||||
func GetClaims(ctx khttp.Context) (*jwt.CustomClaims, bool) {
|
||||
// 尝试从request context获取
|
||||
if claims, ok := ctx.Request().Context().Value(claimsKey{}).(*jwt.CustomClaims); ok {
|
||||
return claims, true
|
||||
}
|
||||
// 尝试使用字符串key
|
||||
if claims, ok := ctx.Request().Context().Value("claims").(*jwt.CustomClaims); ok {
|
||||
return claims, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetToken 从Kratos HTTP Context获取Token
|
||||
func GetToken(ctx khttp.Context) string {
|
||||
// 从header获取
|
||||
token := ctx.Request().Header.Get("x-token")
|
||||
if token != "" {
|
||||
return token
|
||||
}
|
||||
|
||||
// 从cookie获取
|
||||
if cookie, err := ctx.Request().Cookie("x-token"); err == nil {
|
||||
return cookie.Value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetUserUUID 从Kratos HTTP Context获取用户UUID
|
||||
func GetUserUUID(ctx khttp.Context) string {
|
||||
claims, ok := GetClaims(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return claims.UUID
|
||||
}
|
||||
Reference in New Issue
Block a user