Files
Meshray-Manager/docs/前端服务机制排查报告.md
T
2026-06-30 15:14:37 +08:00

10 KiB
Raw Blame History

MeshRay - 前端服务机制完整排查报告

排查时间: 2026-03-24
排查内容: 程序启动后如何提供前端页面


🎯 核心结论

前端服务完全正常! 所有静态资源都正确加载。


📊 完整的前端服务流程

流程图:

graph TD
    A[程序启动 main.go] --> B[调用 api.NewServer]
    B --> C[初始化 Gin 引擎]
    C --> D[加载 embed.FS 静态资源]
    D --> E{检查 index.html}
    E -->|存在 | F[使用内嵌资源 ✅]
    E -->|不存在 | G[使用外部目录]
    F --> H[注册 StaticFS 路由]
    H --> I[注册 NoRoute 处理 SPA]
    I --> J[启动 HTTP 服务 :9531]
    J --> K[用户访问 /]
    K --> L[返回 index.html]
    L --> M[浏览器加载 JS/CSS]
    M --> N[Vue 应用启动]
    N --> O[显示登录页面]

🔍 详细排查结果

Step 1: 后端启动流程

文件: cmd/meshray/main.go

// 第 94 行:创建 API 服务器
server, err := api.NewServer(cfg, logger, store)
if err != nil {
    logger.Fatal("创建服务器失败", zap.Error(err))
}

// 第 103 行:启动服务
server.Start()

执行结果:

✅ 配置加载成功
✅ 日志系统初始化成功
✅ 数据库初始化成功
✅ 默认策略初始化成功
🚀 正在启动 MeshRay...

Step 2: 静态资源嵌入

文件: internal/api/server.go

// 第 94-109 行:使用 embed 的静态文件
var staticFS fs.FS
var useEmbed bool

// 直接使用 web/dist 包中的 WebAssets
if embedFS, err := fs.Sub(dist.WebAssets, "."); err == nil {
    // 检查是否有 index.html
    if _, statErr := fs.Stat(embedFS, "index.html"); statErr == nil {
        staticFS = embedFS
        useEmbed = true
        s.logger.Info("使用内嵌的静态文件")
    }
}

关键组件:

  1. dist.WebAssets: 来自 web/dist/embed.go
  2. embed.FS: Go 1.16+ 的内嵌文件系统
  3. fs.Sub(): 创建子目录视图

Step 3: embed.go 定义

文件: web/dist/embed.go

package dist

import "embed"

//go:embed *
var WebAssets embed.FS // MeshRay frontend assets

作用:

  • web/dist/ 下的所有文件嵌入到 Go 二进制
  • 通过 WebAssets 变量访问
  • 编译时打包,运行时直接读取内存

Step 4: 静态文件服务配置

文件: internal/api/server.go

if staticFS != nil {
    // 创建 HTTP 文件系统
    httpFS := http.FS(staticFS)
    
    // 先注册静态文件目录(优先级高)
    s.engine.StaticFS("/assets", httpFS)
    s.engine.StaticFS("/static", httpFS)
    
    // 再注册 NoRoute 处理 SPA 路由(优先级低)
    s.engine.NoRoute(func(c *gin.Context) {
        path := c.Request.URL.Path
        
        // API 请求返回 404
        if strings.HasPrefix(path, "/api/") {
            c.JSON(404, gin.H{"error": "API not found"})
            return
        }
        
        // 尝试直接提供文件
        filePath := strings.TrimPrefix(path, "/")
        if filePath == "" {
            filePath = "index.html"
        }
        
        // 读取并返回文件内容
        file, err := staticFS.Open(filePath)
        if err == nil {
            defer file.Close()
            content, _ := io.ReadAll(file)
            c.Data(200, getContentType(filePath), content)
            return
        }
        
        // 其他请求都返回 index.htmlSPA 路由支持)
        file, _ = staticFS.Open("index.html")
        if file != nil {
            defer file.Close()
            content, _ := io.ReadAll(file)
            c.Data(200, "text/html; charset=utf-8", content)
        }
    })
}

关键点:

  1. StaticFS 注册: /assets/static 目录
  2. NoRoute 兜底: 处理 Vue Router 的 SPA 路由
  3. MIME 类型: 通过 getContentType() 设置正确的 Content-Type

Step 5: 实际文件验证

编译后的前端文件:

web/dist/
├── embed.go              (99 bytes)   ✅ embed 定义
├── index.html            (639 bytes)  ✅ 入口 HTML
└── assets/                            ✅ JS/CSS 资源
    ├── index-AIAUiq89.js
    ├── index-JLRO43JB.css
    ├── vue-vendor-BBChLKcR.js
    ├── element-plus-CWITzeOz.js
    ├── Dashboard-DSsX0xA1.js
    └── ... (共 44 个文件)

生成时间: 2026/3/24 20:44:21 最新


Step 6: 服务启动日志

日志内容:

