后端重构

This commit is contained in:
Yvan
2026-01-14 14:34:49 +08:00
parent f5f77b32fe
commit 30842b42e5
274 changed files with 39754 additions and 1005 deletions
+1 -1
View File
@@ -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)
+86
View File
@@ -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
}
+108
View File
@@ -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))
}
}
}
}
}
}
+1 -1
View File
@@ -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中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
+14 -1
View File
@@ -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 默认限流中间件
+62
View File
@@ -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()
}
}
+11 -5
View File
@@ -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,
+1 -1
View File
@@ -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
}