feat: 官网 + 管理端 + Gin/GORM 后端首个完整版本
- web/: 产品官网(Hero 事件流终端、功能矩阵、架构、快速开始、下载、博客 + Markdown 详情页),青瓷绿双主题 - admin/: 内容管理(JWT 登录、文章分页/搜索/CRUD、草稿与发布),embed 挂 /admin - server/: Gin + GORM,MySQL(DSN 走 .env,SQLite 兜底);规范落地:sundynix_ 表前缀、字符串雪花主键、snake_case 列名、统一响应信封、公共分页参数 - 双 SPA embed 单二进制部署,make build 一键出包 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.sundynix.cn/Blizzard/sundynix-site/server/internal/handler"
|
||||
"git.sundynix.cn/Blizzard/sundynix-site/server/internal/middleware"
|
||||
"git.sundynix.cn/Blizzard/sundynix-site/server/internal/pkg/resp"
|
||||
"git.sundynix.cn/Blizzard/sundynix-site/server/internal/webfs"
|
||||
)
|
||||
|
||||
func New(db *gorm.DB) (*gin.Engine, error) {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger(), gin.Recovery(), middleware.CORS())
|
||||
|
||||
// ── 公开 API ──
|
||||
api := r.Group("/api")
|
||||
{
|
||||
api.GET("/healthz", func(c *gin.Context) {
|
||||
resp.OK(c, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
posts := handler.NewPostHandler(db)
|
||||
api.GET("/posts", posts.List)
|
||||
api.GET("/posts/:slug", posts.Get)
|
||||
}
|
||||
|
||||
// ── 管理端 API ──
|
||||
adminAuth := handler.NewAdminAuthHandler()
|
||||
api.POST("/admin/login", adminAuth.Login)
|
||||
|
||||
adminAPI := api.Group("/admin", middleware.Auth())
|
||||
{
|
||||
posts := handler.NewAdminPostHandler(db)
|
||||
adminAPI.GET("/posts", posts.List)
|
||||
adminAPI.POST("/posts", posts.Create)
|
||||
adminAPI.GET("/posts/:id", posts.Get)
|
||||
adminAPI.PUT("/posts/:id", posts.Update)
|
||||
adminAPI.DELETE("/posts/:id", posts.Delete)
|
||||
}
|
||||
|
||||
// ── 静态站点:/admin → 管理端,其余 → 用户端 ──
|
||||
webDist, err := webfs.WebDist()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adminDist, err := webfs.AdminDist()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webServer := http.FileServer(http.FS(webDist))
|
||||
adminServer := http.StripPrefix("/admin", http.FileServer(http.FS(adminDist)))
|
||||
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
path := c.Request.URL.Path
|
||||
if strings.HasPrefix(path, "/api/") {
|
||||
resp.NotFound(c, "接口不存在")
|
||||
return
|
||||
}
|
||||
if path == "/admin" || strings.HasPrefix(path, "/admin/") {
|
||||
serveSPA(c, adminDist, adminServer, strings.TrimPrefix(path, "/admin"), "/admin/")
|
||||
return
|
||||
}
|
||||
serveSPA(c, webDist, webServer, path, "/")
|
||||
})
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// serveSPA 真实文件直接吐,否则回退 index.html 交给前端路由。
|
||||
func serveSPA(c *gin.Context, dist fs.FS, server http.Handler, rel, fallback string) {
|
||||
p := strings.TrimPrefix(rel, "/")
|
||||
if p != "" {
|
||||
if _, err := fs.Stat(dist, p); err == nil {
|
||||
server.ServeHTTP(c.Writer, c.Request)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Request.URL.Path = fallback
|
||||
server.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
Reference in New Issue
Block a user