{
  "level": "info",
  "time": "2026-03-24T20:44:55.330+0800",
  "caller": "api/server.go:103",
  "message": "使用内嵌的静态文件"  
}
{
  "level": "info",
  "time": "2026-03-24T20:44:55.330+0800",
  "caller": "api/server.go:394",
  "message": "Starting MeshRay",
  "address": ":9531"  
}

Step 7: 实际访问测试

测试 1: 访问根路径

curl.exe http://localhost:9531/

返回:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <title>MeshRay - 简单、高效异地组网</title>
    <script type="module" crossorigin src="/assets/index-AIAUiq89.js"></script>
    <link rel="modulepreload" crossorigin href="/assets/vue-vendor-BBChLKcR.js">
    <link rel="stylesheet" crossorigin href="/assets/index-JLRO43JB.css">
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

HTML 正确返回,包含最新的 JS hashAIAUiq89


测试 2: 访问 JS 文件

curl.exe http://localhost:9531/assets/index-AIAUiq89.js -I

返回:

HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8

MIME 类型正确


测试 3: 服务器日志

Get-Content logs\meshray.log -Tail 20

显示:

status": 200, path: "/assets/index-AIAUiq89.js"  ✅
status": 200, path: "/assets/vue-vendor-BBChLKcR.js"  ✅
status": 200, path: "/assets/element-plus-CWITzeOz.js"  ✅
status": 200, path: "/assets/Dashboard-DSsX0xA1.js"  ✅
status": 200, path: "/"  ✅

所有静态资源返回 200 OK


🏗️ 架构设计总结

1. 单文件部署策略

┌─────────────────────────┐
│   meshray.exe          │
│  ┌──────────────────┐  │
│  │  Go 二进制代码   │  │
│  │  ┌────────────┐ │  │
│  │  │ embed.FS   │ │  │
│  │  │ 前端资源   │ │  │
│  │  └────────────┘ │  │
│  └──────────────────┘  │
└─────────────────────────┘

优势:

  • 无需单独部署前端
  • 避免 CORS 问题
  • 简化运维
  • 版本一致性

2. 路由优先级

graph LR
    A[请求到达] --> B{路径匹配?}
    B -->|/api/*| C[API 路由]
    B -->|/assets/*| D[StaticFS]
    B -->|/static/*| E[StaticFS]
    B -->|其他 | F[NoRoute]
    F --> G{是 SPA 路由?}
    G -->|是 | H[返回 index.html]
    G -->|否 | I[返回 404]

优先级顺序:

  1. 中间件(认证、日志)
  2. 精确路由(/api/v1/*
  3. StaticFS/assets/*, /static/*
  4. NoRoute(兜底,处理 SPA

3. 数据流

sequenceDiagram
    participant User as 用户浏览器
    participant Server as Gin 服务器
    participant Embed as embed.FS
    participant Vue as Vue 应用
    
    User->>Server: GET /
    Server->>Embed: Open("index.html")
    Embed-->>Server: HTML 内容
    Server->>User: HTML + JS 引用
    
    User->>Server: GET /assets/index.js
    Server->>Embed: Open("assets/index.js")
    Embed-->>Server: JS 内容
    Server->>User: application/javascript
    
    User->>User: 执行 Vue 应用
    User->>Server: GET /api/v1/dashboard/stats
    Server->>Server: 业务逻辑处理
    Server->>User: JSON 数据

验证清单

完成以下检查确认前端服务正常:

  • web/dist/embed.go 存在
  • web/dist/index.html 存在且最新
  • web/dist/assets/*.js 存在且最新
  • 后端编译成功(包含 embed
  • 服务启动日志显示"使用内嵌的静态文件"
  • curl 测试 / 返回正确的 HTML
  • curl 测试 /assets/*.js 返回 200 + application/javascript
  • 服务器日志显示所有静态资源 200 OK
  • 浏览器访问能看到登录页面(需手动测试)

🎯 当前状态

后端:

✅ 服务运行在 :9531
✅ embed 静态资源已加载
✅ 所有路由配置正确
✅ MIME 类型设置正确

前端:

✅ index.html 已编译(最新 hash: AIAUiq89
✅ JS 文件已编译(44 个文件)
✅ CSS 文件已编译
✅ Vue Router 配置正确
✅ 登录页组件存在

通信:

✅ 静态资源返回 200 OK
✅ Content-Type 正确
✅ SPA 路由支持正常
✅ API 路由隔离正常

📋 关键文件清单

文件 作用 状态
cmd/meshray/main.go 程序入口
internal/api/server.go 服务器配置
web/dist/embed.go embed 定义
web/dist/index.html 入口 HTML
web/dist/assets/*.js Vue 应用
web/src/router/index.js 路由配置
web/src/views/Login.vue 登录组件

🎉 结论

前端服务机制:完全正常!

证据:

  1. embed.go 正确定义
  2. 前端已重新编译
  3. 后端成功嵌入静态资源
  4. 服务启动日志正常
  5. curl 测试返回正确
  6. 服务器日志显示所有资源 200 OK

下一步:

  • 请在浏览器中访问 http://localhost:9531
  • 应该能看到登录页面
  • 如果看不到,请截图错误信息

MeshRay - 抽丝剥茧,真相大白! 🔧