更新底层架构

This commit is contained in:
2026-03-18 21:51:17 +08:00
parent b69c6ccbca
commit 68bea98b81
71 changed files with 5220 additions and 7619 deletions

View File

@@ -1,7 +1,7 @@
package middleware
import (
"networkDev/web"
"NetworkAuth/web"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"

54
middleware/install.go Normal file
View File

@@ -0,0 +1,54 @@
package middleware
import (
"NetworkAuth/services"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// InstallCheckMiddleware 检查系统是否已安装
func InstallCheckMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
// 放行静态资源和favicon
if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") || path == "/favicon.ico" {
c.Next()
return
}
// 检查是否为安装相关的路由
isInstallRoute := path == "/install" || path == "/api/install"
// 获取系统的安装状态
// 在没有数据库的时候GetSettingsService().GetString 会返回默认值 "0"
isInstalled := services.GetSettingsService().GetString("is_installed", "0") == "1"
// 如果未安装且当前不是访问安装页面,则重定向到安装页面
if !isInstalled && !isInstallRoute {
// 对于 API 请求,返回 JSON 提示
if strings.HasPrefix(path, "/api/") || strings.Contains(path, "/api/") {
c.JSON(http.StatusForbidden, gin.H{
"code": 403,
"msg": "系统未初始化,请先完成安装",
})
c.Abort()
return
}
c.Redirect(http.StatusTemporaryRedirect, "/install")
c.Abort()
return
}
// 如果已安装但尝试访问安装页面,则重定向到首页或后台
if isInstalled && isInstallRoute {
c.Redirect(http.StatusTemporaryRedirect, "/admin")
c.Abort()
return
}
c.Next()
}
}

View File

@@ -1,11 +1,13 @@
package middleware
import (
"strings"
"time"
"NetworkAuth/utils/logger"
"github.com/gin-gonic/gin"
"networkDev/utils/logger"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
// ============================================================================
@@ -34,76 +36,49 @@ func NewLoggingMiddleware(logger *logger.Logger) *LoggingMiddleware {
// ============================================================================
// Handler 返回Gin中间件函数用于记录HTTP请求日志
// 记录格式遵循Apache Common Log Format
// 记录格式参考了更灵活的 NetworkAuth 实现,支持配置开关和日志级别检查
func (lm *LoggingMiddleware) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
// 记录开始时间
// 检查是否启用了访问日志
if !viper.GetBool("server.access_log") {
c.Next()
return
}
// 如果日志级别不是Debug或更高Trace则不记录访问日志
// 避免在Info级别输出过多的访问日志干扰正常业务日志
if lm.logger.Level < logrus.DebugLevel {
c.Next()
return
}
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// 处理请求
c.Next()
// 计算处理时间
// 计算响应时间
duration := time.Since(start)
// 获取客户端IP
clientIP := getClientIP(c)
if raw != "" {
path = path + "?" + raw
}
// 记录日志 - Apache Common Log Format
// 使用专门的HTTP日志方法避免User-Agent中的反斜杠被转义
// 记录请求日志
lm.logger.LogRequestWithHeaders(
c.Request.Method,
c.Request.RequestURI,
clientIP,
path,
c.ClientIP(), // 使用 Gin 内置的方法获取 IP
c.Writer.Status(),
duration,
"-", // referer (已废弃)
c.Errors.ByType(gin.ErrorTypePrivate).String(),
c.Request.UserAgent(),
)
}
}
// ============================================================================
// 私有函数
// ============================================================================
// getClientIP 获取客户端真实IP地址
// 优先从X-Forwarded-For、X-Real-IP等头部获取最后使用RemoteAddr
func getClientIP(c *gin.Context) string {
// 检查X-Forwarded-For头部
xForwardedFor := c.GetHeader("X-Forwarded-For")
if xForwardedFor != "" {
// X-Forwarded-For可能包含多个IP取第一个
ips := strings.Split(xForwardedFor, ",")
if len(ips) > 0 {
return strings.TrimSpace(ips[0])
}
}
// 检查X-Real-IP头部
xRealIP := c.GetHeader("X-Real-IP")
if xRealIP != "" {
return xRealIP
}
// 检查X-Forwarded头部
xForwarded := c.GetHeader("X-Forwarded")
if xForwarded != "" {
return xForwarded
}
// 使用RemoteAddr
remoteAddr := c.Request.RemoteAddr
if strings.Contains(remoteAddr, ":") {
// 移除端口号
if idx := strings.LastIndex(remoteAddr, ":"); idx != -1 {
return remoteAddr[:idx]
}
}
return remoteAddr
}
// ============================================================================
// 公共函数
// ============================================================================
@@ -111,7 +86,7 @@ func getClientIP(c *gin.Context) string {
// WrapHandler 创建Gin日志中间件
// 使用全局日志记录器创建日志中间件
func WrapHandler() gin.HandlerFunc {
logger := logger.GetLogger()
middleware := NewLoggingMiddleware(logger)
log := logger.GetLogger()
middleware := NewLoggingMiddleware(log)
return middleware.Handler()
}

99
middleware/maintenance.go Normal file
View File

@@ -0,0 +1,99 @@
package middleware
import (
"net/http"
"strings"
"NetworkAuth/services"
"github.com/gin-gonic/gin"
)
// MaintenanceMiddleware 维护模式中间件
// 当开启维护模式时,拦截非白名单请求
func MaintenanceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 检查是否开启维护模式
if !services.GetSettingsService().IsMaintenanceMode() {
c.Next()
return
}
// 白名单检查(路径前缀匹配)
path := c.Request.URL.Path
// 1. 允许静态资源
if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") || path == "/favicon.ico" {
c.Next()
return
}
// 2. 允许管理员后台相关接口(以便管理员登录关闭维护模式)
// 包括登录页、登录接口、API接口、CSRF Token等
if strings.HasPrefix(path, "/admin") {
c.Next()
return
}
// 3. 检查请求类型
// AJAX/JSON 请求返回 503 JSON
accept := c.GetHeader("Accept")
xrw := strings.ToLower(strings.TrimSpace(c.GetHeader("X-Requested-With")))
if strings.Contains(accept, "application/json") || xrw == "xmlhttprequest" || strings.HasPrefix(path, "/api/") {
c.JSON(http.StatusServiceUnavailable, gin.H{
"code": 503,
"success": false,
"msg": "系统正在维护中,请稍后再试",
})
c.Abort()
return
}
// 4. 普通页面请求渲染维护页面
c.Header("Content-Type", "text/html; charset=utf-8")
c.Status(http.StatusServiceUnavailable)
c.Writer.WriteString(maintenanceHTML)
c.Abort()
}
}
// 简单的维护页面 HTML
const maintenanceHTML = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统维护中</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f0f2f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
color: #333;
}
.container {
text-align: center;
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
max-width: 500px;
width: 90%;
}
h1 { font-size: 24px; margin-bottom: 16px; color: #1890ff; }
p { font-size: 16px; color: #666; line-height: 1.6; }
.icon { font-size: 64px; margin-bottom: 24px; color: #faad14; }
</style>
</head>
<body>
<div class="container">
<div class="icon">⚠️</div>
<h1>系统维护中</h1>
<p>为了提供更好的服务,系统正在进行升级维护。<br>请稍后访问,给您带来的不便敬请谅解。</p>
</div>
</body>
</html>`