侧边栏壁纸
博主头像
Wood Chen

万事如意

  • 累计撰写 242 篇文章
  • 累计创建 203 个标签
  • 累计收到 10 条评论

目 录CONTENT

文章目录

go 实现 SPA单页应用 (Nextjs, react等) 伪静态规则

wood
2025-06-14 / 0 评论 / 0 点赞 / 13 阅读 / 0 字

最初发布在 https://www.sunai.net/t/745

比较简单的应用, 如果最后运行用前后端分离+nginx会有点麻烦, 还有如果像random-api-go 这样动态路由的, 就不太好代理.
所以简单的实现方法就是把前端打包后, 让go来进行静态文件服务

handlers/static_handler.go

package handlers

import (
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

type StaticHandler struct {
	staticDir string
}

func NewStaticHandler(staticDir string) *StaticHandler {
	return &StaticHandler{
		staticDir: staticDir,
	}
}

// ServeStatic 处理静态文件请求,实现类似 Nginx try_files 的逻辑
func (s *StaticHandler) ServeStatic(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path

	// 实现 try_files $uri $uri/ @router 逻辑
	filePath := s.tryFiles(path)

	// 设置正确的 Content-Type
	s.setContentType(w, filePath)

	// 服务文件
	http.ServeFile(w, r, filePath)
}

// tryFiles 实现类似 Nginx try_files 的逻辑
func (s *StaticHandler) tryFiles(requestPath string) string {
	// 清理路径
	if requestPath == "" {
		requestPath = "/"
	}

	// 移除查询参数
	if idx := strings.Index(requestPath, "?"); idx != -1 {
		requestPath = requestPath[:idx]
	}

	// 1. 尝试 $uri - 直接文件路径
	directPath := filepath.Join(s.staticDir, requestPath)
	if s.fileExists(directPath) && !s.isDirectory(directPath) {
		return directPath
	}

	// 2. 尝试 $uri/ - 目录下的 index.html
	if requestPath != "/" {
		dirPath := filepath.Join(s.staticDir, requestPath)
		if s.isDirectory(dirPath) {
			indexPath := filepath.Join(dirPath, "index.html")
			if s.fileExists(indexPath) {
				return indexPath
			}
		}

		// 也尝试添加 .html 扩展名
		htmlPath := directPath + ".html"
		if s.fileExists(htmlPath) {
			return htmlPath
		}
	}

	// 3. @router - 回退到根目录的 index.html (SPA 路由处理)
	fallbackPath := filepath.Join(s.staticDir, "index.html")
	return fallbackPath
}

// fileExists 检查文件是否存在
func (s *StaticHandler) fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

// isDirectory 检查路径是否为目录
func (s *StaticHandler) isDirectory(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return info.IsDir()
}

// setContentType 设置正确的 Content-Type
func (s *StaticHandler) setContentType(w http.ResponseWriter, filePath string) {
	ext := strings.ToLower(filepath.Ext(filePath))

	switch ext {
	case ".html":
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
	case ".css":
		w.Header().Set("Content-Type", "text/css; charset=utf-8")
	case ".js":
		w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
	case ".json":
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	case ".png":
		w.Header().Set("Content-Type", "image/png")
	case ".jpg", ".jpeg":
		w.Header().Set("Content-Type", "image/jpeg")
	case ".gif":
		w.Header().Set("Content-Type", "image/gif")
	case ".svg":
		w.Header().Set("Content-Type", "image/svg+xml")
	case ".ico":
		w.Header().Set("Content-Type", "image/x-icon")
	}
}

评论区