修改项目为前后端分离方案

This commit is contained in:
2026-03-28 23:30:02 +08:00
parent d8536354d4
commit 7a7d3aeaaa
77 changed files with 1447 additions and 23765 deletions

21
middleware/cors.go Normal file
View File

@@ -0,0 +1,21 @@
package middleware
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
// CorsMiddleware 处理跨域请求
// 允许 Vue 等前端分离架构在开发和生产环境下访问后端 API
func CorsMiddleware() gin.HandlerFunc {
return cors.New(cors.Config{
AllowOriginFunc: func(origin string) bool { return true }, // 允许所有来源
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization", "X-CSRF-Token", "Accept"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
})
}

View File

@@ -1,8 +1,6 @@
package middleware
import (
"NetworkAuth/web"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
@@ -13,8 +11,6 @@ import (
// DevModeConfig 开发模式配置
type DevModeConfig struct {
// 是否启用模板热重载
EnableTemplateReload bool
// 是否跳过验证码验证
SkipCaptcha bool
// 是否显示详细错误信息
@@ -29,7 +25,7 @@ type DevModeConfig struct {
// DevModeMiddleware 开发模式中间件
// 统一管理所有开发模式相关的功能
func DevModeMiddleware(engine *gin.Engine) gin.HandlerFunc {
func DevModeMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 检查是否为开发模式
if IsDevMode() {
@@ -37,12 +33,6 @@ func DevModeMiddleware(engine *gin.Engine) gin.HandlerFunc {
c.Set("dev_mode", true)
c.Set("dev_config", GetDevModeConfig())
// 如果启用了模板热重载,则重新加载模板
config := GetDevModeConfig()
if config.EnableTemplateReload {
reloadTemplates(engine)
}
// 设置开发模式相关的响应头
c.Header("X-Dev-Mode", "true")
} else {
@@ -69,10 +59,9 @@ func GetDevModeConfig() DevModeConfig {
}
return DevModeConfig{
EnableTemplateReload: true, // 开发模式下默认启用模板热重载
SkipCaptcha: true, // 开发模式下默认跳过验证码
ShowDetailedErrors: true, // 开发模式下显示详细错误
EnableDebugLog: true, // 开发模式下启用调试日志
SkipCaptcha: true, // 开发模式下默认跳过验证码
ShowDetailedErrors: true, // 开发模式下显示详细错误
EnableDebugLog: true, // 开发模式下启用调试日志
}
}
@@ -107,10 +96,3 @@ func ShouldSkipCaptcha(c *gin.Context) bool {
// ============================================================================
// 私有函数
// ============================================================================
// reloadTemplates 重新加载模板(内部函数)
func reloadTemplates(engine *gin.Engine) {
if tmpl, err := web.ParseTemplates(); err == nil {
engine.SetHTMLTemplate(tmpl)
}
}

View File

@@ -3,9 +3,10 @@ package middleware
import (
"NetworkAuth/services"
"net/http"
"strings"
"os"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// InstallCheckMiddleware 检查系统是否已安装
@@ -13,38 +14,52 @@ 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"
isInstallRoute := path == "/api/install" || path == "/api/install/"
// 获取系统的安装状态
// 在没有数据库的时候GetSettingsService().GetString 会返回默认值 "0"
isInstalled := services.GetSettingsService().GetString("is_installed", "0") == "1"
isInstalledStr := services.GetSettingsService().GetString("is_installed", "0")
isInstalled := isInstalledStr == "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
// 如果设置服务没获取到(因为未连接数据库),再结合文件判断
if !isInstalled {
// 检查数据库文件是否存在(如果是 sqlite
dbType := viper.GetString("database.type")
switch dbType {
case "sqlite":
dbPath := viper.GetString("database.sqlite.path")
if dbPath == "" {
dbPath = "./database.db"
}
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
isInstalled = false
} else {
isInstalled = true
}
case "mysql":
// 如果是 mysql 且配置了 database我们认为是已安装
dbName := viper.GetString("database.mysql.database")
if dbName != "" {
isInstalled = true
}
}
c.Redirect(http.StatusTemporaryRedirect, "/install")
}
// 如果未安装且不是访问安装接口,则返回 403 JSON
if !isInstalled && !isInstallRoute {
c.JSON(http.StatusForbidden, gin.H{
"code": 403,
"msg": "系统未初始化,请先完成安装",
})
c.Abort()
return
}
// 如果已安装但尝试访问安装页面,则重定向到首页或后台
// 如果已安装但尝试访问安装接口,则返回 403 JSON
if isInstalled && isInstallRoute {
c.Redirect(http.StatusTemporaryRedirect, "/admin")
c.JSON(http.StatusForbidden, gin.H{
"code": 403,
"msg": "系统已安装,请勿重复初始化",
})
c.Abort()
return
}

View File

@@ -36,7 +36,7 @@ func NewLoggingMiddleware(logger *logger.Logger) *LoggingMiddleware {
// ============================================================================
// Handler 返回Gin中间件函数用于记录HTTP请求日志
// 记录格式参考了更灵活的 NetworkAuth 实现,支持配置开关和日志级别检查
// 记录格式参考了更灵活的NetworkAuth实现支持配置开关和日志级别检查
func (lm *LoggingMiddleware) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
// 检查是否启用了访问日志

View File

@@ -19,81 +19,21 @@ func MaintenanceMiddleware() gin.HandlerFunc {
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") {
if strings.HasPrefix(path, "/api/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)
// 返回 503 JSON
c.JSON(http.StatusServiceUnavailable, gin.H{
"code": 503,
"success": false,
"msg": "系统正在维护中,请稍后再试",
})
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>`